diff --git a/.deploy/docker/build-amd64.sh b/.deploy/docker/build-amd64.sh index 50121026de..3d8cd8f31a 100755 --- a/.deploy/docker/build-amd64.sh +++ b/.deploy/docker/build-amd64.sh @@ -3,8 +3,6 @@ # build image echo "$DOCKER_PASSWORD" | docker login -u "$DOCKER_USERNAME" --password-stdin - - if [ "$TRAVIS_BRANCH" == "develop" ]; then echo "Build develop amd64" docker build -t jc5x/firefly-iii:develop-amd64 -f Dockerfile.amd64 . diff --git a/.deploy/docker/build-arm.sh b/.deploy/docker/build-arm.sh index b10b5eb6f1..4ea42d38e9 100755 --- a/.deploy/docker/build-arm.sh +++ b/.deploy/docker/build-arm.sh @@ -2,7 +2,6 @@ docker run --rm --privileged multiarch/qemu-user-static:register --reset - # get qemu-arm-static binary mkdir tmp pushd tmp && \ diff --git a/.deploy/docker/entrypoint.sh b/.deploy/docker/entrypoint.sh index 554973bd12..566dd7eae8 100755 --- a/.deploy/docker/entrypoint.sh +++ b/.deploy/docker/entrypoint.sh @@ -2,8 +2,6 @@ echo "Now in entrypoint.sh for Firefly III" -lscpu - # make sure the correct directories exists (suggested by @chrif): echo "Making directories..." mkdir -p $FIREFLY_PATH/storage/app/public @@ -27,12 +25,6 @@ then echo "Touched!" fi -if [[ $FF_DB_CONNECTION == "sqlite" ]] -then - touch $FIREFLY_PATH/storage/database/database.sqlite - echo "Touched!" -fi - # make sure we own the volumes: echo "Run chown on ${FIREFLY_PATH}/storage..." chown -R www-data:www-data -R $FIREFLY_PATH/storage @@ -43,22 +35,64 @@ chmod -R 775 $FIREFLY_PATH/storage echo "Remove log file..." rm -f $FIREFLY_PATH/storage/logs/laravel.log -echo "Map environment variables on .env file..." -cat $FIREFLY_PATH/.deploy/docker/.env.docker | envsubst > $FIREFLY_PATH/.env echo "Dump auto load..." composer dump-autoload echo "Discover packages..." php artisan package:discover echo "Run various artisan commands..." +if [[ -z "$DB_PORT" ]]; then + if [[ $DB_CONNECTION == "pgsql" ]]; then + DB_PORT=5432 + elif [[ $DB_CONNECTION == "mysql" ]]; then + DB_PORT=3306 + fi +fi +if [[ ! -z "$DB_PORT" ]]; then + $FIREFLY_PATH/.deploy/docker/wait-for-it.sh "${DB_HOST}:${DB_PORT}" -- echo "db is up. Time to execute artisan commands" +fi +#env $(grep -v "^\#" .env | xargs) +php artisan cache:clear php artisan migrate --seed -php artisan firefly:decrypt-all -php artisan firefly:upgrade-database -php artisan firefly:verify +php artisan firefly-iii:decrypt-all + +# there are 12 upgrade commands +php artisan firefly-iii:transaction-identifiers +php artisan firefly-iii:migrate-to-groups +php artisan firefly-iii:account-currencies +php artisan firefly-iii:transfer-currencies +php artisan firefly-iii:other-currencies +php artisan firefly-iii:migrate-notes +php artisan firefly-iii:migrate-attachments +php artisan firefly-iii:bills-to-rules +php artisan firefly-iii:bl-currency +php artisan firefly-iii:cc-liabilities +php artisan firefly-iii:back-to-journals +php artisan firefly-iii:rename-account-meta + +# there are 13 verify commands +php artisan firefly-iii:fix-piggies +php artisan firefly-iii:create-link-types +php artisan firefly-iii:create-access-tokens +php artisan firefly-iii:remove-bills +php artisan firefly-iii:enable-currencies +php artisan firefly-iii:fix-transfer-budgets +php artisan firefly-iii:fix-uneven-amount +php artisan firefly-iii:delete-zero-amount +php artisan firefly-iii:delete-orphaned-transactions + php artisan firefly-iii:delete-empty-journals +php artisan firefly-iii:delete-empty-groups +php artisan firefly-iii:fix-account-types +php artisan firefly-iii:rename-meta-fields + +# report commands +php artisan firefly-iii:report-empty-objects +php artisan firefly-iii:report-sum + php artisan passport:install php artisan cache:clear php artisan firefly:instructions install echo "Go!" -exec apache2-foreground \ No newline at end of file +exec apache2-foreground diff --git a/.deploy/docker/wait-for-it.sh b/.deploy/docker/wait-for-it.sh new file mode 100755 index 0000000000..071c2bee3e --- /dev/null +++ b/.deploy/docker/wait-for-it.sh @@ -0,0 +1,178 @@ +#!/usr/bin/env bash +# Use this script to test if a given TCP host/port are available + +WAITFORIT_cmdname=${0##*/} + +echoerr() { if [[ $WAITFORIT_QUIET -ne 1 ]]; then echo "$@" 1>&2; fi } + +usage() +{ + cat << USAGE >&2 +Usage: + $WAITFORIT_cmdname host:port [-s] [-t timeout] [-- command args] + -h HOST | --host=HOST Host or IP under test + -p PORT | --port=PORT TCP port under test + Alternatively, you specify the host and port as host:port + -s | --strict Only execute subcommand if the test succeeds + -q | --quiet Don't output any status messages + -t TIMEOUT | --timeout=TIMEOUT + Timeout in seconds, zero for no timeout + -- COMMAND ARGS Execute command with args after the test finishes +USAGE + exit 1 +} + +wait_for() +{ + if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then + echoerr "$WAITFORIT_cmdname: waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT" + else + echoerr "$WAITFORIT_cmdname: waiting for $WAITFORIT_HOST:$WAITFORIT_PORT without a timeout" + fi + WAITFORIT_start_ts=$(date +%s) + while : + do + if [[ $WAITFORIT_ISBUSY -eq 1 ]]; then + nc -z $WAITFORIT_HOST $WAITFORIT_PORT + WAITFORIT_result=$? + else + (echo > /dev/tcp/$WAITFORIT_HOST/$WAITFORIT_PORT) >/dev/null 2>&1 + WAITFORIT_result=$? + fi + if [[ $WAITFORIT_result -eq 0 ]]; then + WAITFORIT_end_ts=$(date +%s) + echoerr "$WAITFORIT_cmdname: $WAITFORIT_HOST:$WAITFORIT_PORT is available after $((WAITFORIT_end_ts - WAITFORIT_start_ts)) seconds" + break + fi + sleep 1 + done + return $WAITFORIT_result +} + +wait_for_wrapper() +{ + # In order to support SIGINT during timeout: http://unix.stackexchange.com/a/57692 + if [[ $WAITFORIT_QUIET -eq 1 ]]; then + timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --quiet --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT & + else + timeout $WAITFORIT_BUSYTIMEFLAG $WAITFORIT_TIMEOUT $0 --child --host=$WAITFORIT_HOST --port=$WAITFORIT_PORT --timeout=$WAITFORIT_TIMEOUT & + fi + WAITFORIT_PID=$! + trap "kill -INT -$WAITFORIT_PID" INT + wait $WAITFORIT_PID + WAITFORIT_RESULT=$? + if [[ $WAITFORIT_RESULT -ne 0 ]]; then + echoerr "$WAITFORIT_cmdname: timeout occurred after waiting $WAITFORIT_TIMEOUT seconds for $WAITFORIT_HOST:$WAITFORIT_PORT" + fi + return $WAITFORIT_RESULT +} + +# process arguments +while [[ $# -gt 0 ]] +do + case "$1" in + *:* ) + WAITFORIT_hostport=(${1//:/ }) + WAITFORIT_HOST=${WAITFORIT_hostport[0]} + WAITFORIT_PORT=${WAITFORIT_hostport[1]} + shift 1 + ;; + --child) + WAITFORIT_CHILD=1 + shift 1 + ;; + -q | --quiet) + WAITFORIT_QUIET=1 + shift 1 + ;; + -s | --strict) + WAITFORIT_STRICT=1 + shift 1 + ;; + -h) + WAITFORIT_HOST="$2" + if [[ $WAITFORIT_HOST == "" ]]; then break; fi + shift 2 + ;; + --host=*) + WAITFORIT_HOST="${1#*=}" + shift 1 + ;; + -p) + WAITFORIT_PORT="$2" + if [[ $WAITFORIT_PORT == "" ]]; then break; fi + shift 2 + ;; + --port=*) + WAITFORIT_PORT="${1#*=}" + shift 1 + ;; + -t) + WAITFORIT_TIMEOUT="$2" + if [[ $WAITFORIT_TIMEOUT == "" ]]; then break; fi + shift 2 + ;; + --timeout=*) + WAITFORIT_TIMEOUT="${1#*=}" + shift 1 + ;; + --) + shift + WAITFORIT_CLI=("$@") + break + ;; + --help) + usage + ;; + *) + echoerr "Unknown argument: $1" + usage + ;; + esac +done + +if [[ "$WAITFORIT_HOST" == "" || "$WAITFORIT_PORT" == "" ]]; then + echoerr "Error: you need to provide a host and port to test." + usage +fi + +WAITFORIT_TIMEOUT=${WAITFORIT_TIMEOUT:-15} +WAITFORIT_STRICT=${WAITFORIT_STRICT:-0} +WAITFORIT_CHILD=${WAITFORIT_CHILD:-0} +WAITFORIT_QUIET=${WAITFORIT_QUIET:-0} + +# check to see if timeout is from busybox? +WAITFORIT_TIMEOUT_PATH=$(type -p timeout) +WAITFORIT_TIMEOUT_PATH=$(realpath $WAITFORIT_TIMEOUT_PATH 2>/dev/null || readlink -f $WAITFORIT_TIMEOUT_PATH) +if [[ $WAITFORIT_TIMEOUT_PATH =~ "busybox" ]]; then + WAITFORIT_ISBUSY=1 + WAITFORIT_BUSYTIMEFLAG="-t" + +else + WAITFORIT_ISBUSY=0 + WAITFORIT_BUSYTIMEFLAG="" +fi + +if [[ $WAITFORIT_CHILD -gt 0 ]]; then + wait_for + WAITFORIT_RESULT=$? + exit $WAITFORIT_RESULT +else + if [[ $WAITFORIT_TIMEOUT -gt 0 ]]; then + wait_for_wrapper + WAITFORIT_RESULT=$? + else + wait_for + WAITFORIT_RESULT=$? + fi +fi + +if [[ $WAITFORIT_CLI != "" ]]; then + if [[ $WAITFORIT_RESULT -ne 0 && $WAITFORIT_STRICT -eq 1 ]]; then + echoerr "$WAITFORIT_cmdname: strict mode, refusing to execute subprocess" + exit $WAITFORIT_RESULT + fi + exec "${WAITFORIT_CLI[@]}" +else + exit $WAITFORIT_RESULT +fi diff --git a/.deploy/heroku/.locales b/.deploy/heroku/.locales index d56ea57b9a..2b029e9ff0 100644 --- a/.deploy/heroku/.locales +++ b/.deploy/heroku/.locales @@ -2,9 +2,13 @@ en_US es_ES de_DE fr_FR +ro_RO it_IT nl_NL pl_PL pt_BR ru_RU -nb_NO \ No newline at end of file +nb_NO +cs_CZ +id_ID +hu_HU diff --git a/.deploy/kubernetes/firefly.yaml b/.deploy/kubernetes/firefly.yaml index b2e3d2c912..1dc6309e84 100644 --- a/.deploy/kubernetes/firefly.yaml +++ b/.deploy/kubernetes/firefly.yaml @@ -44,17 +44,17 @@ spec: - image: firefly-local name: firefly-local env: - - name: FF_APP_ENV + - name: APP_ENV value: "local" - - name: FF_APP_KEY + - name: APP_KEY value: "S0m3R@nd0mString0f32Ch@rsEx@ct1y" - - name: FF_DB_HOST + - name: DB_HOST value: "172.17.0.9" - - name: FF_DB_NAME + - name: DB_NAME value: "firefly_db" - - name: FF_DB_USER + - name: DB_USER value: "firefly_db" - - name: FF_DB_PASSWORD + - name: DB_PASSWORD value: "password" volumeMounts: - mountPath: "/var/www/firefly-iii/storage/export" diff --git a/.env.example b/.env.example index a5899ff9d7..ad1ca04795 100644 --- a/.env.example +++ b/.env.example @@ -8,8 +8,8 @@ APP_DEBUG=false # This should be your email address SITE_OWNER=mail@example.com -# The encryption key for your database and sessions. Keep this very secure. -# If you generate a new one all existing data must be considered LOST. +# The encryption key for your sessions. Keep this very secure. +# If you generate a new one existing data must be considered LOST. # Change it to a string of exactly 32 chars or use command `php artisan key:generate` to generate it APP_KEY=SomeRandomStringOf32CharsExactly @@ -22,6 +22,7 @@ TZ=Europe/Amsterdam APP_URL=http://localhost # TRUSTED_PROXIES is a useful variable when using Docker and/or a reverse proxy. +# Set it to ** and reverse proxies work just fine. TRUSTED_PROXIES= # The log channel defines where your log entries go to. @@ -101,10 +102,11 @@ SEND_REPORT_JOURNALS=true MAPBOX_API_KEY= # Firefly III currently supports two provider for live Currency Exchange Rates: -# "fixer" is the default (for backward compatibility), and "ratesapi" is the new one. +# "fixer", and "ratesapi". # RatesApi.IO (see https://ratesapi.io) is a FREE and OPEN SOURCE live currency exchange rates, -# built compatible with Fixer.IO, based on data published by European Central Bank, and don't require API key. -CER_PROVIDER=fixer +# built compatible with Fixer.IO, based on data published by European Central Bank, and doesn't require API key. +CER_PROVIDER=ratesapi + # If you have select "fixer" as default currency exchange rates, # set a Fixer IO API key here (see https://fixer.io) to enable live currency exchange rates. # Please note that this WILL ONLY WORK FOR PAID fixer.io accounts because they severely limited @@ -114,10 +116,6 @@ FIXER_API_KEY= # If you wish to track your own behavior over Firefly III, set a valid analytics tracker ID here. ANALYTICS_ID= -# Most parts of the database are encrypted by default, but you can turn this off if you want to. -# This makes it easier to migrate your database. Not that some fields will never be decrypted. -USE_ENCRYPTION=true - # Firefly III has two options for user authentication. "eloquent" is the default, # and "ldap" for LDAP servers. # For full instructions on these settings please visit: @@ -179,6 +177,7 @@ PUSHER_ID= DEMO_USERNAME= DEMO_PASSWORD= IS_DOCKER=false +USE_ENCRYPTION=false IS_SANDSTORM=false IS_HEROKU=false BUNQ_USE_SANDBOX=false diff --git a/.github/funding.yml b/.github/funding.yml new file mode 100644 index 0000000000..5f3fe3e3af --- /dev/null +++ b/.github/funding.yml @@ -0,0 +1,5 @@ +# These are supported funding model platforms + +#github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +patreon: JC5 +custom: https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=44UKUT455HUFA diff --git a/.github/ranger.yml b/.github/ranger.yml new file mode 100644 index 0000000000..8e2aa09c6e --- /dev/null +++ b/.github/ranger.yml @@ -0,0 +1,6 @@ +# in .github/ranger.yml +comments: + - action: delete_comment + pattern: +1 + - action: delete_comment + pattern: ":+1:" \ No newline at end of file diff --git a/.github/stale.yml b/.github/stale.yml index 01b7d8a372..b8870bbf00 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -14,6 +14,7 @@ exemptLabels: - feature - bug - possible-bug + - "possible bug" - announcement # Set to true to ignore issues in a project (defaults to false) diff --git a/.sandstorm/changelog.md b/.sandstorm/changelog.md index 3e742969dd..5afdecf542 100644 --- a/.sandstorm/changelog.md +++ b/.sandstorm/changelog.md @@ -1,3 +1,43 @@ +# 4.8.0 (API 0.10.0) + +- Hungarian translation! +- New database model that changes the concept of "split transactions"; +- New installation routine with rewritten database integrity tests and upgrade code; +- Rewritten screen to create transactions which will now completely rely on the API; +- Most terminal commands now have the prefix `firefly-iii`. +- New MFA code that will generate backup codes for you and is more robust. MFA will have to be re-enabled for ALL users. +- This will probably be the last Firefly III version to have import routines for files, Bunq and others. These will be moved to separate applications that use the Firefly III API. +- The export function has been removed. +- [Issue 1652](https://github.com/firefly-iii/firefly-iii/issues/1652), new strings to use during the import. +- [Issue 1860](https://github.com/firefly-iii/firefly-iii/issues/1860), fixing the default currency not being on top in a JSON box. +- [Issue 2031](https://github.com/firefly-iii/firefly-iii/issues/2031), a fix for Triodos imports. +- [Issue 2153](https://github.com/firefly-iii/firefly-iii/issues/2153), problems with editing credit cards. +- [Issue 2179](https://github.com/firefly-iii/firefly-iii/issues/2179), consistent and correct redirect behavior. +- [Issue 2180](https://github.com/firefly-iii/firefly-iii/issues/2180), API issues with foreign amounts. +- [Issue 2187](https://github.com/firefly-iii/firefly-iii/issues/2187), bulk editing reconciled transactions was broken. +- [Issue 2188](https://github.com/firefly-iii/firefly-iii/issues/2188), redirect loop in bills +- [Issue 2189](https://github.com/firefly-iii/firefly-iii/issues/2189), bulk edit could not handle tags. +- [Issue 2203](https://github.com/firefly-iii/firefly-iii/issues/2203), [issue 2208](https://github.com/firefly-iii/firefly-iii/issues/2208), [issue 2352](https://github.com/firefly-iii/firefly-iii/issues/2352), reconciliation fixes +- [Issue 2204](https://github.com/firefly-iii/firefly-iii/issues/2204), transaction type fix +- [Issue 2211](https://github.com/firefly-iii/firefly-iii/issues/2211), mass edit fixes. +- [Issue 2212](https://github.com/firefly-iii/firefly-iii/issues/2212), bug in the API when deleting objects. +- [Issue 2214](https://github.com/firefly-iii/firefly-iii/issues/2214), could not view attachment. +- [Issue 2219](https://github.com/firefly-iii/firefly-iii/issues/2219), max amount was a little low. +- [Issue 2239](https://github.com/firefly-iii/firefly-iii/issues/2239), fixed ordering issue. +- [Issue 2246](https://github.com/firefly-iii/firefly-iii/issues/2246), could not disable EUR. +- [Issue 2268](https://github.com/firefly-iii/firefly-iii/issues/2268), could not import into liability accounts. +- [Issue 2293](https://github.com/firefly-iii/firefly-iii/issues/2293), could not trigger rule on deposits in some circumstances +- [Issue 2314](https://github.com/firefly-iii/firefly-iii/issues/2314), could not trigger rule on transfers in some circumstances +- [Issue 2325](https://github.com/firefly-iii/firefly-iii/issues/2325), some balance issues on the frontpage. +- [Issue 2328](https://github.com/firefly-iii/firefly-iii/issues/2328), some date range issues in reports +- [Issue 2331](https://github.com/firefly-iii/firefly-iii/issues/2331), some broken fields in reports. +- [Issue 2333](https://github.com/firefly-iii/firefly-iii/issues/2333), API issues with piggy banks. +- [Issue 2355](https://github.com/firefly-iii/firefly-iii/issues/2355), configuration issues with LDAP +- [Issue 2361](https://github.com/firefly-iii/firefly-iii/issues/2361), some ordering issues. +- Updated API to reflect the changes in the database. +- New API end-point for a summary of your data. +- Some new API charts. + # 4.7.17.6 (API 0.9.2) - XSS issue in liability account redirect, found by [@0x2500](https://github.com/0x2500). diff --git a/.sandstorm/launcher.sh b/.sandstorm/launcher.sh index d203eb5c92..9395e9f7e7 100755 --- a/.sandstorm/launcher.sh +++ b/.sandstorm/launcher.sh @@ -11,7 +11,7 @@ mkdir -p /var/log mkdir -p /var/log/mysql mkdir -p /var/log/nginx # Wipe /var/run, since pidfiles and socket files from previous launches should go away -# TODO someday: I'd prefer a tmpfs for these. +# Someday: I'd prefer a tmpfs for these. rm -rf /var/run mkdir -p /var/run rm -rf /var/tmp diff --git a/.sandstorm/setup.sh b/.sandstorm/setup.sh index 89e48c2294..60ee904fe4 100755 --- a/.sandstorm/setup.sh +++ b/.sandstorm/setup.sh @@ -25,13 +25,13 @@ sed -i 's/# ru_RU.UTF-8 UTF-8/ru_RU.UTF-8 UTF-8/g' /etc/locale.gen sed -i 's/# zh_TW.UTF-8 UTF-8/zh_TW.UTF-8 UTF-8/g' /etc/locale.gen sed -i 's/# zh_CN.UTF-8 UTF-8/zh_CN.UTF-8 UTF-8/g' /etc/locale.gen sed -i 's/# nb_NO.UTF-8 UTF-8/nb_NO.UTF-8 UTF-8/g' /etc/locale.gen - +sed -i 's/# ro_RO.UTF-8 UTF-8/ro_RO.UTF-8 UTF-8/g' /etc/locale.gen +sed -i 's/# cs_CZ.UTF-8 UTF-8/cs_CZ.UTF-8 UTF-8/g' /etc/locale.gen +sed -i 's/# id_ID.UTF-8 UTF-8/id_ID.UTF-8 UTF-8/g' /etc/locale.gen +sed -i 's/# hu_HU.UTF-8 UTF-8/hu_HU.UTF-8 UTF-8/g' /etc/locale.gen dpkg-reconfigure --frontend=noninteractive locales - - - # actually add repository apt-key adv --keyserver keyserver.ubuntu.com --recv-keys E9C74FEEA2098A6E add-apt-repository "deb http://packages.dotdeb.org jessie all" diff --git a/.travis.yml b/.travis.yml index e7c74e3738..49e596ae6a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ sudo: required language: bash env: - - VERSION=4.7.17.6 + - VERSION=4.8.0 dist: xenial diff --git a/Dockerfile b/Dockerfile index 762ad87f5d..590e644763 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,12 +1,11 @@ FROM php:7.2-apache - ENV FIREFLY_PATH=/var/www/firefly-iii COMPOSER_ALLOW_SUPERUSER=1 LABEL version="1.4" maintainer="thegrumpydictator@gmail.com" # Create volumes VOLUME $FIREFLY_PATH/storage/export $FIREFLY_PATH/storage/upload -# Install some stuff +# Install stuff Firefly III runs with & depends on: php extensions, locales, dev headers and composer RUN apt-get update && apt-get install -y libpng-dev \ libicu-dev \ unzip \ @@ -18,10 +17,29 @@ RUN apt-get update && apt-get install -y libpng-dev \ apt-get clean && \ rm -rf /var/lib/apt/lists/* +RUN docker-php-ext-configure ldap --with-libdir=lib/$(gcc -dumpmachine)/ && \ + docker-php-ext-install -j$(nproc) zip bcmath ldap gd pdo_pgsql pdo_mysql intl opcache && \ + pecl install memcached-3.1.3 && \ + docker-php-ext-enable memcached && \ + a2enmod rewrite && a2enmod ssl && \ + echo "hu_HU.UTF-8 UTF-8\nro_RO.UTF-8 UTF-8\nnb_NO.UTF-8 UTF-8\nde_DE.UTF-8 UTF-8\ncs_CZ.UTF-8 UTF-8\nen_US.UTF-8 UTF-8\nes_ES.UTF-8 UTF-8\nfr_FR.UTF-8 UTF-8\nid_ID.UTF-8 UTF-8\nit_IT.UTF-8 UTF-8\nnl_NL.UTF-8 UTF-8\npl_PL.UTF-8 UTF-8\npt_BR.UTF-8 UTF-8\nru_RU.UTF-8 UTF-8\ntr_TR.UTF-8 UTF-8\nzh_TW.UTF-8 UTF-8\nzh_CN.UTF-8 UTF-8\n\n" > /etc/locale.gen && \ + locale-gen && \ + curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer + +# configure PHP +RUN cp /usr/local/etc/php/php.ini-production /usr/local/etc/php/php.ini && \ + sed -i 's/max_execution_time = 30/max_execution_time = 600/' /usr/local/etc/php/php.ini && \ + sed -i 's/memory_limit = 128M/memory_limit = 512M/' /usr/local/etc/php/php.ini + # Copy in Firefly III source WORKDIR $FIREFLY_PATH ADD . $FIREFLY_PATH +# Ensure correct app directory permission, then `composer install` +RUN chown -R www-data:www-data /var/www && \ + chmod -R 775 $FIREFLY_PATH/storage && \ + composer install --prefer-dist --no-dev --no-scripts --no-suggest + # copy ca certs to correct location COPY ./.deploy/docker/cacert.pem /usr/local/ssl/cert.pem @@ -31,24 +49,6 @@ COPY ./.deploy/docker/apache2.conf /etc/apache2/apache2.conf # Enable default site (Firefly III) COPY ./.deploy/docker/apache-firefly.conf /etc/apache2/sites-available/000-default.conf -# Run a lot of installation commands: -RUN chown -R www-data:www-data /var/www && \ - chmod -R 775 $FIREFLY_PATH/storage && \ - a2enmod rewrite && a2enmod ssl && \ - docker-php-ext-configure ldap --with-libdir=lib/$(gcc -dumpmachine)/ && \ - docker-php-ext-install -j$(nproc) zip bcmath ldap gd pdo_pgsql pdo_mysql intl opcache && \ - pecl install memcached-3.1.3 && \ - docker-php-ext-enable memcached && \ - curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer && \ - echo "de_DE.UTF-8 UTF-8\nen_US.UTF-8 UTF-8\nes_ES.UTF-8 UTF-8\nfr_FR.UTF-8 UTF-8\nid_ID.UTF-8 UTF-8\nit_IT.UTF-8 UTF-8\nnl_NL.UTF-8 UTF-8\npl_PL.UTF-8 UTF-8\npt_BR.UTF-8 UTF-8\nru_RU.UTF-8 UTF-8\ntr_TR.UTF-8 UTF-8\nzh_TW.UTF-8 UTF-8\nzh_CN.UTF-8 UTF-8\n\n" > /etc/locale.gen && \ - locale-gen && \ - composer install --prefer-dist --no-dev --no-scripts --no-suggest - -# configure PHP -RUN cp /usr/local/etc/php/php.ini-production /usr/local/etc/php/php.ini && \ - sed -i 's/max_execution_time = 30/max_execution_time = 600/' /usr/local/etc/php/php.ini && \ - sed -i 's/memory_limit = 128M/memory_limit = 512M/' /usr/local/etc/php/php.ini - # Expose port 80 EXPOSE 80 diff --git a/Dockerfile.amd64 b/Dockerfile.amd64 index 80335d26ad..e6f6e58dca 100644 --- a/Dockerfile.amd64 +++ b/Dockerfile.amd64 @@ -6,7 +6,7 @@ LABEL version="1.4" maintainer="thegrumpydictator@gmail.com" # Create volumes VOLUME $FIREFLY_PATH/storage/export $FIREFLY_PATH/storage/upload -# Install some stuff +# Install stuff Firefly III runs with & depends on: php extensions, locales, dev headers and composer RUN apt-get update && apt-get install -y libpng-dev \ libicu-dev \ unzip \ @@ -18,10 +18,29 @@ RUN apt-get update && apt-get install -y libpng-dev \ apt-get clean && \ rm -rf /var/lib/apt/lists/* +RUN docker-php-ext-configure ldap --with-libdir=lib/$(gcc -dumpmachine)/ && \ + docker-php-ext-install -j$(nproc) zip bcmath ldap gd pdo_pgsql pdo_mysql intl opcache && \ + pecl install memcached-3.1.3 && \ + docker-php-ext-enable memcached && \ + a2enmod rewrite && a2enmod ssl && \ + echo "hu_HU.UTF-8 UTF-8\nro_RO.UTF-8 UTF-8\nnb_NO.UTF-8 UTF-8\nde_DE.UTF-8 UTF-8\ncs_CZ.UTF-8 UTF-8\nen_US.UTF-8 UTF-8\nes_ES.UTF-8 UTF-8\nfr_FR.UTF-8 UTF-8\nid_ID.UTF-8 UTF-8\nit_IT.UTF-8 UTF-8\nnl_NL.UTF-8 UTF-8\npl_PL.UTF-8 UTF-8\npt_BR.UTF-8 UTF-8\nru_RU.UTF-8 UTF-8\ntr_TR.UTF-8 UTF-8\nzh_TW.UTF-8 UTF-8\nzh_CN.UTF-8 UTF-8\n\n" > /etc/locale.gen && \ + locale-gen && \ + curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer + +# configure PHP +RUN cp /usr/local/etc/php/php.ini-production /usr/local/etc/php/php.ini && \ + sed -i 's/max_execution_time = 30/max_execution_time = 600/' /usr/local/etc/php/php.ini && \ + sed -i 's/memory_limit = 128M/memory_limit = 512M/' /usr/local/etc/php/php.ini + # Copy in Firefly III source WORKDIR $FIREFLY_PATH ADD . $FIREFLY_PATH +# Ensure correct app directory permission, then `composer install` +RUN chown -R www-data:www-data /var/www && \ + chmod -R 775 $FIREFLY_PATH/storage && \ + composer install --prefer-dist --no-dev --no-scripts --no-suggest + # copy ca certs to correct location COPY ./.deploy/docker/cacert.pem /usr/local/ssl/cert.pem @@ -31,25 +50,6 @@ COPY ./.deploy/docker/apache2.conf /etc/apache2/apache2.conf # Enable default site (Firefly III) COPY ./.deploy/docker/apache-firefly.conf /etc/apache2/sites-available/000-default.conf -# Run a lot of installation commands: -RUN chown -R www-data:www-data /var/www && \ - chmod -R 775 $FIREFLY_PATH/storage && \ - a2enmod rewrite && a2enmod ssl && \ - docker-php-ext-configure ldap --with-libdir=lib/$(gcc -dumpmachine)/ && \ - docker-php-ext-install -j$(nproc) zip bcmath ldap gd pdo_pgsql pdo_mysql intl opcache && \ - pecl install memcached-3.1.3 && \ - docker-php-ext-enable memcached && \ - curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer && \ - echo "de_DE.UTF-8 UTF-8\nen_US.UTF-8 UTF-8\nes_ES.UTF-8 UTF-8\nfr_FR.UTF-8 UTF-8\nid_ID.UTF-8 UTF-8\nit_IT.UTF-8 UTF-8\nnl_NL.UTF-8 UTF-8\npl_PL.UTF-8 UTF-8\npt_BR.UTF-8 UTF-8\nru_RU.UTF-8 UTF-8\ntr_TR.UTF-8 UTF-8\nzh_TW.UTF-8 UTF-8\nzh_CN.UTF-8 UTF-8\n\n" > /etc/locale.gen && \ - locale-gen && \ - composer install --prefer-dist --no-dev --no-scripts --no-suggest - -# configure PHP -RUN cp /usr/local/etc/php/php.ini-production /usr/local/etc/php/php.ini && \ - sed -i 's/max_execution_time = 30/max_execution_time = 600/' /usr/local/etc/php/php.ini && \ - sed -i 's/memory_limit = 128M/memory_limit = 512M/' /usr/local/etc/php/php.ini - - # Expose port 80 EXPOSE 80 diff --git a/Dockerfile.arm b/Dockerfile.arm index 536ef42cdc..a389aad123 100644 --- a/Dockerfile.arm +++ b/Dockerfile.arm @@ -7,7 +7,7 @@ LABEL version="1.4" maintainer="thegrumpydictator@gmail.com" # Create volumes VOLUME $FIREFLY_PATH/storage/export $FIREFLY_PATH/storage/upload -# Install some stuff +# Install stuff Firefly III runs with & depends on: php extensions, locales, dev headers and composer RUN apt-get update && apt-get install -y libpng-dev \ libicu-dev \ unzip \ @@ -15,12 +15,33 @@ RUN apt-get update && apt-get install -y libpng-dev \ libldap2-dev \ libpq-dev \ locales \ - libmemcached-dev + libmemcached-dev && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +RUN docker-php-ext-configure ldap --with-libdir=lib/$(gcc -dumpmachine)/ && \ + docker-php-ext-install -j$(nproc) zip bcmath ldap gd pdo_pgsql pdo_mysql intl opcache && \ + pecl install memcached-3.1.3 && \ + docker-php-ext-enable memcached && \ + a2enmod rewrite && a2enmod ssl && \ + echo "hu_HU.UTF-8 UTF-8\nro_RO.UTF-8 UTF-8\nnb_NO.UTF-8 UTF-8\nde_DE.UTF-8 UTF-8\ncs_CZ.UTF-8 UTF-8\nen_US.UTF-8 UTF-8\nes_ES.UTF-8 UTF-8\nfr_FR.UTF-8 UTF-8\nid_ID.UTF-8 UTF-8\nit_IT.UTF-8 UTF-8\nnl_NL.UTF-8 UTF-8\npl_PL.UTF-8 UTF-8\npt_BR.UTF-8 UTF-8\nru_RU.UTF-8 UTF-8\ntr_TR.UTF-8 UTF-8\nzh_TW.UTF-8 UTF-8\nzh_CN.UTF-8 UTF-8\n\n" > /etc/locale.gen && \ + locale-gen && \ + curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer + +# configure PHP +RUN cp /usr/local/etc/php/php.ini-production /usr/local/etc/php/php.ini && \ + sed -i 's/max_execution_time = 30/max_execution_time = 600/' /usr/local/etc/php/php.ini && \ + sed -i 's/memory_limit = 128M/memory_limit = 512M/' /usr/local/etc/php/php.ini # Copy in Firefly III source WORKDIR $FIREFLY_PATH ADD . $FIREFLY_PATH +# Ensure correct app directory permission, then `composer install` +RUN chown -R www-data:www-data /var/www && \ + chmod -R 775 $FIREFLY_PATH/storage && \ + composer install --prefer-dist --no-dev --no-scripts --no-suggest + # copy ca certs to correct location COPY ./.deploy/docker/cacert.pem /usr/local/ssl/cert.pem @@ -30,19 +51,6 @@ COPY ./.deploy/docker/apache2.conf /etc/apache2/apache2.conf # Enable default site (Firefly III) COPY ./.deploy/docker/apache-firefly.conf /etc/apache2/sites-available/000-default.conf -# Run a lot of installation commands: -RUN chown -R www-data:www-data /var/www && \ - chmod -R 775 $FIREFLY_PATH/storage && \ - a2enmod rewrite && a2enmod ssl && \ - docker-php-ext-configure ldap --with-libdir=lib/$(gcc -dumpmachine)/ && \ - docker-php-ext-install -j$(nproc) zip bcmath ldap gd pdo_pgsql pdo_mysql intl opcache && \ - pecl install memcached-3.1.3 && \ - docker-php-ext-enable memcached && \ - curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer && \ - echo "de_DE.UTF-8 UTF-8\nen_US.UTF-8 UTF-8\nes_ES.UTF-8 UTF-8\nfr_FR.UTF-8 UTF-8\nid_ID.UTF-8 UTF-8\nit_IT.UTF-8 UTF-8\nnl_NL.UTF-8 UTF-8\npl_PL.UTF-8 UTF-8\npt_BR.UTF-8 UTF-8\nru_RU.UTF-8 UTF-8\ntr_TR.UTF-8 UTF-8\nzh_TW.UTF-8 UTF-8\nzh_CN.UTF-8 UTF-8\n\n" > /etc/locale.gen && \ - locale-gen && \ - composer install --prefer-dist --no-dev --no-scripts --no-suggest - # Expose port 80 EXPOSE 80 diff --git a/Dockerfile.arm64 b/Dockerfile.arm64 index 536ef42cdc..a389aad123 100644 --- a/Dockerfile.arm64 +++ b/Dockerfile.arm64 @@ -7,7 +7,7 @@ LABEL version="1.4" maintainer="thegrumpydictator@gmail.com" # Create volumes VOLUME $FIREFLY_PATH/storage/export $FIREFLY_PATH/storage/upload -# Install some stuff +# Install stuff Firefly III runs with & depends on: php extensions, locales, dev headers and composer RUN apt-get update && apt-get install -y libpng-dev \ libicu-dev \ unzip \ @@ -15,12 +15,33 @@ RUN apt-get update && apt-get install -y libpng-dev \ libldap2-dev \ libpq-dev \ locales \ - libmemcached-dev + libmemcached-dev && \ + apt-get clean && \ + rm -rf /var/lib/apt/lists/* + +RUN docker-php-ext-configure ldap --with-libdir=lib/$(gcc -dumpmachine)/ && \ + docker-php-ext-install -j$(nproc) zip bcmath ldap gd pdo_pgsql pdo_mysql intl opcache && \ + pecl install memcached-3.1.3 && \ + docker-php-ext-enable memcached && \ + a2enmod rewrite && a2enmod ssl && \ + echo "hu_HU.UTF-8 UTF-8\nro_RO.UTF-8 UTF-8\nnb_NO.UTF-8 UTF-8\nde_DE.UTF-8 UTF-8\ncs_CZ.UTF-8 UTF-8\nen_US.UTF-8 UTF-8\nes_ES.UTF-8 UTF-8\nfr_FR.UTF-8 UTF-8\nid_ID.UTF-8 UTF-8\nit_IT.UTF-8 UTF-8\nnl_NL.UTF-8 UTF-8\npl_PL.UTF-8 UTF-8\npt_BR.UTF-8 UTF-8\nru_RU.UTF-8 UTF-8\ntr_TR.UTF-8 UTF-8\nzh_TW.UTF-8 UTF-8\nzh_CN.UTF-8 UTF-8\n\n" > /etc/locale.gen && \ + locale-gen && \ + curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer + +# configure PHP +RUN cp /usr/local/etc/php/php.ini-production /usr/local/etc/php/php.ini && \ + sed -i 's/max_execution_time = 30/max_execution_time = 600/' /usr/local/etc/php/php.ini && \ + sed -i 's/memory_limit = 128M/memory_limit = 512M/' /usr/local/etc/php/php.ini # Copy in Firefly III source WORKDIR $FIREFLY_PATH ADD . $FIREFLY_PATH +# Ensure correct app directory permission, then `composer install` +RUN chown -R www-data:www-data /var/www && \ + chmod -R 775 $FIREFLY_PATH/storage && \ + composer install --prefer-dist --no-dev --no-scripts --no-suggest + # copy ca certs to correct location COPY ./.deploy/docker/cacert.pem /usr/local/ssl/cert.pem @@ -30,19 +51,6 @@ COPY ./.deploy/docker/apache2.conf /etc/apache2/apache2.conf # Enable default site (Firefly III) COPY ./.deploy/docker/apache-firefly.conf /etc/apache2/sites-available/000-default.conf -# Run a lot of installation commands: -RUN chown -R www-data:www-data /var/www && \ - chmod -R 775 $FIREFLY_PATH/storage && \ - a2enmod rewrite && a2enmod ssl && \ - docker-php-ext-configure ldap --with-libdir=lib/$(gcc -dumpmachine)/ && \ - docker-php-ext-install -j$(nproc) zip bcmath ldap gd pdo_pgsql pdo_mysql intl opcache && \ - pecl install memcached-3.1.3 && \ - docker-php-ext-enable memcached && \ - curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer && \ - echo "de_DE.UTF-8 UTF-8\nen_US.UTF-8 UTF-8\nes_ES.UTF-8 UTF-8\nfr_FR.UTF-8 UTF-8\nid_ID.UTF-8 UTF-8\nit_IT.UTF-8 UTF-8\nnl_NL.UTF-8 UTF-8\npl_PL.UTF-8 UTF-8\npt_BR.UTF-8 UTF-8\nru_RU.UTF-8 UTF-8\ntr_TR.UTF-8 UTF-8\nzh_TW.UTF-8 UTF-8\nzh_CN.UTF-8 UTF-8\n\n" > /etc/locale.gen && \ - locale-gen && \ - composer install --prefer-dist --no-dev --no-scripts --no-suggest - # Expose port 80 EXPOSE 80 diff --git a/app/Api/V1/Controllers/AboutController.php b/app/Api/V1/Controllers/AboutController.php index ed4ec478aa..447e187369 100644 --- a/app/Api/V1/Controllers/AboutController.php +++ b/app/Api/V1/Controllers/AboutController.php @@ -34,7 +34,7 @@ use League\Fractal\Serializer\JsonApiSerializer; /** * Returns basic information about this installation. - * + * @codeCoverageIgnore * Class AboutController. */ class AboutController extends Controller @@ -51,8 +51,10 @@ class AboutController extends Controller $phpVersion = str_replace($search, $replace, PHP_VERSION); $phpOs = str_replace($search, $replace, PHP_OS); $currentDriver = DB::getDriverName(); + + $data - = [ + = [ 'version' => config('firefly.version'), 'api_version' => config('firefly.api_version'), 'php_version' => $phpVersion, @@ -67,7 +69,6 @@ class AboutController extends Controller * Returns information about the user. * * @param Request $request - * * @return JsonResponse */ public function user(Request $request): JsonResponse diff --git a/app/Api/V1/Controllers/AccountController.php b/app/Api/V1/Controllers/AccountController.php index 6dc976cc35..97090d71ba 100644 --- a/app/Api/V1/Controllers/AccountController.php +++ b/app/Api/V1/Controllers/AccountController.php @@ -23,17 +23,16 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Controllers; -use FireflyIII\Api\V1\Requests\AccountRequest; -use FireflyIII\Helpers\Collector\TransactionCollectorInterface; -use FireflyIII\Helpers\Filter\InternalTransferFilter; +use FireflyIII\Api\V1\Requests\AccountStoreRequest; +use FireflyIII\Api\V1\Requests\AccountUpdateRequest; +use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Models\Account; -use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Support\Http\Api\AccountFilter; use FireflyIII\Support\Http\Api\TransactionFilter; use FireflyIII\Transformers\AccountTransformer; use FireflyIII\Transformers\PiggyBankTransformer; -use FireflyIII\Transformers\TransactionTransformer; +use FireflyIII\Transformers\TransactionGroupTransformer; use FireflyIII\User; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; @@ -58,6 +57,8 @@ class AccountController extends Controller /** * AccountController constructor. + * + * @codeCoverageIgnore */ public function __construct() { @@ -78,8 +79,9 @@ class AccountController extends Controller /** * Remove the specified resource from storage. * - * @param \FireflyIII\Models\Account $account + * @param Account $account * + * @codeCoverageIgnore * @return JsonResponse */ public function delete(Account $account): JsonResponse @@ -94,6 +96,7 @@ class AccountController extends Controller * * @param Request $request * + * @codeCoverageIgnore * @return JsonResponse */ public function index(Request $request): JsonResponse @@ -134,12 +137,14 @@ class AccountController extends Controller /** - * List all of them. + * List all piggies. * * @param Request $request * @param Account $account * - * @return JsonResponse] + * @codeCoverageIgnore + * + * @return JsonResponse */ public function piggyBanks(Request $request, Account $account): JsonResponse { @@ -179,7 +184,7 @@ class AccountController extends Controller * @param Request $request * @param Account $account * - * @return \Illuminate\Http\JsonResponse + * @return JsonResponse */ public function show(Request $request, Account $account): JsonResponse { @@ -198,13 +203,13 @@ class AccountController extends Controller /** * Store a new instance. * - * @param AccountRequest $request + * @param AccountStoreRequest $request * - * @return \Illuminate\Http\JsonResponse + * @return JsonResponse */ - public function store(AccountRequest $request): JsonResponse + public function store(AccountStoreRequest $request): JsonResponse { - $data = $request->getAll(); + $data = $request->getAllAccountData(); $account = $this->repository->store($data); $manager = new Manager; $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; @@ -220,12 +225,17 @@ class AccountController extends Controller } /** - * Show all transactions. + * Show all transaction groups related to the account. + * + * @codeCoverageIgnore * * @param Request $request * @param Account $account * * @return JsonResponse + * + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ public function transactions(Request $request, Account $account): JsonResponse { @@ -242,41 +252,31 @@ class AccountController extends Controller $types = $this->mapTransactionTypes($this->parameters->get('type')); $manager = new Manager(); $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; - - $manager->setSerializer(new JsonApiSerializer($baseUrl)); /** @var User $admin */ $admin = auth()->user(); - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setUser($admin); - $collector->withOpposingAccount()->withCategoryInformation()->withBudgetInformation(); - if ($this->repository->isAsset($account)) { - $collector->setAccounts(new Collection([$account])); - } - if (!$this->repository->isAsset($account)) { - $collector->setOpposingAccounts(new Collection([$account])); - } - if (\in_array(TransactionType::TRANSFER, $types, true)) { - $collector->removeFilter(InternalTransferFilter::class); - } + // use new group collector: + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setUser($admin)->setAccounts(new Collection([$account])) + ->withAPIInformation()->setLimit($pageSize)->setPage($this->parameters->get('page'))->setTypes($types); + // set range if necessary: if (null !== $this->parameters->get('start') && null !== $this->parameters->get('end')) { $collector->setRange($this->parameters->get('start'), $this->parameters->get('end')); } - $collector->setLimit($pageSize)->setPage($this->parameters->get('page')); - $collector->setTypes($types); - $paginator = $collector->getPaginatedTransactions(); - $paginator->setPath(route('api.v1.accounts.transactions', [$account->id]) . $this->buildParams()); - $transactions = $paginator->getCollection(); - /** @var TransactionTransformer $transformer */ - $transformer = app(TransactionTransformer::class); + $paginator = $collector->getPaginatedGroups(); + $paginator->setPath(route('api.v1.accounts.transactions', [$account->id]) . $this->buildParams()); + $groups = $paginator->getCollection(); + + /** @var TransactionGroupTransformer $transformer */ + $transformer = app(TransactionGroupTransformer::class); $transformer->setParameters($this->parameters); - $resource = new FractalCollection($transactions, $transformer, 'transactions'); + $resource = new FractalCollection($groups, $transformer, 'transactions'); $resource->setPaginator(new IlluminatePaginatorAdapter($paginator)); return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); @@ -285,14 +285,14 @@ class AccountController extends Controller /** * Update account. * - * @param AccountRequest $request - * @param Account $account + * @param AccountUpdateRequest $request + * @param Account $account * - * @return \Illuminate\Http\JsonResponse + * @return JsonResponse */ - public function update(AccountRequest $request, Account $account): JsonResponse + public function update(AccountUpdateRequest $request, Account $account): JsonResponse { - $data = $request->getAll(); + $data = $request->getAllAccountData(); $data['type'] = config('firefly.shortNamesByFullName.' . $account->accountType->type); $this->repository->update($account, $data); $manager = new Manager; diff --git a/app/Api/V1/Controllers/AttachmentController.php b/app/Api/V1/Controllers/AttachmentController.php index 14c6e59cda..7525029681 100644 --- a/app/Api/V1/Controllers/AttachmentController.php +++ b/app/Api/V1/Controllers/AttachmentController.php @@ -39,6 +39,7 @@ use League\Fractal\Pagination\IlluminatePaginatorAdapter; use League\Fractal\Resource\Collection as FractalCollection; use League\Fractal\Resource\Item; use League\Fractal\Serializer\JsonApiSerializer; +use function strlen; /** * Class AttachmentController. @@ -52,6 +53,7 @@ class AttachmentController extends Controller /** * AccountController constructor. + * @codeCoverageIgnore */ public function __construct() { @@ -70,7 +72,7 @@ class AttachmentController extends Controller /** * Remove the specified resource from storage. - * + * @codeCoverageIgnore * @param Attachment $attachment * * @return JsonResponse @@ -86,9 +88,9 @@ class AttachmentController extends Controller * Download an attachment. * * @param Attachment $attachment - * + * @codeCoverageIgnore * @return LaravelResponse - * @throws FireflyException + * @throws FireflyException */ public function download(Attachment $attachment): LaravelResponse { @@ -110,7 +112,7 @@ class AttachmentController extends Controller ->header('Expires', '0') ->header('Cache-Control', 'must-revalidate, post-check=0, pre-check=0') ->header('Pragma', 'public') - ->header('Content-Length', \strlen($content)); + ->header('Content-Length', strlen($content)); return $response; } @@ -121,7 +123,7 @@ class AttachmentController extends Controller * Display a listing of the resource. * * @param Request $request - * + * @codeCoverageIgnore * @return JsonResponse */ public function index(Request $request): JsonResponse @@ -158,9 +160,8 @@ class AttachmentController extends Controller /** * Display the specified resource. * - * @param Request $request + * @param Request $request * @param Attachment $attachment - * * @return JsonResponse */ public function show(Request $request, Attachment $attachment): JsonResponse @@ -207,7 +208,7 @@ class AttachmentController extends Controller * Update the specified resource in storage. * * @param AttachmentRequest $request - * @param Attachment $attachment + * @param Attachment $attachment * * @return JsonResponse */ @@ -230,8 +231,8 @@ class AttachmentController extends Controller /** * Upload an attachment. - * - * @param Request $request + * @codeCoverageIgnore + * @param Request $request * @param Attachment $attachment * * @return JsonResponse diff --git a/app/Api/V1/Controllers/AvailableBudgetController.php b/app/Api/V1/Controllers/AvailableBudgetController.php index 5008f39387..73cacafb97 100644 --- a/app/Api/V1/Controllers/AvailableBudgetController.php +++ b/app/Api/V1/Controllers/AvailableBudgetController.php @@ -50,7 +50,9 @@ class AvailableBudgetController extends Controller private $repository; /** - * AccountController constructor. + * AvailableBudgetController constructor. + * + * @codeCoverageIgnore */ public function __construct() { @@ -72,6 +74,8 @@ class AvailableBudgetController extends Controller * * @param AvailableBudget $availableBudget * + * @codeCoverageIgnore + * * @return JsonResponse */ public function delete(AvailableBudget $availableBudget): JsonResponse @@ -87,6 +91,7 @@ class AvailableBudgetController extends Controller * @param Request $request * * @return JsonResponse + * @codeCoverageIgnore */ public function index(Request $request): JsonResponse { @@ -97,21 +102,11 @@ class AvailableBudgetController extends Controller // types to get, page size: $pageSize = (int)app('preferences')->getForUser(auth()->user(), 'listPageSize', 50)->data; - // get list of available budgets. Count it and split it. - $collection = $this->repository->getAvailableBudgets(); - - // filter list on start and end date, if present. - // TODO: put this in the query. $start = $this->parameters->get('start'); $end = $this->parameters->get('end'); - if (null !== $start && null !== $end) { - $collection = $collection->filter( - function (AvailableBudget $availableBudget) use ($start, $end) { - return $availableBudget->start_date->gte($start) && $availableBudget->end_date->lte($end); - } - ); - } + // get list of available budgets. Count it and split it. + $collection = $this->repository->getAvailableBudgetsByDate($start, $end); $count = $collection->count(); $availableBudgets = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize); @@ -135,10 +130,11 @@ class AvailableBudgetController extends Controller /** * Display the specified resource. * - * @param Request $request - * @param AvailableBudget $availableBudget + * @param Request $request + * @param AvailableBudget $availableBudget * * @return JsonResponse + * @codeCoverageIgnore */ public function show(Request $request, AvailableBudget $availableBudget): JsonResponse { @@ -191,7 +187,7 @@ class AvailableBudgetController extends Controller * Update the specified resource in storage. * * @param AvailableBudgetRequest $request - * @param AvailableBudget $availableBudget + * @param AvailableBudget $availableBudget * * @return JsonResponse */ diff --git a/app/Api/V1/Controllers/BillController.php b/app/Api/V1/Controllers/BillController.php index 17e2840bd0..7378704588 100644 --- a/app/Api/V1/Controllers/BillController.php +++ b/app/Api/V1/Controllers/BillController.php @@ -26,14 +26,14 @@ namespace FireflyIII\Api\V1\Controllers; use FireflyIII\Api\V1\Requests\BillRequest; use FireflyIII\Exceptions\FireflyException; -use FireflyIII\Helpers\Collector\TransactionCollectorInterface; +use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Models\Bill; use FireflyIII\Repositories\Bill\BillRepositoryInterface; use FireflyIII\Support\Http\Api\TransactionFilter; use FireflyIII\Transformers\AttachmentTransformer; use FireflyIII\Transformers\BillTransformer; use FireflyIII\Transformers\RuleTransformer; -use FireflyIII\Transformers\TransactionTransformer; +use FireflyIII\Transformers\TransactionGroupTransformer; use FireflyIII\User; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; @@ -47,6 +47,8 @@ use League\Fractal\Serializer\JsonApiSerializer; /** * Class BillController. + * + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class BillController extends Controller { @@ -56,6 +58,8 @@ class BillController extends Controller /** * BillController constructor. + * + * @codeCoverageIgnore */ public function __construct() { @@ -78,9 +82,10 @@ class BillController extends Controller * Display a listing of the resource. * * @param Request $request - * @param Bill $bill + * @param Bill $bill * * @return JsonResponse + * @codeCoverageIgnore */ public function attachments(Request $request, Bill $bill): JsonResponse { @@ -114,9 +119,10 @@ class BillController extends Controller /** * Remove the specified resource from storage. * - * @param Bill $bill + * @param Bill $bill * * @return JsonResponse + * @codeCoverageIgnore */ public function delete(Bill $bill): JsonResponse { @@ -131,6 +137,7 @@ class BillController extends Controller * @param Request $request * * @return JsonResponse + * @codeCoverageIgnore */ public function index(Request $request): JsonResponse { @@ -157,9 +164,10 @@ class BillController extends Controller * List all of them. * * @param Request $request - * @param Bill $bill + * @param Bill $bill * * @return JsonResponse + * @codeCoverageIgnore */ public function rules(Request $request, Bill $bill): JsonResponse { @@ -198,9 +206,10 @@ class BillController extends Controller * Show the specified bill. * * @param Request $request - * @param Bill $bill + * @param Bill $bill * * @return JsonResponse + * @codeCoverageIgnore */ public function show(Request $request, Bill $bill): JsonResponse { @@ -250,9 +259,10 @@ class BillController extends Controller * * @param Request $request * - * @param Bill $bill + * @param Bill $bill * * @return JsonResponse + * @codeCoverageIgnore */ public function transactions(Request $request, Bill $bill): JsonResponse { @@ -267,24 +277,35 @@ class BillController extends Controller /** @var User $admin */ $admin = auth()->user(); - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setUser($admin); - $collector->withOpposingAccount()->withCategoryInformation()->withBudgetInformation(); - $collector->setAllAssetAccounts(); - $collector->setBills(new Collection([$bill])); + // use new group collector: + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector + ->setUser($admin) + // include source + destination account name and type. + ->setBill($bill) + // all info needed for the API: + ->withAPIInformation() + // set page size: + ->setLimit($pageSize) + // set page to retrieve + ->setPage($this->parameters->get('page')) + // set types of transactions to return. + ->setTypes($types); + + // do parameter stuff on new group collector. if (null !== $this->parameters->get('start') && null !== $this->parameters->get('end')) { $collector->setRange($this->parameters->get('start'), $this->parameters->get('end')); } - $collector->setLimit($pageSize)->setPage($this->parameters->get('page')); - $collector->setTypes($types); - $paginator = $collector->getPaginatedTransactions(); + + // get paginator. + $paginator = $collector->getPaginatedGroups(); $paginator->setPath(route('api.v1.bills.transactions', [$bill->id]) . $this->buildParams()); $transactions = $paginator->getCollection(); - /** @var TransactionTransformer $transformer */ - $transformer = app(TransactionTransformer::class); + /** @var TransactionGroupTransformer $transformer */ + $transformer = app(TransactionGroupTransformer::class); $transformer->setParameters($this->parameters); $resource = new FractalCollection($transactions, $transformer, 'transactions'); @@ -297,7 +318,7 @@ class BillController extends Controller * Update a bill. * * @param BillRequest $request - * @param Bill $bill + * @param Bill $bill * * @return JsonResponse */ diff --git a/app/Api/V1/Controllers/BudgetController.php b/app/Api/V1/Controllers/BudgetController.php index 4f14df5af7..b057675f6f 100644 --- a/app/Api/V1/Controllers/BudgetController.php +++ b/app/Api/V1/Controllers/BudgetController.php @@ -23,16 +23,17 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Controllers; +use Exception; use FireflyIII\Api\V1\Requests\BudgetLimitRequest; use FireflyIII\Api\V1\Requests\BudgetRequest; use FireflyIII\Exceptions\FireflyException; -use FireflyIII\Helpers\Collector\TransactionCollectorInterface; +use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Models\Budget; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Support\Http\Api\TransactionFilter; use FireflyIII\Transformers\BudgetLimitTransformer; use FireflyIII\Transformers\BudgetTransformer; -use FireflyIII\Transformers\TransactionTransformer; +use FireflyIII\Transformers\TransactionGroupTransformer; use FireflyIII\User; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; @@ -56,6 +57,8 @@ class BudgetController extends Controller /** * BudgetController constructor. + * + * @codeCoverageIgnore */ public function __construct() { @@ -77,10 +80,12 @@ class BudgetController extends Controller /** * Display a listing of the resource. * + * * @param Request $request - * @param Budget $budget + * @param Budget $budget * * @return JsonResponse + * @codeCoverageIgnore */ public function budgetLimits(Request $request, Budget $budget): JsonResponse { @@ -113,6 +118,7 @@ class BudgetController extends Controller * @param Budget $budget * * @return JsonResponse + * @codeCoverageIgnore */ public function delete(Budget $budget): JsonResponse { @@ -127,6 +133,7 @@ class BudgetController extends Controller * @param Request $request * * @return JsonResponse + * @codeCoverageIgnore */ public function index(Request $request): JsonResponse { @@ -163,9 +170,10 @@ class BudgetController extends Controller * Show a budget. * * @param Request $request - * @param Budget $budget + * @param Budget $budget * * @return JsonResponse + * @codeCoverageIgnore */ public function show(Request $request, Budget $budget): JsonResponse { @@ -189,6 +197,7 @@ class BudgetController extends Controller * * @return JsonResponse * @throws FireflyException + * */ public function store(BudgetRequest $request): JsonResponse { @@ -213,9 +222,9 @@ class BudgetController extends Controller * Store a newly created resource in storage. * * @param BudgetLimitRequest $request - * @param Budget $budget - * + * @param Budget $budget * @return JsonResponse + * @throws Exception */ public function storeBudgetLimit(BudgetLimitRequest $request, Budget $budget): JsonResponse { @@ -240,9 +249,10 @@ class BudgetController extends Controller * * @param Request $request * - * @param Budget $budget + * @param Budget $budget * * @return JsonResponse + * @codeCoverageIgnore */ public function transactions(Request $request, Budget $budget): JsonResponse { @@ -264,25 +274,33 @@ class BudgetController extends Controller /** @var User $admin */ $admin = auth()->user(); - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setUser($admin); - $collector->withOpposingAccount()->withCategoryInformation()->withBudgetInformation(); - $collector->setAllAssetAccounts(); - $collector->setBudget($budget); + + // use new group collector: + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector + ->setUser($admin) + // filter on budget. + ->setBudget($budget) + // all info needed for the API: + ->withAPIInformation() + // set page size: + ->setLimit($pageSize) + // set page to retrieve + ->setPage($this->parameters->get('page')) + // set types of transactions to return. + ->setTypes($types); if (null !== $this->parameters->get('start') && null !== $this->parameters->get('end')) { $collector->setRange($this->parameters->get('start'), $this->parameters->get('end')); } - $collector->setLimit($pageSize)->setPage($this->parameters->get('page')); - $collector->setTypes($types); - $paginator = $collector->getPaginatedTransactions(); + $paginator = $collector->getPaginatedGroups(); $paginator->setPath(route('api.v1.budgets.transactions', [$budget->id]) . $this->buildParams()); $transactions = $paginator->getCollection(); - /** @var TransactionTransformer $transformer */ - $transformer = app(TransactionTransformer::class); + /** @var TransactionGroupTransformer $transformer */ + $transformer = app(TransactionGroupTransformer::class); $transformer->setParameters($this->parameters); @@ -296,7 +314,7 @@ class BudgetController extends Controller * Update a budget. * * @param BudgetRequest $request - * @param Budget $budget + * @param Budget $budget * * @return JsonResponse */ diff --git a/app/Api/V1/Controllers/BudgetLimitController.php b/app/Api/V1/Controllers/BudgetLimitController.php index 3d71b885c3..587d0d305a 100644 --- a/app/Api/V1/Controllers/BudgetLimitController.php +++ b/app/Api/V1/Controllers/BudgetLimitController.php @@ -26,12 +26,12 @@ namespace FireflyIII\Api\V1\Controllers; use FireflyIII\Api\V1\Requests\BudgetLimitRequest; use FireflyIII\Exceptions\FireflyException; -use FireflyIII\Helpers\Collector\TransactionCollectorInterface; +use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Models\BudgetLimit; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Support\Http\Api\TransactionFilter; use FireflyIII\Transformers\BudgetLimitTransformer; -use FireflyIII\Transformers\TransactionTransformer; +use FireflyIII\Transformers\TransactionGroupTransformer; use FireflyIII\User; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; @@ -43,7 +43,6 @@ use League\Fractal\Resource\Collection as FractalCollection; use League\Fractal\Resource\Item; use League\Fractal\Serializer\JsonApiSerializer; - /** * Class BudgetLimitController. * @@ -56,7 +55,9 @@ class BudgetLimitController extends Controller private $repository; /** - * AccountController constructor. + * BudgetLimitController constructor. + * + * @codeCoverageIgnore */ public function __construct() { @@ -79,6 +80,7 @@ class BudgetLimitController extends Controller * @param BudgetLimit $budgetLimit * * @return JsonResponse + * @codeCoverageIgnore */ public function delete(BudgetLimit $budgetLimit): JsonResponse { @@ -93,6 +95,7 @@ class BudgetLimitController extends Controller * @param Request $request * * @return JsonResponse + * @codeCoverageIgnore */ public function index(Request $request): JsonResponse { @@ -131,10 +134,11 @@ class BudgetLimitController extends Controller /** * Display the specified resource. * - * @param Request $request + * @param Request $request * @param BudgetLimit $budgetLimit * * @return JsonResponse + * @codeCoverageIgnore */ public function show(Request $request, BudgetLimit $budgetLimit): JsonResponse { @@ -158,6 +162,7 @@ class BudgetLimitController extends Controller * * @return JsonResponse * @throws FireflyException + * */ public function store(BudgetLimitRequest $request): JsonResponse { @@ -184,10 +189,11 @@ class BudgetLimitController extends Controller /** * Show all transactions. * - * @param Request $request + * @param Request $request * @param BudgetLimit $budgetLimit * * @return JsonResponse + * @codeCoverageIgnore */ public function transactions(Request $request, BudgetLimit $budgetLimit): JsonResponse { @@ -202,21 +208,31 @@ class BudgetLimitController extends Controller /** @var User $admin */ $admin = auth()->user(); - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setUser($admin); - $collector->withOpposingAccount()->withCategoryInformation()->withBudgetInformation(); - $collector->setAllAssetAccounts(); - $collector->setBudget($budgetLimit->budget); + + // use new group collector: + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector + ->setUser($admin) + // filter on budget. + ->setBudget($budgetLimit->budget) + // all info needed for the API: + ->withAPIInformation() + // set page size: + ->setLimit($pageSize) + // set page to retrieve + ->setPage($this->parameters->get('page')) + // set types of transactions to return. + ->setTypes($types); + $collector->setRange($budgetLimit->start_date, $budgetLimit->end_date); - $collector->setLimit($pageSize)->setPage($this->parameters->get('page')); $collector->setTypes($types); - $paginator = $collector->getPaginatedTransactions(); + $paginator = $collector->getPaginatedGroups(); $paginator->setPath(route('api.v1.budget_limits.transactions', [$budgetLimit->id]) . $this->buildParams()); $transactions = $paginator->getCollection(); - /** @var TransactionTransformer $transformer */ - $transformer = app(TransactionTransformer::class); + /** @var TransactionGroupTransformer $transformer */ + $transformer = app(TransactionGroupTransformer::class); $transformer->setParameters($this->parameters); $resource = new FractalCollection($transactions, $transformer, 'transactions'); @@ -229,7 +245,7 @@ class BudgetLimitController extends Controller * Update the specified resource in storage. * * @param BudgetLimitRequest $request - * @param BudgetLimit $budgetLimit + * @param BudgetLimit $budgetLimit * * @return JsonResponse */ diff --git a/app/Api/V1/Controllers/CategoryController.php b/app/Api/V1/Controllers/CategoryController.php index a6da367a86..93f8ab75e2 100644 --- a/app/Api/V1/Controllers/CategoryController.php +++ b/app/Api/V1/Controllers/CategoryController.php @@ -25,14 +25,12 @@ namespace FireflyIII\Api\V1\Controllers; use FireflyIII\Api\V1\Requests\CategoryRequest; use FireflyIII\Exceptions\FireflyException; -use FireflyIII\Helpers\Collector\TransactionCollectorInterface; -use FireflyIII\Helpers\Filter\InternalTransferFilter; +use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Models\Category; -use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Category\CategoryRepositoryInterface; use FireflyIII\Support\Http\Api\TransactionFilter; use FireflyIII\Transformers\CategoryTransformer; -use FireflyIII\Transformers\TransactionTransformer; +use FireflyIII\Transformers\TransactionGroupTransformer; use FireflyIII\User; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; @@ -56,6 +54,8 @@ class CategoryController extends Controller /** * CategoryController constructor. + * + * @codeCoverageIgnore */ public function __construct() { @@ -80,6 +80,7 @@ class CategoryController extends Controller * @param Category $category * * @return JsonResponse + * @codeCoverageIgnore */ public function delete(Category $category): JsonResponse { @@ -94,6 +95,7 @@ class CategoryController extends Controller * @param Request $request * * @return JsonResponse + * @codeCoverageIgnore */ public function index(Request $request): JsonResponse { @@ -131,10 +133,11 @@ class CategoryController extends Controller /** * Show the category. * - * @param Request $request + * @param Request $request * @param Category $category * * @return JsonResponse + * @codeCoverageIgnore */ public function show(Request $request, Category $category): JsonResponse { @@ -181,11 +184,12 @@ class CategoryController extends Controller /** * Show all transactions. * - * @param Request $request + * @param Request $request * * @param Category $category * * @return JsonResponse + * @codeCoverageIgnore */ public function transactions(Request $request, Category $category): JsonResponse { @@ -200,28 +204,33 @@ class CategoryController extends Controller /** @var User $admin */ $admin = auth()->user(); - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setUser($admin); - $collector->withOpposingAccount()->withCategoryInformation()->withBudgetInformation(); - $collector->setAllAssetAccounts(); - $collector->setCategory($category); - if (\in_array(TransactionType::TRANSFER, $types, true)) { - $collector->removeFilter(InternalTransferFilter::class); - } + // use new group collector: + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector + ->setUser($admin) + // filter on category. + ->setCategory($category) + // all info needed for the API: + ->withAPIInformation() + // set page size: + ->setLimit($pageSize) + // set page to retrieve + ->setPage($this->parameters->get('page')) + // set types of transactions to return. + ->setTypes($types); if (null !== $this->parameters->get('start') && null !== $this->parameters->get('end')) { $collector->setRange($this->parameters->get('start'), $this->parameters->get('end')); } - $collector->setLimit($pageSize)->setPage($this->parameters->get('page')); - $collector->setTypes($types); - $paginator = $collector->getPaginatedTransactions(); + + $paginator = $collector->getPaginatedGroups(); $paginator->setPath(route('api.v1.categories.transactions', [$category->id]) . $this->buildParams()); $transactions = $paginator->getCollection(); - /** @var TransactionTransformer $transformer */ - $transformer = app(TransactionTransformer::class); + /** @var TransactionGroupTransformer $transformer */ + $transformer = app(TransactionGroupTransformer::class); $transformer->setParameters($this->parameters); $resource = new FractalCollection($transactions, $transformer, 'transactions'); @@ -234,7 +243,7 @@ class CategoryController extends Controller * Update the category. * * @param CategoryRequest $request - * @param Category $category + * @param Category $category * * @return JsonResponse */ diff --git a/app/Api/V1/Controllers/Chart/AccountController.php b/app/Api/V1/Controllers/Chart/AccountController.php index 40adb4502c..964e21c3ca 100644 --- a/app/Api/V1/Controllers/Chart/AccountController.php +++ b/app/Api/V1/Controllers/Chart/AccountController.php @@ -26,22 +26,22 @@ namespace FireflyIII\Api\V1\Controllers\Chart; use Carbon\Carbon; use FireflyIII\Api\V1\Controllers\Controller; -use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Api\V1\Requests\DateRequest; use FireflyIII\Models\Account; use FireflyIII\Models\AccountType; use FireflyIII\Models\TransactionCurrency; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; +use FireflyIII\Support\Http\Api\ApiSupport; use FireflyIII\User; use Illuminate\Http\JsonResponse; -use Illuminate\Http\Request; -use Illuminate\Support\Collection; /** * Class AccountController */ class AccountController extends Controller { + use ApiSupport; /** @var CurrencyRepositoryInterface */ private $currencyRepository; /** @var AccountRepositoryInterface */ @@ -49,6 +49,7 @@ class AccountController extends Controller /** * AccountController constructor. + * @codeCoverageIgnore */ public function __construct() { @@ -69,22 +70,19 @@ class AccountController extends Controller } /** - * @param Request $request + * @param DateRequest $request * * @return JsonResponse - * @throws FireflyException */ - public function expenseOverview(Request $request): JsonResponse + public function expenseOverview(DateRequest $request): JsonResponse { // parameters for chart: - $start = (string)$request->get('start'); - $end = (string)$request->get('end'); - if ('' === $start || '' === $end) { - throw new FireflyException('Start and end are mandatory parameters.'); - } + $dates = $request->getAll(); + /** @var Carbon $start */ + $start = $dates['start']; + /** @var Carbon $end */ + $end = $dates['end']; - $start = Carbon::createFromFormat('Y-m-d', $start); - $end = Carbon::createFromFormat('Y-m-d', $end); $start->subDay(); // prep some vars: @@ -128,7 +126,7 @@ class AccountController extends Controller // loop all found currencies and build the data array for the chart. /** - * @var int $currencyId + * @var int $currencyId * @var TransactionCurrency $currency */ foreach ($currencies as $currencyId => $currency) { @@ -156,32 +154,31 @@ class AccountController extends Controller return response()->json($chartData); } + /** - * @param Request $request + * @param DateRequest $request * * @return JsonResponse - * @throws FireflyException */ - public function overview(Request $request): JsonResponse + public function overview(DateRequest $request): JsonResponse { // parameters for chart: - $start = (string)$request->get('start'); - $end = (string)$request->get('end'); - if ('' === $start || '' === $end) { - throw new FireflyException('Start and end are mandatory parameters.'); - } - - $start = Carbon::createFromFormat('Y-m-d', $start); - $end = Carbon::createFromFormat('Y-m-d', $end); + $dates = $request->getAll(); + /** @var Carbon $start */ + $start = $dates['start']; + /** @var Carbon $end */ + $end = $dates['end']; // user's preferences - $defaultSet = $this->repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET])->pluck('id')->toArray(); + $defaultSet = $this->repository->getAccountsByType([AccountType::ASSET])->pluck('id')->toArray(); $frontPage = app('preferences')->get('frontPageAccounts', $defaultSet); $default = app('amount')->getDefaultCurrency(); - if (0 === \count($frontPage->data)) { + // @codeCoverageIgnoreStart + if (0 === count($frontPage->data)) { $frontPage->data = $defaultSet; $frontPage->save(); } + // @codeCoverageIgnoreEnd // get accounts: $accounts = $this->repository->getAccountsById($frontPage->data); @@ -190,7 +187,7 @@ class AccountController extends Controller foreach ($accounts as $account) { $currency = $this->repository->getAccountCurrency($account); if (null === $currency) { - $currency = $default; + $currency = $default; // @codeCoverageIgnore } $currentSet = [ 'label' => $account->name, @@ -202,7 +199,7 @@ class AccountController extends Controller 'yAxisID' => 0, // 0, 1, 2 'entries' => [], ]; - + /** @var Carbon $currentStart */ $currentStart = clone $start; $range = app('steam')->balanceInRange($account, $start, clone $end); $previous = round(array_values($range)[0], 12); @@ -221,22 +218,19 @@ class AccountController extends Controller } /** - * @param Request $request + * @param DateRequest $request * * @return JsonResponse - * @throws FireflyException */ - public function revenueOverview(Request $request): JsonResponse + public function revenueOverview(DateRequest $request): JsonResponse { // parameters for chart: - $start = (string)$request->get('start'); - $end = (string)$request->get('end'); - if ('' === $start || '' === $end) { - throw new FireflyException('Start and end are mandatory parameters.'); - } + $dates = $request->getAll(); + /** @var Carbon $start */ + $start = $dates['start']; + /** @var Carbon $end */ + $end = $dates['end']; - $start = Carbon::createFromFormat('Y-m-d', $start); - $end = Carbon::createFromFormat('Y-m-d', $end); $start->subDay(); // prep some vars: @@ -267,7 +261,8 @@ class AccountController extends Controller $tempData[] = [ 'name' => $accountNames[$accountId], 'difference' => bcmul($diff, '-1'), - 'diff_float' => (float)$diff * -1, + // For some reason this line is never covered in code coverage: + 'diff_float' => ((float)$diff) * -1, // @codeCoverageIgnore 'currency_id' => $currencyId, ]; } @@ -280,7 +275,7 @@ class AccountController extends Controller // loop all found currencies and build the data array for the chart. /** - * @var int $currencyId + * @var int $currencyId * @var TransactionCurrency $currency */ foreach ($currencies as $currencyId => $currency) { @@ -308,41 +303,4 @@ class AccountController extends Controller return response()->json($chartData); } - /** - * Small helper function for the revenue and expense account charts. - * TODO should include Trait instead of doing this. - * - * @param array $names - * - * @return array - */ - protected function expandNames(array $names): array - { - $result = []; - foreach ($names as $entry) { - $result[$entry['name']] = 0; - } - - return $result; - } - - /** - * Small helper function for the revenue and expense account charts. - * TODO should include Trait instead of doing this. - * - * @param Collection $accounts - * - * @return array - */ - protected function extractNames(Collection $accounts): array - { - $return = []; - /** @var Account $account */ - foreach ($accounts as $account) { - $return[$account->id] = $account->name; - } - - return $return; - } - } diff --git a/app/Api/V1/Controllers/Chart/AvailableBudgetController.php b/app/Api/V1/Controllers/Chart/AvailableBudgetController.php index c876d4b2df..7f36d977b8 100644 --- a/app/Api/V1/Controllers/Chart/AvailableBudgetController.php +++ b/app/Api/V1/Controllers/Chart/AvailableBudgetController.php @@ -42,6 +42,7 @@ class AvailableBudgetController extends Controller /** * AvailableBudgetController constructor. + * @codeCoverageIgnore */ public function __construct() { diff --git a/app/Api/V1/Controllers/Chart/CategoryController.php b/app/Api/V1/Controllers/Chart/CategoryController.php index 868297af1e..1314983f16 100644 --- a/app/Api/V1/Controllers/Chart/CategoryController.php +++ b/app/Api/V1/Controllers/Chart/CategoryController.php @@ -26,11 +26,10 @@ namespace FireflyIII\Api\V1\Controllers\Chart; use Carbon\Carbon; use FireflyIII\Api\V1\Controllers\Controller; -use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Api\V1\Requests\DateRequest; use FireflyIII\Repositories\Category\CategoryRepositoryInterface; use FireflyIII\User; use Illuminate\Http\JsonResponse; -use Illuminate\Http\Request; use Illuminate\Support\Collection; /** @@ -43,6 +42,7 @@ class CategoryController extends Controller /** * AccountController constructor. + * @codeCoverageIgnore */ public function __construct() { @@ -61,21 +61,22 @@ class CategoryController extends Controller /** - * @param Request $request + * @param DateRequest $request * * @return JsonResponse - * @throws FireflyException + * + * TODO after 4.8.0, simplify */ - public function overview(Request $request): JsonResponse + public function overview(DateRequest $request): JsonResponse { // parameters for chart: - $start = (string)$request->get('start'); - $end = (string)$request->get('end'); - if ('' === $start || '' === $end) { - throw new FireflyException('Start and end are mandatory parameters.'); - } - $start = Carbon::createFromFormat('Y-m-d', $start); - $end = Carbon::createFromFormat('Y-m-d', $end); + $dates = $request->getAll(); + /** @var Carbon $start */ + $start = $dates['start']; + /** @var Carbon $end */ + $end = $dates['end']; + + $tempData = []; $spent = $this->categoryRepository->spentInPeriodPerCurrency(new Collection, new Collection, $start, $end); $earned = $this->categoryRepository->earnedInPeriodPerCurrency(new Collection, new Collection, $start, $end); @@ -127,7 +128,7 @@ class CategoryController extends Controller 'entries' => [], ]; } - $amount = round($income['spent'], $decimalPlaces); + $amount = round($income['earned'], $decimalPlaces); $categories[$categoryName] = isset($categories[$categoryName]) ? $categories[$categoryName] + $amount : $amount; $tempData[$key]['entries'][$categoryName] = $amount; diff --git a/app/Api/V1/Controllers/ConfigurationController.php b/app/Api/V1/Controllers/ConfigurationController.php index 12ee8c55dd..972abdd6cd 100644 --- a/app/Api/V1/Controllers/ConfigurationController.php +++ b/app/Api/V1/Controllers/ConfigurationController.php @@ -32,6 +32,7 @@ use Illuminate\Http\JsonResponse; /** * Class ConfigurationController. + * @codeCoverageIgnore */ class ConfigurationController extends Controller { @@ -41,7 +42,8 @@ class ConfigurationController extends Controller private $repository; /** - * BudgetController constructor. + * ConfigurationController constructor. + * */ public function __construct() { @@ -78,10 +80,9 @@ class ConfigurationController extends Controller * Update the configuration. * * @param ConfigurationRequest $request - * @param string $name + * @param string $name * * @return JsonResponse - * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ public function update(ConfigurationRequest $request, string $name): JsonResponse { @@ -96,7 +97,6 @@ class ConfigurationController extends Controller * Get all config values. * * @return array - * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ private function getConfigData(): array { diff --git a/app/Api/V1/Controllers/Controller.php b/app/Api/V1/Controllers/Controller.php index aa15ba0d17..5f0717ec8f 100644 --- a/app/Api/V1/Controllers/Controller.php +++ b/app/Api/V1/Controllers/Controller.php @@ -61,7 +61,6 @@ class Controller extends BaseController * * @return string * - * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ protected function buildParams(): string { @@ -86,7 +85,6 @@ class Controller extends BaseController * Method to grab all parameters from the URI. * * @return ParameterBag - * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ private function getParameters(): ParameterBag { diff --git a/app/Api/V1/Controllers/CurrencyController.php b/app/Api/V1/Controllers/CurrencyController.php index 61a6f2a798..04e96231cb 100644 --- a/app/Api/V1/Controllers/CurrencyController.php +++ b/app/Api/V1/Controllers/CurrencyController.php @@ -26,18 +26,14 @@ namespace FireflyIII\Api\V1\Controllers; use FireflyIII\Api\V1\Requests\CurrencyRequest; use FireflyIII\Exceptions\FireflyException; -use FireflyIII\Helpers\Collector\TransactionCollectorInterface; -use FireflyIII\Helpers\Filter\InternalTransferFilter; +use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Models\Account; -use FireflyIII\Models\AvailableBudget; use FireflyIII\Models\Bill; -use FireflyIII\Models\BudgetLimit; use FireflyIII\Models\Recurrence; use FireflyIII\Models\RecurrenceTransaction; use FireflyIII\Models\Rule; use FireflyIII\Models\RuleTrigger; use FireflyIII\Models\TransactionCurrency; -use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Bill\BillRepositoryInterface; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; @@ -55,7 +51,7 @@ use FireflyIII\Transformers\CurrencyExchangeRateTransformer; use FireflyIII\Transformers\CurrencyTransformer; use FireflyIII\Transformers\RecurrenceTransformer; use FireflyIII\Transformers\RuleTransformer; -use FireflyIII\Transformers\TransactionTransformer; +use FireflyIII\Transformers\TransactionGroupTransformer; use FireflyIII\User; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; @@ -82,6 +78,7 @@ class CurrencyController extends Controller /** * CurrencyRepository constructor. + * @codeCoverageIgnore */ public function __construct() { @@ -104,10 +101,11 @@ class CurrencyController extends Controller /** * Display a list of accounts. * - * @param Request $request + * @param Request $request * @param TransactionCurrency $currency * * @return JsonResponse + * @codeCoverageIgnore */ public function accounts(Request $request, TransactionCurrency $currency): JsonResponse { @@ -130,7 +128,7 @@ class CurrencyController extends Controller // filter list on currency preference: $collection = $unfiltered->filter( - function (Account $account) use ($currency, $accountRepository) { + static function (Account $account) use ($currency, $accountRepository) { $currencyId = (int)$accountRepository->getMetaValue($account, 'currency_id'); return $currencyId === $currency->id; @@ -161,11 +159,12 @@ class CurrencyController extends Controller /** * Display a listing of the resource. * - * @param Request $request + * @param Request $request * * @param TransactionCurrency $currency * * @return JsonResponse + * @codeCoverageIgnore */ public function availableBudgets(Request $request, TransactionCurrency $currency): JsonResponse { @@ -180,18 +179,11 @@ class CurrencyController extends Controller $pageSize = (int)app('preferences')->getForUser(auth()->user(), 'listPageSize', 50)->data; // get list of available budgets. Count it and split it. + /** @var BudgetRepositoryInterface $repository */ $repository = app(BudgetRepositoryInterface::class); $repository->setUser($admin); - $unfiltered = $repository->getAvailableBudgets(); - - // filter list. - $collection = $unfiltered->filter( - function (AvailableBudget $availableBudget) use ($currency) { - return $availableBudget->transaction_currency_id === $currency->id; - } - ); - + $collection = $repository->getAvailableBudgetsByCurrency($currency); $count = $collection->count(); $availableBudgets = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize); @@ -216,10 +208,11 @@ class CurrencyController extends Controller /** * List all bills * - * @param Request $request + * @param Request $request * @param TransactionCurrency $currency * * @return JsonResponse + * @codeCoverageIgnore */ public function bills(Request $request, TransactionCurrency $currency): JsonResponse { @@ -236,7 +229,7 @@ class CurrencyController extends Controller // filter and paginate list: $collection = $unfiltered->filter( - function (Bill $bill) use ($currency) { + static function (Bill $bill) use ($currency) { return $bill->transaction_currency_id === $currency->id; } ); @@ -263,29 +256,21 @@ class CurrencyController extends Controller /** * List all budget limits * - * @param Request $request + * @param Request $request * * @param TransactionCurrency $currency * * @return JsonResponse + * @codeCoverageIgnore */ public function budgetLimits(Request $request, TransactionCurrency $currency): JsonResponse { /** @var BudgetRepositoryInterface $repository */ - $repository = app(BudgetRepositoryInterface::class); - $manager = new Manager; - $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; - $pageSize = (int)app('preferences')->getForUser(auth()->user(), 'listPageSize', 50)->data; - $unfiltered = $repository->getAllBudgetLimits($this->parameters->get('start'), $this->parameters->get('end')); - - // TODO replace this - // filter budget limits on currency ID - $collection = $unfiltered->filter( - function (BudgetLimit $budgetLimit) use ($currency) { - return $budgetLimit->transaction_currency_id === $currency->id; - } - ); - + $repository = app(BudgetRepositoryInterface::class); + $manager = new Manager; + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $pageSize = (int)app('preferences')->getForUser(auth()->user(), 'listPageSize', 50)->data; + $collection = $repository->getAllBudgetLimitsByCurrency($currency, $this->parameters->get('start'), $this->parameters->get('end')); $count = $collection->count(); $budgetLimits = $collection->slice(($this->parameters->get('page') - 1) * $pageSize, $pageSize); $paginator = new LengthAwarePaginator($budgetLimits, $count, $pageSize, $this->parameters->get('page')); @@ -306,10 +291,11 @@ class CurrencyController extends Controller /** * Show a list of known exchange rates * - * @param Request $request + * @param Request $request * @param TransactionCurrency $currency * * @return JsonResponse + * @codeCoverageIgnore */ public function cer(Request $request, TransactionCurrency $currency): JsonResponse { @@ -341,10 +327,11 @@ class CurrencyController extends Controller /** * Remove the specified resource from storage. * - * @param TransactionCurrency $currency + * @param TransactionCurrency $currency * * @return JsonResponse * @throws FireflyException + * @codeCoverageIgnore */ public function delete(TransactionCurrency $currency): JsonResponse { @@ -366,10 +353,11 @@ class CurrencyController extends Controller /** * Disable a currency. * - * @param Request $request + * @param Request $request * @param TransactionCurrency $currency * * @return JsonResponse + * @codeCoverageIgnore */ public function disable(Request $request, TransactionCurrency $currency): JsonResponse { @@ -398,10 +386,11 @@ class CurrencyController extends Controller /** * Enable a currency. * - * @param Request $request + * @param Request $request * @param TransactionCurrency $currency * * @return JsonResponse + * @codeCoverageIgnore */ public function enable(Request $request, TransactionCurrency $currency): JsonResponse { @@ -429,6 +418,7 @@ class CurrencyController extends Controller * @param Request $request * * @return JsonResponse + * @codeCoverageIgnore */ public function index(Request $request): JsonResponse { @@ -460,10 +450,11 @@ class CurrencyController extends Controller /** * Make the currency a default currency. * - * @param Request $request + * @param Request $request * @param TransactionCurrency $currency * * @return JsonResponse + * @codeCoverageIgnore */ public function makeDefault(Request $request, TransactionCurrency $currency): JsonResponse { @@ -491,11 +482,12 @@ class CurrencyController extends Controller /** * List all recurring transactions. * - * @param Request $request + * @param Request $request * * @param TransactionCurrency $currency * - * @return JsonResponse] + * @return JsonResponse + * @codeCoverageIgnore */ public function recurrences(Request $request, TransactionCurrency $currency): JsonResponse { @@ -513,7 +505,7 @@ class CurrencyController extends Controller // filter selection $collection = $unfiltered->filter( - function (Recurrence $recurrence) use ($currency) { + static function (Recurrence $recurrence) use ($currency) { /** @var RecurrenceTransaction $transaction */ foreach ($recurrence->recurrenceTransactions as $transaction) { if ($transaction->transaction_currency_id === $currency->id || $transaction->foreign_currency_id === $currency->id) { @@ -550,10 +542,11 @@ class CurrencyController extends Controller /** * List all of them. * - * @param Request $request + * @param Request $request * @param TransactionCurrency $currency * - * @return JsonResponse] + * @return JsonResponse + * @codeCoverageIgnore */ public function rules(Request $request, TransactionCurrency $currency): JsonResponse { @@ -567,7 +560,7 @@ class CurrencyController extends Controller $unfiltered = $repository->getAll(); $collection = $unfiltered->filter( - function (Rule $rule) use ($currency) { + static function (Rule $rule) use ($currency) { /** @var RuleTrigger $trigger */ foreach ($rule->ruleTriggers as $trigger) { if ('currency_is' === $trigger->trigger_type && $currency->name === $trigger->trigger_value) { @@ -603,10 +596,11 @@ class CurrencyController extends Controller /** * Show a currency. * - * @param Request $request + * @param Request $request * @param TransactionCurrency $currency * * @return JsonResponse + * @codeCoverageIgnore */ public function show(Request $request, TransactionCurrency $currency): JsonResponse { @@ -663,11 +657,12 @@ class CurrencyController extends Controller /** * Show all transactions. * - * @param Request $request + * @param Request $request * * @param TransactionCurrency $currency * * @return JsonResponse + * @codeCoverageIgnore */ public function transactions(Request $request, TransactionCurrency $currency): JsonResponse { @@ -682,28 +677,33 @@ class CurrencyController extends Controller /** @var User $admin */ $admin = auth()->user(); - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setUser($admin); - $collector->withOpposingAccount()->withCategoryInformation()->withBudgetInformation(); - $collector->setAllAssetAccounts(); - $collector->setCurrency($currency); - if (\in_array(TransactionType::TRANSFER, $types, true)) { - $collector->removeFilter(InternalTransferFilter::class); - } + // use new group collector: + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector + ->setUser($admin) + // filter on currency. + ->setCurrency($currency) + // all info needed for the API: + ->withAPIInformation() + // set page size: + ->setLimit($pageSize) + // set page to retrieve + ->setPage($this->parameters->get('page')) + // set types of transactions to return. + ->setTypes($types); + if (null !== $this->parameters->get('start') && null !== $this->parameters->get('end')) { $collector->setRange($this->parameters->get('start'), $this->parameters->get('end')); } - $collector->setLimit($pageSize)->setPage($this->parameters->get('page')); - $collector->setTypes($types); - $paginator = $collector->getPaginatedTransactions(); + $paginator = $collector->getPaginatedGroups(); $paginator->setPath(route('api.v1.currencies.transactions', [$currency->code]) . $this->buildParams()); $transactions = $paginator->getCollection(); - /** @var TransactionTransformer $transformer */ - $transformer = app(TransactionTransformer::class); + /** @var TransactionGroupTransformer $transformer */ + $transformer = app(TransactionGroupTransformer::class); $transformer->setParameters($this->parameters); $resource = new FractalCollection($transactions, $transformer, 'transactions'); @@ -715,7 +715,7 @@ class CurrencyController extends Controller /** * Update a currency. * - * @param CurrencyRequest $request + * @param CurrencyRequest $request * @param TransactionCurrency $currency * * @return JsonResponse diff --git a/app/Api/V1/Controllers/CurrencyExchangeRateController.php b/app/Api/V1/Controllers/CurrencyExchangeRateController.php index 0308285e70..25ddc71b32 100644 --- a/app/Api/V1/Controllers/CurrencyExchangeRateController.php +++ b/app/Api/V1/Controllers/CurrencyExchangeRateController.php @@ -37,6 +37,7 @@ use League\Fractal\Serializer\JsonApiSerializer; /** * Class CurrencyExchangeRateController + * @codeCoverageIgnore */ class CurrencyExchangeRateController extends Controller { @@ -45,6 +46,7 @@ class CurrencyExchangeRateController extends Controller /** * CurrencyExchangeRateController constructor. + * */ public function __construct() { @@ -88,6 +90,7 @@ class CurrencyExchangeRateController extends Controller throw new FireflyException('Unknown destination currency.'); } + /** @var Carbon $dateObj */ $dateObj = Carbon::createFromFormat('Y-m-d', $request->get('date') ?? date('Y-m-d')); $this->parameters->set('from', $fromCurrency->code); $this->parameters->set('to', $toCurrency->code); diff --git a/app/Api/V1/Controllers/ImportController.php b/app/Api/V1/Controllers/ImportController.php index ecaa5b5acc..302136d7b0 100644 --- a/app/Api/V1/Controllers/ImportController.php +++ b/app/Api/V1/Controllers/ImportController.php @@ -23,14 +23,12 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Controllers; -use FireflyIII\Helpers\Collector\TransactionCollectorInterface; -use FireflyIII\Helpers\Filter\InternalTransferFilter; +use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Models\ImportJob; -use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; use FireflyIII\Support\Http\Api\TransactionFilter; use FireflyIII\Transformers\ImportJobTransformer; -use FireflyIII\Transformers\TransactionTransformer; +use FireflyIII\Transformers\TransactionGroupTransformer; use FireflyIII\User; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; @@ -52,7 +50,8 @@ class ImportController extends Controller private $repository; /** - * LinkTypeController constructor. + * ImportController constructor. + * @codeCoverageIgnore */ public function __construct() { @@ -73,6 +72,7 @@ class ImportController extends Controller * @param Request $request * * @return JsonResponse + * @codeCoverageIgnore */ public function listAll(Request $request): JsonResponse { @@ -104,10 +104,11 @@ class ImportController extends Controller } /** - * @param Request $request + * @param Request $request * @param ImportJob $importJob * * @return JsonResponse + * @codeCoverageIgnore */ public function show(Request $request, ImportJob $importJob): JsonResponse { @@ -127,10 +128,11 @@ class ImportController extends Controller /** * Show all transactions * - * @param Request $request + * @param Request $request * @param ImportJob $importJob * * @return JsonResponse + * @codeCoverageIgnore */ public function transactions(Request $request, ImportJob $importJob): JsonResponse { @@ -151,29 +153,33 @@ class ImportController extends Controller if (null !== $tag) { /** @var User $admin */ $admin = auth()->user(); - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setUser($admin); - $collector->withOpposingAccount()->withCategoryInformation()->withBudgetInformation(); - $collector->setAllAssetAccounts(); - $collector->setTag($tag); - if (\in_array(TransactionType::TRANSFER, $types, true)) { - $collector->removeFilter(InternalTransferFilter::class); - } + // use new group collector: + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector + ->setUser($admin) + // filter on tag. + ->setTag($tag) + // all info needed for the API: + ->withAPIInformation() + // set page size: + ->setLimit($pageSize) + // set page to retrieve + ->setPage($this->parameters->get('page')) + // set types of transactions to return. + ->setTypes($types); if (null !== $this->parameters->get('start') && null !== $this->parameters->get('end')) { $collector->setRange($this->parameters->get('start'), $this->parameters->get('end')); } - $collector->setLimit($pageSize)->setPage($this->parameters->get('page')); - $collector->setTypes($types); - $paginator = $collector->getPaginatedTransactions(); + $paginator = $collector->getPaginatedGroups(); $paginator->setPath(route('api.v1.transactions.index') . $this->buildParams()); $transactions = $paginator->getCollection(); } - /** @var TransactionTransformer $transformer */ - $transformer = app(TransactionTransformer::class); + /** @var TransactionGroupTransformer $transformer */ + $transformer = app(TransactionGroupTransformer::class); $transformer->setParameters($this->parameters); $resource = new FractalCollection($transactions, $transformer, 'transactions'); diff --git a/app/Api/V1/Controllers/LinkTypeController.php b/app/Api/V1/Controllers/LinkTypeController.php index 5171d68a3a..1ce5a1bc74 100644 --- a/app/Api/V1/Controllers/LinkTypeController.php +++ b/app/Api/V1/Controllers/LinkTypeController.php @@ -25,15 +25,13 @@ namespace FireflyIII\Api\V1\Controllers; use FireflyIII\Api\V1\Requests\LinkTypeRequest; use FireflyIII\Exceptions\FireflyException; -use FireflyIII\Helpers\Collector\TransactionCollectorInterface; -use FireflyIII\Helpers\Filter\InternalTransferFilter; +use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Models\LinkType; -use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\LinkType\LinkTypeRepositoryInterface; use FireflyIII\Repositories\User\UserRepositoryInterface; use FireflyIII\Support\Http\Api\TransactionFilter; use FireflyIII\Transformers\LinkTypeTransformer; -use FireflyIII\Transformers\TransactionTransformer; +use FireflyIII\Transformers\TransactionGroupTransformer; use FireflyIII\User; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; @@ -60,6 +58,8 @@ class LinkTypeController extends Controller /** * LinkTypeController constructor. + * + * @codeCoverageIgnore */ public function __construct() { @@ -84,6 +84,7 @@ class LinkTypeController extends Controller * * @return JsonResponse * @throws FireflyException + * @codeCoverageIgnore */ public function delete(LinkType $linkType): JsonResponse { @@ -100,7 +101,8 @@ class LinkTypeController extends Controller * * @param Request $request * - * @return JsonResponse] + * @return JsonResponse + * @codeCoverageIgnore */ public function index(Request $request): JsonResponse { @@ -135,10 +137,11 @@ class LinkTypeController extends Controller /** * List single resource. * - * @param Request $request + * @param Request $request * @param LinkType $linkType * * @return JsonResponse + * @codeCoverageIgnore */ public function show(Request $request, LinkType $linkType): JsonResponse { @@ -190,10 +193,11 @@ class LinkTypeController extends Controller /** * Delete the resource. * - * @param Request $request + * @param Request $request * @param LinkType $linkType * * @return JsonResponse + * @codeCoverageIgnore */ public function transactions(Request $request, LinkType $linkType): JsonResponse { @@ -211,28 +215,33 @@ class LinkTypeController extends Controller /** @var User $admin */ $admin = auth()->user(); - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setUser($admin); - $collector->withOpposingAccount()->withCategoryInformation()->withBudgetInformation(); - $collector->setAllAssetAccounts(); - $collector->setJournalIds($journalIds); - if (\in_array(TransactionType::TRANSFER, $types, true)) { - $collector->removeFilter(InternalTransferFilter::class); - } + // use new group collector: + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector + ->setUser($admin) + // filter on journal IDs. + ->setJournalIds($journalIds) + // all info needed for the API: + ->withAPIInformation() + // set page size: + ->setLimit($pageSize) + // set page to retrieve + ->setPage($this->parameters->get('page')) + // set types of transactions to return. + ->setTypes($types); + if (null !== $this->parameters->get('start') && null !== $this->parameters->get('end')) { $collector->setRange($this->parameters->get('start'), $this->parameters->get('end')); } - $collector->setLimit($pageSize)->setPage($this->parameters->get('page')); - $collector->setTypes($types); - $paginator = $collector->getPaginatedTransactions(); + $paginator = $collector->getPaginatedGroups(); $paginator->setPath(route('api.v1.transactions.index') . $this->buildParams()); $transactions = $paginator->getCollection(); - /** @var TransactionTransformer $transformer */ - $transformer = app(TransactionTransformer::class); + /** @var TransactionGroupTransformer $transformer */ + $transformer = app(TransactionGroupTransformer::class); $transformer->setParameters($this->parameters); $resource = new FractalCollection($transactions, $transformer, 'transactions'); @@ -246,7 +255,7 @@ class LinkTypeController extends Controller * Update object. * * @param LinkTypeRequest $request - * @param LinkType $linkType + * @param LinkType $linkType * * @return JsonResponse * @throws FireflyException diff --git a/app/Api/V1/Controllers/PiggyBankController.php b/app/Api/V1/Controllers/PiggyBankController.php index 650c8c7732..2f223e821a 100644 --- a/app/Api/V1/Controllers/PiggyBankController.php +++ b/app/Api/V1/Controllers/PiggyBankController.php @@ -52,6 +52,8 @@ class PiggyBankController extends Controller /** * PiggyBankController constructor. + * + * @codeCoverageIgnore */ public function __construct() { @@ -76,6 +78,7 @@ class PiggyBankController extends Controller * @param PiggyBank $piggyBank * * @return JsonResponse + * @codeCoverageIgnore */ public function delete(PiggyBank $piggyBank): JsonResponse { @@ -89,7 +92,8 @@ class PiggyBankController extends Controller * * @param Request $request * - * @return JsonResponse] + * @return JsonResponse + * @codeCoverageIgnore */ public function index(Request $request): JsonResponse { @@ -126,10 +130,11 @@ class PiggyBankController extends Controller /** * List single resource. * - * @param Request $request + * @param Request $request * @param PiggyBank $piggyBank * * @return JsonResponse + * @codeCoverageIgnore */ public function piggyBankEvents(Request $request, PiggyBank $piggyBank): JsonResponse { @@ -161,10 +166,11 @@ class PiggyBankController extends Controller /** * List single resource. * - * @param Request $request + * @param Request $request * @param PiggyBank $piggyBank * * @return JsonResponse + * @codeCoverageIgnore */ public function show(Request $request, PiggyBank $piggyBank): JsonResponse { @@ -214,7 +220,7 @@ class PiggyBankController extends Controller * Update piggy bank. * * @param PiggyBankRequest $request - * @param PiggyBank $piggyBank + * @param PiggyBank $piggyBank * * @return JsonResponse */ diff --git a/app/Api/V1/Controllers/PreferenceController.php b/app/Api/V1/Controllers/PreferenceController.php index 16e0ffad09..1dc807e410 100644 --- a/app/Api/V1/Controllers/PreferenceController.php +++ b/app/Api/V1/Controllers/PreferenceController.php @@ -45,12 +45,14 @@ class PreferenceController extends Controller { /** * LinkTypeController constructor. + * + * @codeCoverageIgnore */ public function __construct() { parent::__construct(); $this->middleware( - function ($request, $next) { + static function ($request, $next) { /** @var User $user */ $user = auth()->user(); $repository = app(AccountRepositoryInterface::class); @@ -59,7 +61,7 @@ class PreferenceController extends Controller // an important fallback is that the frontPageAccount array gets refilled automatically // when it turns up empty. $frontPageAccounts = app('preferences')->getForUser($user, 'frontPageAccounts', [])->data; - if (0 === \count($frontPageAccounts)) { + if (0 === count($frontPageAccounts)) { /** @var Collection $accounts */ $accounts = $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]); $accountIds = $accounts->pluck('id')->toArray(); @@ -76,7 +78,8 @@ class PreferenceController extends Controller * * @param Request $request * - * @return JsonResponse] + * @return JsonResponse + * @codeCoverageIgnore */ public function index(Request $request): JsonResponse { @@ -85,7 +88,7 @@ class PreferenceController extends Controller $available = [ 'language', 'customFiscalYear', 'fiscalYearStart', 'currencyPreference', 'transaction_journal_optional_fields', 'frontPageAccounts', 'viewRange', - 'listPageSize, twoFactorAuthEnabled', + 'listPageSize', ]; $preferences = new Collection; @@ -111,17 +114,16 @@ class PreferenceController extends Controller return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); - } /** * Return a single preference by name. * - * @param Request $request + * @param Request $request * @param Preference $preference * * @return JsonResponse - * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @codeCoverageIgnore */ public function show(Request $request, Preference $preference): JsonResponse { @@ -144,10 +146,9 @@ class PreferenceController extends Controller * Update a preference. * * @param PreferenceRequest $request - * @param Preference $preference + * @param Preference $preference * * @return JsonResponse - * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ public function update(PreferenceRequest $request, Preference $preference): JsonResponse { @@ -165,7 +166,6 @@ class PreferenceController extends Controller $newValue = (int)$data['data']; break; case 'customFiscalYear': - case 'twoFactorAuthEnabled': $newValue = 1 === (int)$data['data']; break; } diff --git a/app/Api/V1/Controllers/RecurrenceController.php b/app/Api/V1/Controllers/RecurrenceController.php index 6a01d17d61..8cae59e5ea 100644 --- a/app/Api/V1/Controllers/RecurrenceController.php +++ b/app/Api/V1/Controllers/RecurrenceController.php @@ -23,17 +23,16 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Controllers; -use FireflyIII\Api\V1\Requests\RecurrenceRequest; +use FireflyIII\Api\V1\Requests\RecurrenceStoreRequest; +use FireflyIII\Api\V1\Requests\RecurrenceUpdateRequest; use FireflyIII\Exceptions\FireflyException; -use FireflyIII\Helpers\Collector\TransactionCollectorInterface; -use FireflyIII\Helpers\Filter\InternalTransferFilter; +use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Models\Recurrence; -use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Recurring\RecurringRepositoryInterface; use FireflyIII\Support\Cronjobs\RecurringCronjob; use FireflyIII\Support\Http\Api\TransactionFilter; use FireflyIII\Transformers\RecurrenceTransformer; -use FireflyIII\Transformers\TransactionTransformer; +use FireflyIII\Transformers\TransactionGroupTransformer; use FireflyIII\User; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; @@ -56,6 +55,8 @@ class RecurrenceController extends Controller /** * RecurrenceController constructor. + * + * @codeCoverageIgnore */ public function __construct() { @@ -80,6 +81,7 @@ class RecurrenceController extends Controller * @param Recurrence $recurrence * * @return JsonResponse + * @codeCoverageIgnore */ public function delete(Recurrence $recurrence): JsonResponse { @@ -93,7 +95,8 @@ class RecurrenceController extends Controller * * @param Request $request * - * @return JsonResponse] + * @return JsonResponse + * @codeCoverageIgnore */ public function index(Request $request): JsonResponse { @@ -130,10 +133,11 @@ class RecurrenceController extends Controller /** * List single resource. * - * @param Request $request + * @param Request $request * @param Recurrence $recurrence * * @return JsonResponse + * @codeCoverageIgnore */ public function show(Request $request, Recurrence $recurrence): JsonResponse { @@ -155,13 +159,13 @@ class RecurrenceController extends Controller /** * Store new object. * - * @param RecurrenceRequest $request + * @param RecurrenceStoreRequest $request * * @return JsonResponse */ - public function store(RecurrenceRequest $request): JsonResponse + public function store(RecurrenceStoreRequest $request): JsonResponse { - $recurrence = $this->repository->store($request->getAll()); + $recurrence = $this->repository->store($request->getAllRecurrenceData()); $manager = new Manager(); $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; $manager->setSerializer(new JsonApiSerializer($baseUrl)); @@ -178,10 +182,11 @@ class RecurrenceController extends Controller /** * Show transactions for this recurrence. * - * @param Request $request + * @param Request $request * @param Recurrence $recurrence * * @return JsonResponse + * @codeCoverageIgnore */ public function transactions(Request $request, Recurrence $recurrence): JsonResponse { @@ -199,28 +204,32 @@ class RecurrenceController extends Controller /** @var User $admin */ $admin = auth()->user(); - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setUser($admin); - $collector->withOpposingAccount()->withCategoryInformation()->withBudgetInformation(); - $collector->setAllAssetAccounts(); - $collector->setJournalIds($journalIds); - if (\in_array(TransactionType::TRANSFER, $types, true)) { - $collector->removeFilter(InternalTransferFilter::class); - } + // use new group collector: + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector + ->setUser($admin) + // filter on journal IDs. + ->setJournalIds($journalIds) + // all info needed for the API: + ->withAPIInformation() + // set page size: + ->setLimit($pageSize) + // set page to retrieve + ->setPage($this->parameters->get('page')) + // set types of transactions to return. + ->setTypes($types); if (null !== $this->parameters->get('start') && null !== $this->parameters->get('end')) { $collector->setRange($this->parameters->get('start'), $this->parameters->get('end')); } - $collector->setLimit($pageSize)->setPage($this->parameters->get('page')); - $collector->setTypes($types); - $paginator = $collector->getPaginatedTransactions(); + $paginator = $collector->getPaginatedGroups(); $paginator->setPath(route('api.v1.transactions.index') . $this->buildParams()); $transactions = $paginator->getCollection(); - /** @var TransactionTransformer $transformer */ - $transformer = app(TransactionTransformer::class); + /** @var TransactionGroupTransformer $transformer */ + $transformer = app(TransactionGroupTransformer::class); $transformer->setParameters($this->parameters); $resource = new FractalCollection($transactions, $transformer, 'transactions'); @@ -232,6 +241,7 @@ class RecurrenceController extends Controller /** * @return JsonResponse * @throws FireflyException + * @codeCoverageIgnore */ public function trigger(): JsonResponse { @@ -256,14 +266,14 @@ class RecurrenceController extends Controller /** * Update single recurrence. * - * @param RecurrenceRequest $request - * @param Recurrence $recurrence + * @param RecurrenceUpdateRequest $request + * @param Recurrence $recurrence * * @return JsonResponse */ - public function update(RecurrenceRequest $request, Recurrence $recurrence): JsonResponse + public function update(RecurrenceUpdateRequest $request, Recurrence $recurrence): JsonResponse { - $data = $request->getAll(); + $data = $request->getAllRecurrenceData(); $category = $this->repository->update($recurrence, $data); $manager = new Manager(); $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; diff --git a/app/Api/V1/Controllers/RuleController.php b/app/Api/V1/Controllers/RuleController.php index 6990f21a78..444794f36b 100644 --- a/app/Api/V1/Controllers/RuleController.php +++ b/app/Api/V1/Controllers/RuleController.php @@ -23,22 +23,22 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Controllers; -use Carbon\Carbon; use FireflyIII\Api\V1\Requests\RuleRequest; +use FireflyIII\Api\V1\Requests\RuleTestRequest; +use FireflyIII\Api\V1\Requests\RuleTriggerRequest; use FireflyIII\Exceptions\FireflyException; -use FireflyIII\Jobs\ExecuteRuleOnExistingTransactions; -use FireflyIII\Models\AccountType; +use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Models\Rule; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Rule\RuleRepositoryInterface; +use FireflyIII\TransactionRules\Engine\RuleEngine; use FireflyIII\TransactionRules\TransactionMatcher; use FireflyIII\Transformers\RuleTransformer; -use FireflyIII\Transformers\TransactionTransformer; +use FireflyIII\Transformers\TransactionGroupTransformer; use FireflyIII\User; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Pagination\LengthAwarePaginator; -use Illuminate\Support\Collection; use League\Fractal\Manager; use League\Fractal\Pagination\IlluminatePaginatorAdapter; use League\Fractal\Resource\Collection as FractalCollection; @@ -48,6 +48,7 @@ use Log; /** * Class RuleController + * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class RuleController extends Controller { @@ -58,6 +59,7 @@ class RuleController extends Controller /** * RuleController constructor. + * @codeCoverageIgnore */ public function __construct() { @@ -84,6 +86,7 @@ class RuleController extends Controller * @param Rule $rule * * @return JsonResponse + * @codeCoverageIgnore */ public function delete(Rule $rule): JsonResponse { @@ -97,7 +100,8 @@ class RuleController extends Controller * * @param Request $request * - * @return JsonResponse] + * @return JsonResponse + * @codeCoverageIgnore */ public function index(Request $request): JsonResponse { @@ -135,9 +139,10 @@ class RuleController extends Controller * List single resource. * * @param Request $request - * @param Rule $rule + * @param Rule $rule * * @return JsonResponse + * @codeCoverageIgnore */ public function show(Request $request, Rule $rule): JsonResponse { @@ -179,55 +184,32 @@ class RuleController extends Controller } /** - * @param Request $request - * @param Rule $rule + * @param RuleTestRequest $request + * @param Rule $rule * * @return JsonResponse * @throws FireflyException */ - public function testRule(Request $request, Rule $rule): JsonResponse + public function testRule(RuleTestRequest $request, Rule $rule): JsonResponse { - $pageSize = (int)app('preferences')->getForUser(auth()->user(), 'listPageSize', 50)->data; - $page = 0 === (int)$request->query('page') ? 1 : (int)$request->query('page'); - $startDate = null === $request->query('start_date') ? null : Carbon::createFromFormat('Y-m-d', $request->query('start_date')); - $endDate = null === $request->query('end_date') ? null : Carbon::createFromFormat('Y-m-d', $request->query('end_date')); - $searchLimit = 0 === (int)$request->query('search_limit') ? (int)config('firefly.test-triggers.limit') : (int)$request->query('search_limit'); - $triggerLimit = 0 === (int)$request->query('triggered_limit') ? (int)config('firefly.test-triggers.range') : (int)$request->query('triggered_limit'); - $accountList = '' === (string)$request->query('accounts') ? [] : explode(',', $request->query('accounts')); - $accounts = new Collection; - - foreach ($accountList as $accountId) { - Log::debug(sprintf('Searching for asset account with id "%s"', $accountId)); - $account = $this->accountRepository->findNull((int)$accountId); - if (null !== $account && AccountType::ASSET === $account->accountType->type) { - Log::debug(sprintf('Found account #%d ("%s") and its an asset account', $account->id, $account->name)); - $accounts->push($account); - } - if (null === $account) { - Log::debug(sprintf('No asset account with id "%s"', $accountId)); - } - } - + $pageSize = (int)app('preferences')->getForUser(auth()->user(), 'listPageSize', 50)->data; + $parameters = $request->getTestParameters(); /** @var Rule $rule */ Log::debug(sprintf('Now testing rule #%d, "%s"', $rule->id, $rule->title)); /** @var TransactionMatcher $matcher */ $matcher = app(TransactionMatcher::class); // set all parameters: $matcher->setRule($rule); - $matcher->setStartDate($startDate); - $matcher->setEndDate($endDate); - $matcher->setSearchLimit($searchLimit); - $matcher->setTriggeredLimit($triggerLimit); - $matcher->setAccounts($accounts); + $matcher->setStartDate($parameters['start_date']); + $matcher->setEndDate($parameters['end_date']); + $matcher->setSearchLimit($parameters['search_limit']); + $matcher->setTriggeredLimit($parameters['trigger_limit']); + $matcher->setAccounts($parameters['accounts']); $matchingTransactions = $matcher->findTransactionsByRule(); - $matchingTransactions = $matchingTransactions->unique('id'); - - // make paginator out of results. - $count = $matchingTransactions->count(); - $transactions = $matchingTransactions->slice(($page - 1) * $pageSize, $pageSize); - // make paginator: - $paginator = new LengthAwarePaginator($transactions, $count, $pageSize, $this->parameters->get('page')); + $count = count($matchingTransactions); + $transactions = array_slice($matchingTransactions, ($parameters['page'] - 1) * $pageSize, $pageSize); + $paginator = new LengthAwarePaginator($transactions, $count, $pageSize, $this->parameters->get('page')); $paginator->setPath(route('api.v1.rules.test', [$rule->id]) . $this->buildParams()); // resulting list is presented as JSON thing. @@ -235,8 +217,8 @@ class RuleController extends Controller $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; $manager->setSerializer(new JsonApiSerializer($baseUrl)); - /** @var TransactionTransformer $transformer */ - $transformer = app(TransactionTransformer::class); + /** @var TransactionGroupTransformer $transformer */ + $transformer = app(TransactionGroupTransformer::class); $transformer->setParameters($this->parameters); $resource = new FractalCollection($matchingTransactions, $transformer, 'transactions'); @@ -248,45 +230,38 @@ class RuleController extends Controller /** * Execute the given rule group on a set of existing transactions. * - * @param Request $request - * @param Rule $rule + * @param RuleTriggerRequest $request + * @param Rule $rule * * @return JsonResponse */ - public function triggerRule(Request $request, Rule $rule): JsonResponse + public function triggerRule(RuleTriggerRequest $request, Rule $rule): JsonResponse { // Get parameters specified by the user - /** @var User $user */ - $user = auth()->user(); - $startDate = new Carbon($request->get('start_date')); - $endDate = new Carbon($request->get('end_date')); - $accountList = '' === (string)$request->query('accounts') ? [] : explode(',', $request->query('accounts')); - $accounts = new Collection; + $parameters = $request->getTriggerParameters(); - foreach ($accountList as $accountId) { - Log::debug(sprintf('Searching for asset account with id "%s"', $accountId)); - $account = $this->accountRepository->findNull((int)$accountId); - if (null !== $account && $this->accountRepository->isAsset($account)) { - Log::debug(sprintf('Found account #%d ("%s") and its an asset account', $account->id, $account->name)); - $accounts->push($account); - } - if (null === $account) { - Log::debug(sprintf('No asset account with id "%s"', $accountId)); - } + /** @var RuleEngine $ruleEngine */ + $ruleEngine = app(RuleEngine::class); + $ruleEngine->setUser(auth()->user()); + + $rules = [$rule->id]; + + $ruleEngine->setRulesToApply($rules); + $ruleEngine->setTriggerMode(RuleEngine::TRIGGER_STORE); + + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setAccounts($parameters['accounts']); + $collector->setRange($parameters['start_date'], $parameters['end_date']); + $journals = $collector->getExtractedJournals(); + + /** @var array $journal */ + foreach ($journals as $journal) { + Log::debug('Start of new journal.'); + $ruleEngine->processJournalArray($journal); + Log::debug('Done with all rules for this group + done with journal.'); } - // Create a job to do the work asynchronously - $job = new ExecuteRuleOnExistingTransactions($rule); - - // Apply parameters to the job - $job->setUser($user); - $job->setAccounts($accounts); - $job->setStartDate($startDate); - $job->setEndDate($endDate); - - // Dispatch a new job to execute it in a queue - $this->dispatch($job); - return response()->json([], 204); } @@ -294,13 +269,59 @@ class RuleController extends Controller * Update a rule. * * @param RuleRequest $request - * @param Rule $rule + * @param Rule $rule * * @return JsonResponse */ public function update(RuleRequest $request, Rule $rule): JsonResponse { - $rule = $this->ruleRepository->update($rule, $request->getAll()); + $rule = $this->ruleRepository->update($rule, $request->getAll()); + + $manager = new Manager(); + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + + /** @var RuleTransformer $transformer */ + $transformer = app(RuleTransformer::class); + $transformer->setParameters($this->parameters); + + $resource = new Item($rule, $transformer, 'rules'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + } + + /** + * @param Request $request + * @param Rule $rule + * @return JsonResponse + */ + public function moveDown(Request $request, Rule $rule): JsonResponse + { + $this->ruleRepository->moveDown($rule); + $rule = $this->ruleRepository->find($rule->id); + $manager = new Manager(); + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + + /** @var RuleTransformer $transformer */ + $transformer = app(RuleTransformer::class); + $transformer->setParameters($this->parameters); + + $resource = new Item($rule, $transformer, 'rules'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + + } + + /** + * @param Request $request + * @param Rule $rule + * @return JsonResponse + */ + public function moveUp(Request $request, Rule $rule): JsonResponse + { + $this->ruleRepository->moveUp($rule); + $rule = $this->ruleRepository->find($rule->id); $manager = new Manager(); $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; $manager->setSerializer(new JsonApiSerializer($baseUrl)); diff --git a/app/Api/V1/Controllers/RuleGroupController.php b/app/Api/V1/Controllers/RuleGroupController.php index 286a247ee9..bfce0bcdc9 100644 --- a/app/Api/V1/Controllers/RuleGroupController.php +++ b/app/Api/V1/Controllers/RuleGroupController.php @@ -23,19 +23,21 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Controllers; -use Carbon\Carbon; +use Exception; use FireflyIII\Api\V1\Requests\RuleGroupRequest; +use FireflyIII\Api\V1\Requests\RuleGroupTestRequest; +use FireflyIII\Api\V1\Requests\RuleGroupTriggerRequest; use FireflyIII\Exceptions\FireflyException; -use FireflyIII\Jobs\ExecuteRuleOnExistingTransactions; -use FireflyIII\Models\AccountType; +use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Models\Rule; use FireflyIII\Models\RuleGroup; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface; +use FireflyIII\TransactionRules\Engine\RuleEngine; use FireflyIII\TransactionRules\TransactionMatcher; use FireflyIII\Transformers\RuleGroupTransformer; use FireflyIII\Transformers\RuleTransformer; -use FireflyIII\Transformers\TransactionTransformer; +use FireflyIII\Transformers\TransactionGroupTransformer; use FireflyIII\User; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; @@ -48,7 +50,6 @@ use League\Fractal\Resource\Item; use League\Fractal\Serializer\JsonApiSerializer; use Log; - /** * Class RuleGroupController */ @@ -61,6 +62,7 @@ class RuleGroupController extends Controller /** * RuleGroupController constructor. + * @codeCoverageIgnore */ public function __construct() { @@ -87,6 +89,7 @@ class RuleGroupController extends Controller * @param RuleGroup $ruleGroup * * @return JsonResponse + * @codeCoverageIgnore */ public function delete(RuleGroup $ruleGroup): JsonResponse { @@ -100,7 +103,8 @@ class RuleGroupController extends Controller * * @param Request $request * - * @return JsonResponse] + * @return JsonResponse + * @codeCoverageIgnore */ public function index(Request $request): JsonResponse { @@ -134,10 +138,11 @@ class RuleGroupController extends Controller } /** - * @param Request $request + * @param Request $request * @param RuleGroup $group * * @return JsonResponse + * @codeCoverageIgnore */ public function rules(Request $request, RuleGroup $group): JsonResponse { @@ -174,10 +179,11 @@ class RuleGroupController extends Controller /** * List single resource. * - * @param Request $request + * @param Request $request * @param RuleGroup $ruleGroup * * @return JsonResponse + * @codeCoverageIgnore */ public function show(Request $request, RuleGroup $ruleGroup): JsonResponse { @@ -220,23 +226,24 @@ class RuleGroupController extends Controller } /** - * @param Request $request + * @param RuleGroupTestRequest $request * @param RuleGroup $group * * @return JsonResponse * @throws FireflyException + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ - public function testGroup(Request $request, RuleGroup $group): JsonResponse + public function testGroup(RuleGroupTestRequest $request, RuleGroup $group): JsonResponse { + $pageSize = (int)app('preferences')->getForUser(auth()->user(), 'listPageSize', 50)->data; Log::debug('Now in testGroup()'); /** @var Collection $rules */ $rules = $this->ruleGroupRepository->getActiveRules($group); if (0 === $rules->count()) { throw new FireflyException('No rules in this rule group.'); } - $parameters = $this->getTestParameters($request); - $accounts = $this->getAccountParameter($parameters['account_list']); - $matchingTransactions = new Collection; + $parameters = $request->getTestParameters(); + $matchingTransactions = []; Log::debug(sprintf('Going to test %d rules', $rules->count())); /** @var Rule $rule */ @@ -250,18 +257,19 @@ class RuleGroupController extends Controller $matcher->setEndDate($parameters['end_date']); $matcher->setSearchLimit($parameters['search_limit']); $matcher->setTriggeredLimit($parameters['trigger_limit']); - $matcher->setAccounts($accounts); + $matcher->setAccounts($parameters['accounts']); - $result = $matcher->findTransactionsByRule(); - $matchingTransactions = $result->merge($matchingTransactions); + $result = $matcher->findTransactionsByRule(); + /** @noinspection AdditionOperationOnArraysInspection */ + $matchingTransactions = $result + $matchingTransactions; } - $matchingTransactions = $matchingTransactions->unique('id'); // make paginator out of results. - $count = $matchingTransactions->count(); - $transactions = $matchingTransactions->slice(($parameters['page'] - 1) * $parameters['page_size'], $parameters['page_size']); + $count = count($matchingTransactions); + $transactions = array_slice($matchingTransactions, ($parameters['page'] - 1) * $pageSize, $pageSize); + // make paginator: - $paginator = new LengthAwarePaginator($transactions, $count, $parameters['page_size'], $parameters['page']); + $paginator = new LengthAwarePaginator($transactions, $count, $pageSize, $parameters['page']); $paginator->setPath(route('api.v1.rule_groups.test', [$group->id]) . $this->buildParams()); // resulting list is presented as JSON thing. @@ -269,8 +277,7 @@ class RuleGroupController extends Controller $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; $manager->setSerializer(new JsonApiSerializer($baseUrl)); - /** @var TransactionTransformer $transformer */ - $transformer = app(TransactionTransformer::class); + $transformer = app(TransactionGroupTransformer::class); $transformer->setParameters($this->parameters); $resource = new FractalCollection($matchingTransactions, $transformer, 'transactions'); @@ -282,47 +289,42 @@ class RuleGroupController extends Controller /** * Execute the given rule group on a set of existing transactions. * - * @param Request $request + * @param RuleGroupTriggerRequest $request * @param RuleGroup $group * * @return JsonResponse + * @throws Exception */ - public function triggerGroup(Request $request, RuleGroup $group): JsonResponse + public function triggerGroup(RuleGroupTriggerRequest $request, RuleGroup $group): JsonResponse { - // Get parameters specified by the user - /** @var User $user */ - $user = auth()->user(); - $startDate = new Carbon($request->get('start_date')); - $endDate = new Carbon($request->get('end_date')); - $accountList = '' === (string)$request->query('accounts') ? [] : explode(',', $request->query('accounts')); - $accounts = new Collection; + $parameters = $request->getTriggerParameters(); - foreach ($accountList as $accountId) { - Log::debug(sprintf('Searching for asset account with id "%s"', $accountId)); - $account = $this->accountRepository->findNull((int)$accountId); - if (null !== $account && AccountType::ASSET === $account->accountType->type) { - Log::debug(sprintf('Found account #%d ("%s") and its an asset account', $account->id, $account->name)); - $accounts->push($account); - } - if (null === $account) { - Log::debug(sprintf('No asset account with id "%s"', $accountId)); - } + /** @var Collection $collection */ + $collection = $this->ruleGroupRepository->getActiveRules($group); + $rules = []; + /** @var Rule $item */ + foreach ($collection as $item) { + $rules[] = $item->id; } - /** @var Collection $rules */ - $rules = $this->ruleGroupRepository->getActiveRules($group); - foreach ($rules as $rule) { - // Create a job to do the work asynchronously - $job = new ExecuteRuleOnExistingTransactions($rule); + // start looping. + /** @var RuleEngine $ruleEngine */ + $ruleEngine = app(RuleEngine::class); + $ruleEngine->setUser(auth()->user()); + $ruleEngine->setRulesToApply($rules); + $ruleEngine->setTriggerMode(RuleEngine::TRIGGER_STORE); - // Apply parameters to the job - $job->setUser($user); - $job->setAccounts($accounts); - $job->setStartDate($startDate); - $job->setEndDate($endDate); + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setAccounts($parameters['accounts']); + $collector->setRange($parameters['start_date'], $parameters['end_date']); + $journals = $collector->getExtractedJournals(); - // Dispatch a new job to execute it in a queue - $this->dispatch($job); + /** @var array $journal */ + foreach ($journals as $journal) { + Log::debug('Start of new journal.'); + $ruleEngine->processJournalArray($journal); + Log::debug('Done with all rules for this group + done with journal.'); } return response()->json([], 204); @@ -330,10 +332,9 @@ class RuleGroupController extends Controller /** * Update a rule group. - * TODO update order of rule group * * @param RuleGroupRequest $request - * @param RuleGroup $ruleGroup + * @param RuleGroup $ruleGroup * * @return JsonResponse */ @@ -351,51 +352,49 @@ class RuleGroupController extends Controller $resource = new Item($ruleGroup, $transformer, 'rule_groups'); return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); - - } - - /** - * @param array $accounts - * - * @return Collection - */ - private function getAccountParameter(array $accounts): Collection - { - $return = new Collection; - foreach ($accounts as $accountId) { - Log::debug(sprintf('Searching for asset account with id "%s"', $accountId)); - $account = $this->accountRepository->findNull((int)$accountId); - if (null !== $account && AccountType::ASSET === $account->accountType->type) { - Log::debug(sprintf('Found account #%d ("%s") and its an asset account', $account->id, $account->name)); - $return->push($account); - } - if (null === $account) { - Log::debug(sprintf('No asset account with id "%s"', $accountId)); - } - } - - return $return; } /** * @param Request $request - * - * @return array + * @param RuleGroup $ruleGroup + * @return JsonResponse */ - private function getTestParameters(Request $request): array + public function moveDown(Request $request, RuleGroup $ruleGroup): JsonResponse { - return [ - 'page_size' => (int)app('preferences')->getForUser(auth()->user(), 'listPageSize', 50)->data, - 'page' => 0 === (int)$request->query('page') ? 1 : (int)$request->query('page'), - 'start_date' => null === $request->query('start_date') ? null : Carbon::createFromFormat('Y-m-d', $request->query('start_date')), - 'end_date' => null === $request->query('end_date') ? null : Carbon::createFromFormat('Y-m-d', $request->query('end_date')), - 'search_limit' => 0 === (int)$request->query('search_limit') ? (int)config('firefly.test-triggers.limit') : (int)$request->query('search_limit'), - 'trigger_limit' => 0 === (int)$request->query('triggered_limit') - ? (int)config('firefly.test-triggers.range') - : (int)$request->query( - 'triggered_limit' - ), - 'account_list' => '' === (string)$request->query('accounts') ? [] : explode(',', $request->query('accounts')), - ]; + $this->ruleGroupRepository->moveDown($ruleGroup); + $ruleGroup = $this->ruleGroupRepository->find($ruleGroup->id); + $manager = new Manager(); + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + + /** @var RuleGroupTransformer $transformer */ + $transformer = app(RuleGroupTransformer::class); + $transformer->setParameters($this->parameters); + + $resource = new Item($ruleGroup, $transformer, 'rule_groups'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); + } + + /** + * @param Request $request + * @param RuleGroup $ruleGroup + * @return JsonResponse + */ + public function moveUp(Request $request, RuleGroup $ruleGroup): JsonResponse + { + $this->ruleGroupRepository->moveUp($ruleGroup); + $ruleGroup = $this->ruleGroupRepository->find($ruleGroup->id); + $manager = new Manager(); + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + $manager->setSerializer(new JsonApiSerializer($baseUrl)); + + /** @var RuleGroupTransformer $transformer */ + $transformer = app(RuleGroupTransformer::class); + $transformer->setParameters($this->parameters); + + $resource = new Item($ruleGroup, $transformer, 'rule_groups'); + + return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); } } diff --git a/app/Api/V1/Controllers/SummaryController.php b/app/Api/V1/Controllers/SummaryController.php index 929320e1d9..1fcb4fa713 100644 --- a/app/Api/V1/Controllers/SummaryController.php +++ b/app/Api/V1/Controllers/SummaryController.php @@ -26,12 +26,12 @@ namespace FireflyIII\Api\V1\Controllers; use Carbon\Carbon; -use FireflyIII\Exceptions\FireflyException; -use FireflyIII\Helpers\Collector\TransactionCollectorInterface; +use Exception; +use FireflyIII\Api\V1\Requests\DateRequest; +use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Helpers\Report\NetWorthInterface; use FireflyIII\Models\Account; use FireflyIII\Models\AccountType; -use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionCurrency; use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Account\AccountRepositoryInterface; @@ -40,7 +40,6 @@ use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; use FireflyIII\User; use Illuminate\Http\JsonResponse; -use Illuminate\Http\Request; use Illuminate\Support\Collection; /** @@ -58,7 +57,8 @@ class SummaryController extends Controller private $currencyRepos; /** - * AccountController constructor. + * SummaryController constructor. + * @codeCoverageIgnore */ public function __construct() { @@ -84,21 +84,19 @@ class SummaryController extends Controller } /** - * @param Request $request + * @param DateRequest $request * * @return JsonResponse - * @throws FireflyException + * @throws Exception */ - public function basic(Request $request): JsonResponse + public function basic(DateRequest $request): JsonResponse { // parameters for boxes: - $start = (string)$request->get('start'); - $end = (string)$request->get('end'); - if ('' === $start || '' === $end) { - throw new FireflyException('Start and end are mandatory parameters.'); - } - $start = Carbon::createFromFormat('Y-m-d', $start); - $end = Carbon::createFromFormat('Y-m-d', $end); + $dates = $request->getAll(); + $start = $dates['start']; + $end = $dates['end']; + $code = $request->get('currency_code'); + // balance information: $balanceData = $this->getBalanceInformation($start, $end); $billData = $this->getBillInformation($start, $end); @@ -106,9 +104,15 @@ class SummaryController extends Controller $networthData = $this->getNetWorthInfo($start, $end); $total = array_merge($balanceData, $billData, $spentData, $networthData); - // TODO: liabilities with icon line-chart + // give new keys + $return = []; + foreach ($total as $entry) { + if (null === $code || (null !== $code && $code === $entry['currency_code'])) { + $return[$entry['key']] = $entry; + } + } - return response()->json($total); + return response()->json($return); } @@ -121,7 +125,6 @@ class SummaryController extends Controller * @param Carbon $end * * @return bool - * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ protected function notInDateRange(Carbon $date, Carbon $start, Carbon $end): bool // Validate a preference { @@ -137,25 +140,6 @@ class SummaryController extends Controller return $result; } - /** - * This method will scroll through the results of the spentInPeriodMc() array and return the correct info. - * - * @param array $spentInfo - * @param TransactionCurrency $currency - * - * @return float - */ - private function findInSpentArray(array $spentInfo, TransactionCurrency $currency): float - { - foreach ($spentInfo as $array) { - if ($array['currency_id'] === $currency->id) { - return $array['amount']; - } - } - - return 0.0; - } - /** * @param Carbon $start * @param Carbon $end @@ -170,36 +154,47 @@ class SummaryController extends Controller $sums = []; $return = []; - // collect income of user: - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setAllAssetAccounts()->setRange($start, $end) - ->setTypes([TransactionType::DEPOSIT]) - ->withOpposingAccount(); - $set = $collector->getTransactions(); - /** @var Transaction $transaction */ - foreach ($set as $transaction) { - $currencyId = (int)$transaction->transaction_currency_id; + // collect income of user using the new group collector. + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector + ->setRange($start, $end) + // set page to retrieve + ->setPage($this->parameters->get('page')) + // set types of transactions to return. + ->setTypes([TransactionType::DEPOSIT]); + + $set = $collector->getExtractedJournals(); + /** @var array $transactionJournal */ + foreach ($set as $transactionJournal) { + + $currencyId = (int)$transactionJournal['currency_id']; $incomes[$currencyId] = $incomes[$currencyId] ?? '0'; - $incomes[$currencyId] = bcadd($incomes[$currencyId], $transaction->transaction_amount); + $incomes[$currencyId] = bcadd($incomes[$currencyId], bcmul($transactionJournal['amount'], '-1')); $sums[$currencyId] = $sums[$currencyId] ?? '0'; - $sums[$currencyId] = bcadd($sums[$currencyId], $transaction->transaction_amount); + $sums[$currencyId] = bcadd($sums[$currencyId], bcmul($transactionJournal['amount'], '-1')); } - // collect expenses: - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setAllAssetAccounts()->setRange($start, $end) - ->setTypes([TransactionType::WITHDRAWAL]) - ->withOpposingAccount(); - $set = $collector->getTransactions(); - /** @var Transaction $transaction */ - foreach ($set as $transaction) { - $currencyId = (int)$transaction->transaction_currency_id; + // collect expenses of user using the new group collector. + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector + ->setRange($start, $end) + // set page to retrieve + ->setPage($this->parameters->get('page')) + // set types of transactions to return. + ->setTypes([TransactionType::WITHDRAWAL]); + + + $set = $collector->getExtractedJournals(); + + /** @var array $transactionJournal */ + foreach ($set as $transactionJournal) { + $currencyId = (int)$transactionJournal['currency_id']; $expenses[$currencyId] = $expenses[$currencyId] ?? '0'; - $expenses[$currencyId] = bcadd($expenses[$currencyId], $transaction->transaction_amount); + $expenses[$currencyId] = bcadd($expenses[$currencyId], $transactionJournal['amount']); $sums[$currencyId] = $sums[$currencyId] ?? '0'; - $sums[$currencyId] = bcadd($sums[$currencyId], $transaction->transaction_amount); + $sums[$currencyId] = bcadd($sums[$currencyId], $transactionJournal['amount']); } // format amounts: @@ -315,6 +310,7 @@ class SummaryController extends Controller * @param Carbon $end * * @return array + * @throws Exception */ private function getLeftToSpendInfo(Carbon $start, Carbon $end): array { @@ -354,6 +350,25 @@ class SummaryController extends Controller return $return; } + /** + * This method will scroll through the results of the spentInPeriodMc() array and return the correct info. + * + * @param array $spentInfo + * @param TransactionCurrency $currency + * + * @return float + */ + private function findInSpentArray(array $spentInfo, TransactionCurrency $currency): float + { + foreach ($spentInfo as $array) { + if ($array['currency_id'] === $currency->id) { + return $array['amount']; + } + } + + return 0.0; // @codeCoverageIgnore + } + /** * @param Carbon $start * @param Carbon $end @@ -389,7 +404,7 @@ class SummaryController extends Controller $netWorthSet = $netWorthHelper->getNetWorthByCurrency($filtered, $date); $return = []; - foreach ($netWorthSet as $index => $data) { + foreach ($netWorthSet as $data) { /** @var TransactionCurrency $currency */ $currency = $data['currency']; $amount = round($data['balance'], $currency->decimal_places); diff --git a/app/Api/V1/Controllers/TagController.php b/app/Api/V1/Controllers/TagController.php index 3b4f92db49..3246337835 100644 --- a/app/Api/V1/Controllers/TagController.php +++ b/app/Api/V1/Controllers/TagController.php @@ -24,20 +24,19 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Controllers; use Carbon\Carbon; +use FireflyIII\Api\V1\Requests\DateRequest; use FireflyIII\Api\V1\Requests\TagRequest; -use FireflyIII\Exceptions\FireflyException; -use FireflyIII\Helpers\Collector\TransactionCollectorInterface; -use FireflyIII\Helpers\Filter\InternalTransferFilter; +use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Models\Tag; -use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Tag\TagRepositoryInterface; use FireflyIII\Support\Http\Api\TransactionFilter; use FireflyIII\Transformers\TagTransformer; -use FireflyIII\Transformers\TransactionTransformer; +use FireflyIII\Transformers\TransactionGroupTransformer; use FireflyIII\User; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Illuminate\Pagination\LengthAwarePaginator; +use Illuminate\Support\Collection; use League\Fractal\Manager; use League\Fractal\Pagination\IlluminatePaginatorAdapter; use League\Fractal\Resource\Collection as FractalCollection; @@ -55,7 +54,9 @@ class TagController extends Controller private $repository; /** - * RuleController constructor. + * TagController constructor. + * + * @codeCoverageIgnore */ public function __construct() { @@ -74,52 +75,22 @@ class TagController extends Controller } /** - * @param Request $request + * @param DateRequest $request * * @return JsonResponse - * @throws FireflyException */ - public function cloud(Request $request): JsonResponse + public function cloud(DateRequest $request): JsonResponse { - // parameters for cloud: - $start = (string)$request->get('start'); - $end = (string)$request->get('end'); - if ('' === $start || '' === $end) { - throw new FireflyException('Start and end are mandatory parameters.'); - } - $start = Carbon::createFromFormat('Y-m-d', $start); - $end = Carbon::createFromFormat('Y-m-d', $end); + // parameters for boxes: + $dates = $request->getAll(); + $start = $dates['start']; + $end = $dates['end']; // get all tags: - $tags = $this->repository->get(); - $min = null; - $max = 0; - $return = [ - 'tags' => [], - ]; - /** @var Tag $tag */ - foreach ($tags as $tag) { - $earned = (float)$this->repository->earnedInPeriod($tag, $start, $end); - $spent = (float)$this->repository->spentInPeriod($tag, $start, $end); - $size = ($spent * -1) + $earned; - $min = $min ?? $size; - if ($size > 0) { - $max = $size > $max ? $size : $max; - $return['tags'][] = [ - 'tag' => $tag->tag, - 'id' => $tag->id, - 'size' => $size, - ]; - } - } - foreach ($return['tags'] as $index => $info) { - $return['tags'][$index]['relative'] = $return['tags'][$index]['size'] / $max; - } - $return['min'] = $min; - $return['max'] = $max; + $tags = $this->repository->get(); + $cloud = $this->getTagCloud($tags, $start, $end); - - return response()->json($return); + return response()->json($cloud); } /** @@ -128,6 +99,7 @@ class TagController extends Controller * @param Tag $tag * * @return JsonResponse + * @codeCoverageIgnore */ public function delete(Tag $tag): JsonResponse { @@ -141,7 +113,8 @@ class TagController extends Controller * * @param Request $request * - * @return JsonResponse] + * @return JsonResponse + * @codeCoverageIgnore */ public function index(Request $request): JsonResponse { @@ -178,9 +151,10 @@ class TagController extends Controller * List single resource. * * @param Request $request - * @param Tag $tag + * @param Tag $tag * * @return JsonResponse + * @codeCoverageIgnore */ public function show(Request $request, Tag $tag): JsonResponse { @@ -225,9 +199,10 @@ class TagController extends Controller * Show all transactions. * * @param Request $request - * @param Tag $tag + * @param Tag $tag * * @return JsonResponse + * @codeCoverageIgnore */ public function transactions(Request $request, Tag $tag): JsonResponse { @@ -242,28 +217,32 @@ class TagController extends Controller /** @var User $admin */ $admin = auth()->user(); - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setUser($admin); - $collector->withOpposingAccount()->withCategoryInformation()->withBudgetInformation(); - $collector->setAllAssetAccounts(); - $collector->setTag($tag); - if (\in_array(TransactionType::TRANSFER, $types, true)) { - $collector->removeFilter(InternalTransferFilter::class); - } + // use new group collector: + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector + ->setUser($admin) + // filter on tag. + ->setTag($tag) + // all info needed for the API: + ->withAPIInformation() + // set page size: + ->setLimit($pageSize) + // set page to retrieve + ->setPage($this->parameters->get('page')) + // set types of transactions to return. + ->setTypes($types); if (null !== $this->parameters->get('start') && null !== $this->parameters->get('end')) { $collector->setRange($this->parameters->get('start'), $this->parameters->get('end')); } - $collector->setLimit($pageSize)->setPage($this->parameters->get('page')); - $collector->setTypes($types); - $paginator = $collector->getPaginatedTransactions(); + $paginator = $collector->getPaginatedGroups(); $paginator->setPath(route('api.v1.transactions.index') . $this->buildParams()); $transactions = $paginator->getCollection(); - /** @var TransactionTransformer $transformer */ - $transformer = app(TransactionTransformer::class); + /** @var TransactionGroupTransformer $transformer */ + $transformer = app(TransactionGroupTransformer::class); $transformer->setParameters($this->parameters); $resource = new FractalCollection($transactions, $transformer, 'transactions'); @@ -276,7 +255,7 @@ class TagController extends Controller * Update a rule. * * @param TagRequest $request - * @param Tag $tag + * @param Tag $tag * * @return JsonResponse */ @@ -295,4 +274,54 @@ class TagController extends Controller return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); } + + /** + * @param Collection $tags + * @param Carbon $start + * @param Carbon $end + * @return array + */ + private function getTagCloud(Collection $tags, Carbon $start, Carbon $end): array + { + $min = null; + $max = 0; + $cloud = [ + 'tags' => [], + ]; + /** @var Tag $tag */ + foreach ($tags as $tag) { + $earned = (float)$this->repository->earnedInPeriod($tag, $start, $end); + $spent = (float)$this->repository->spentInPeriod($tag, $start, $end); + $size = ($spent * -1) + $earned; + $min = $min ?? $size; + if ($size > 0) { + $max = $size > $max ? $size : $max; + $cloud['tags'][] = [ + 'tag' => $tag->tag, + 'id' => $tag->id, + 'size' => $size, + ]; + } + } + $cloud = $this->analyseTagCloud($cloud, $min, $max); + + return $cloud; + } + + /** + * @param array $cloud + * @param float $min + * @param float $max + * @return array + */ + private function analyseTagCloud(array $cloud, float $min, float $max): array + { + foreach (array_keys($cloud['tags']) as $index) { + $cloud['tags'][$index]['relative'] = round($cloud['tags'][$index]['size'] / $max, 4); + } + $cloud['min'] = $min; + $cloud['max'] = $max; + + return $cloud; + } } diff --git a/app/Api/V1/Controllers/TransactionController.php b/app/Api/V1/Controllers/TransactionController.php index a00438536b..3d4d3aecf5 100644 --- a/app/Api/V1/Controllers/TransactionController.php +++ b/app/Api/V1/Controllers/TransactionController.php @@ -24,43 +24,47 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Controllers; -use FireflyIII\Api\V1\Requests\TransactionRequest; -use FireflyIII\Events\StoredTransactionJournal; -use FireflyIII\Events\UpdatedTransactionJournal; +use FireflyIII\Api\V1\Requests\TransactionStoreRequest; +use FireflyIII\Api\V1\Requests\TransactionUpdateRequest; +use FireflyIII\Events\StoredTransactionGroup; +use FireflyIII\Events\UpdatedTransactionGroup; use FireflyIII\Exceptions\FireflyException; -use FireflyIII\Helpers\Collector\TransactionCollectorInterface; -use FireflyIII\Helpers\Filter\InternalTransferFilter; -use FireflyIII\Helpers\Filter\NegativeAmountFilter; -use FireflyIII\Helpers\Filter\PositiveAmountFilter; -use FireflyIII\Models\Transaction; -use FireflyIII\Models\TransactionType; +use FireflyIII\Helpers\Collector\GroupCollectorInterface; +use FireflyIII\Models\TransactionGroup; +use FireflyIII\Models\TransactionJournal; use FireflyIII\Repositories\Journal\JournalRepositoryInterface; +use FireflyIII\Repositories\TransactionGroup\TransactionGroupRepositoryInterface; use FireflyIII\Support\Http\Api\TransactionFilter; use FireflyIII\Transformers\AttachmentTransformer; use FireflyIII\Transformers\PiggyBankEventTransformer; -use FireflyIII\Transformers\TransactionTransformer; +use FireflyIII\Transformers\TransactionGroupTransformer; use FireflyIII\User; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; -use Illuminate\Support\Collection; use League\Fractal\Manager; use League\Fractal\Pagination\IlluminatePaginatorAdapter; use League\Fractal\Resource\Collection as FractalCollection; +use League\Fractal\Resource\Item; use League\Fractal\Serializer\JsonApiSerializer; +use Log; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** * Class TransactionController - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class TransactionController extends Controller { use TransactionFilter; + /** @var TransactionGroupRepositoryInterface Group repository. */ + private $groupRepository; /** @var JournalRepositoryInterface The journal repository */ private $repository; /** * TransactionController constructor. + * + * @codeCoverageIgnore */ public function __construct() { @@ -70,9 +74,10 @@ class TransactionController extends Controller /** @var User $admin */ $admin = auth()->user(); - /** @var JournalRepositoryInterface repository */ - $this->repository = app(JournalRepositoryInterface::class); + $this->repository = app(JournalRepositoryInterface::class); + $this->groupRepository = app(TransactionGroupRepositoryInterface::class); $this->repository->setUser($admin); + $this->groupRepository->setUser($admin); return $next($request); } @@ -80,18 +85,19 @@ class TransactionController extends Controller } /** - * @param Request $request - * @param Transaction $transaction + * @param Request $request + * @param TransactionJournal $transactionJournal * * @return JsonResponse + * @codeCoverageIgnore */ - public function attachments(Request $request, Transaction $transaction): JsonResponse + public function attachments(Request $request, TransactionJournal $transactionJournal): JsonResponse { $manager = new Manager(); $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; $manager->setSerializer(new JsonApiSerializer($baseUrl)); - $attachments = $this->repository->getAttachmentsByTr($transaction); + $attachments = $this->repository->getAttachments($transactionJournal); /** @var AttachmentTransformer $transformer */ $transformer = app(AttachmentTransformer::class); @@ -106,14 +112,29 @@ class TransactionController extends Controller /** * Remove the specified resource from storage. * - * @param \FireflyIII\Models\Transaction $transaction + * @param TransactionGroup $transactionGroup * * @return JsonResponse + * @codeCoverageIgnore */ - public function delete(Transaction $transaction): JsonResponse + public function delete(TransactionGroup $transactionGroup): JsonResponse { - $journal = $transaction->transactionJournal; - $this->repository->destroy($journal); + $this->repository->destroyGroup($transactionGroup); + + return response()->json([], 204); + } + + /** + * Remove the specified resource from storage. + * + * @param TransactionJournal $transactionJournal + * + * @codeCoverageIgnore + * @return JsonResponse + */ + public function deleteJournal(TransactionJournal $transactionJournal): JsonResponse + { + $this->repository->destroyJournal($transactionJournal); return response()->json([], 204); } @@ -124,6 +145,7 @@ class TransactionController extends Controller * @param Request $request * * @return JsonResponse + * @codeCoverageIgnore */ public function index(Request $request): JsonResponse { @@ -138,27 +160,31 @@ class TransactionController extends Controller /** @var User $admin */ $admin = auth()->user(); - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setUser($admin); - $collector->withOpposingAccount()->withCategoryInformation()->withBudgetInformation(); - $collector->setAllAssetAccounts(); - if (\in_array(TransactionType::TRANSFER, $types, true)) { - $collector->removeFilter(InternalTransferFilter::class); - } + // use new group collector: + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector + ->setUser($admin) + // all info needed for the API: + ->withAPIInformation() + // set page size: + ->setLimit($pageSize) + // set page to retrieve + ->setPage($this->parameters->get('page')) + // set types of transactions to return. + ->setTypes($types); + if (null !== $this->parameters->get('start') && null !== $this->parameters->get('end')) { $collector->setRange($this->parameters->get('start'), $this->parameters->get('end')); } - $collector->setLimit($pageSize)->setPage($this->parameters->get('page')); - $collector->setTypes($types); - $paginator = $collector->getPaginatedTransactions(); + $paginator = $collector->getPaginatedGroups(); $paginator->setPath(route('api.v1.transactions.index') . $this->buildParams()); $transactions = $paginator->getCollection(); - /** @var TransactionTransformer $transformer */ - $transformer = app(TransactionTransformer::class); + /** @var TransactionGroupTransformer $transformer */ + $transformer = app(TransactionGroupTransformer::class); $transformer->setParameters($this->parameters); $resource = new FractalCollection($transactions, $transformer, 'transactions'); @@ -168,18 +194,19 @@ class TransactionController extends Controller } /** - * @param Request $request - * @param Transaction $transaction + * @param Request $request + * @param TransactionJournal $transactionJournal * * @return JsonResponse + * @codeCoverageIgnore */ - public function piggyBankEvents(Request $request, Transaction $transaction): JsonResponse + public function piggyBankEvents(Request $request, TransactionJournal $transactionJournal): JsonResponse { $manager = new Manager(); $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; $manager->setSerializer(new JsonApiSerializer($baseUrl)); - $events = $this->repository->getPiggyBankEventsbyTr($transaction); + $events = $this->repository->getPiggyBankEvents($transactionJournal); /** @var PiggyBankEventTransformer $transformer */ $transformer = app(PiggyBankEventTransformer::class); @@ -194,38 +221,38 @@ class TransactionController extends Controller /** * Show a single transaction. * - * @param Request $request - * @param Transaction $transaction + * @param Request $request + * @param TransactionGroup $transactionGroup * * @return JsonResponse + * @codeCoverageIgnore */ - public function show(Request $request, Transaction $transaction): JsonResponse + public function show(Request $request, TransactionGroup $transactionGroup): JsonResponse { $manager = new Manager(); $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; $manager->setSerializer(new JsonApiSerializer($baseUrl)); - // collect transactions using the journal collector - $collector = app(TransactionCollectorInterface::class); - $collector->setUser(auth()->user()); - $collector->withOpposingAccount()->withCategoryInformation()->withBudgetInformation(); - // filter on specific journals. - $collector->setJournals(new Collection([$transaction->transactionJournal])); + /** @var User $admin */ + $admin = auth()->user(); + // use new group collector: + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector + ->setUser($admin) + // filter on transaction group. + ->setTransactionGroup($transactionGroup) + // all info needed for the API: + ->withAPIInformation(); - // add filter to remove transactions: - $transactionType = $transaction->transactionJournal->transactionType->type; - if ($transactionType === TransactionType::WITHDRAWAL) { - $collector->addFilter(PositiveAmountFilter::class); + $selectedGroup = $collector->getGroups()->first(); + if (null === $selectedGroup) { + throw new NotFoundHttpException(); } - if (!($transactionType === TransactionType::WITHDRAWAL)) { - $collector->addFilter(NegativeAmountFilter::class); // @codeCoverageIgnore - } - - $transactions = $collector->getTransactions(); - /** @var TransactionTransformer $transformer */ - $transformer = app(TransactionTransformer::class); + /** @var TransactionGroupTransformer $transformer */ + $transformer = app(TransactionGroupTransformer::class); $transformer->setParameters($this->parameters); - $resource = new FractalCollection($transactions, $transformer, 'transactions'); + $resource = new Item($selectedGroup, $transformer, 'transactions'); return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); } @@ -233,48 +260,47 @@ class TransactionController extends Controller /** * Store a new transaction. * - * @param TransactionRequest $request + * @param TransactionStoreRequest $request * - * @param JournalRepositoryInterface $repository - * - * @throws FireflyException * @return JsonResponse + * @throws FireflyException */ - public function store(TransactionRequest $request, JournalRepositoryInterface $repository): JsonResponse + public function store(TransactionStoreRequest $request): JsonResponse { $data = $request->getAll(); $data['user'] = auth()->user()->id; - $journal = $repository->store($data); - event(new StoredTransactionJournal($journal)); + Log::channel('audit') + ->info('Store new transaction over API.', $data); + + $transactionGroup = $this->groupRepository->store($data); + + event(new StoredTransactionGroup($transactionGroup)); $manager = new Manager(); $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; $manager->setSerializer(new JsonApiSerializer($baseUrl)); - // collect transactions using the journal collector - $collector = app(TransactionCollectorInterface::class); - $collector->setUser(auth()->user()); - $collector->withOpposingAccount()->withCategoryInformation()->withBudgetInformation(); - // filter on specific journals. - $collector->setJournals(new Collection([$journal])); + /** @var User $admin */ + $admin = auth()->user(); + // use new group collector: + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector + ->setUser($admin) + // filter on transaction group. + ->setTransactionGroup($transactionGroup) + // all info needed for the API: + ->withAPIInformation(); - // add filter to remove transactions: - $transactionType = $journal->transactionType->type; - if ($transactionType === TransactionType::WITHDRAWAL) { - $collector->addFilter(PositiveAmountFilter::class); + $selectedGroup = $collector->getGroups()->first(); + if (null === $selectedGroup) { + throw new NotFoundHttpException(); // @codeCoverageIgnore } - if (!($transactionType === TransactionType::WITHDRAWAL)) { - $collector->addFilter(NegativeAmountFilter::class); - } - - $transactions = $collector->getTransactions(); - - /** @var TransactionTransformer $transformer */ - $transformer = app(TransactionTransformer::class); + /** @var TransactionGroupTransformer $transformer */ + $transformer = app(TransactionGroupTransformer::class); $transformer->setParameters($this->parameters); - - $resource = new FractalCollection($transactions, $transformer, 'transactions'); + $resource = new Item($selectedGroup, $transformer, 'transactions'); return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); } @@ -283,47 +309,42 @@ class TransactionController extends Controller /** * Update a transaction. * - * @param TransactionRequest $request - * @param JournalRepositoryInterface $repository - * @param Transaction $transaction + * @param TransactionUpdateRequest $request + * @param TransactionGroup $transactionGroup * * @return JsonResponse */ - public function update(TransactionRequest $request, JournalRepositoryInterface $repository, Transaction $transaction): JsonResponse + public function update(TransactionUpdateRequest $request, TransactionGroup $transactionGroup): JsonResponse { - $data = $request->getAll(); - $data['user'] = auth()->user()->id; - $journal = $repository->update($transaction->transactionJournal, $data); - $manager = new Manager(); - $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; + Log::debug('Now in update routine.'); + $data = $request->getAll(); + $transactionGroup = $this->groupRepository->update($transactionGroup, $data); + $manager = new Manager(); + $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; $manager->setSerializer(new JsonApiSerializer($baseUrl)); - event(new UpdatedTransactionJournal($journal)); + event(new UpdatedTransactionGroup($transactionGroup)); - // needs a lot of extra data to match the journal collector. Or just expand that one. - // collect transactions using the journal collector - $collector = app(TransactionCollectorInterface::class); - $collector->setUser(auth()->user()); - $collector->withOpposingAccount()->withCategoryInformation()->withBudgetInformation(); - // filter on specific journals. - $collector->setJournals(new Collection([$journal])); + /** @var User $admin */ + $admin = auth()->user(); + // use new group collector: + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector + ->setUser($admin) + // filter on transaction group. + ->setTransactionGroup($transactionGroup) + // all info needed for the API: + ->withAPIInformation(); - // add filter to remove transactions: - $transactionType = $journal->transactionType->type; - if ($transactionType === TransactionType::WITHDRAWAL) { - $collector->addFilter(PositiveAmountFilter::class); + $selectedGroup = $collector->getGroups()->first(); + if (null === $selectedGroup) { + throw new NotFoundHttpException(); // @codeCoverageIgnore } - if (!($transactionType === TransactionType::WITHDRAWAL)) { - $collector->addFilter(NegativeAmountFilter::class); - } - - $transactions = $collector->getTransactions(); - - /** @var TransactionTransformer $transformer */ - $transformer = app(TransactionTransformer::class); + /** @var TransactionGroupTransformer $transformer */ + $transformer = app(TransactionGroupTransformer::class); $transformer->setParameters($this->parameters); - - $resource = new FractalCollection($transactions, $transformer, 'transactions'); + $resource = new Item($selectedGroup, $transformer, 'transactions'); return response()->json($manager->createData($resource)->toArray())->header('Content-Type', 'application/vnd.api+json'); diff --git a/app/Api/V1/Controllers/TransactionLinkController.php b/app/Api/V1/Controllers/TransactionLinkController.php index 9a4dec565a..65dfe22a42 100644 --- a/app/Api/V1/Controllers/TransactionLinkController.php +++ b/app/Api/V1/Controllers/TransactionLinkController.php @@ -53,7 +53,8 @@ class TransactionLinkController extends Controller private $repository; /** - * JournalLinkController constructor. + * TransactionLinkController constructor. + * @codeCoverageIgnore */ public function __construct() { @@ -80,6 +81,7 @@ class TransactionLinkController extends Controller * @param TransactionJournalLink $link * * @return JsonResponse + * @codeCoverageIgnore */ public function delete(TransactionJournalLink $link): JsonResponse { @@ -93,7 +95,8 @@ class TransactionLinkController extends Controller * * @param Request $request * - * @return JsonResponse] + * @return JsonResponse + * @codeCoverageIgnore */ public function index(Request $request): JsonResponse { @@ -102,7 +105,7 @@ class TransactionLinkController extends Controller $baseUrl = $request->getSchemeAndHttpHost() . '/api/v1'; // read type from URI - $name = $request->get('name') ?? null; + $name = $request->get('name'); // types to get, page size: $pageSize = (int)app('preferences')->getForUser(auth()->user(), 'listPageSize', 50)->data; @@ -134,10 +137,11 @@ class TransactionLinkController extends Controller /** * List single resource. * - * @param Request $request + * @param Request $request * @param TransactionJournalLink $journalLink * * @return JsonResponse + * @codeCoverageIgnore */ public function show(Request $request, TransactionJournalLink $journalLink): JsonResponse { diff --git a/app/Api/V1/Controllers/UserController.php b/app/Api/V1/Controllers/UserController.php index f19961f8ca..7d1cf74239 100644 --- a/app/Api/V1/Controllers/UserController.php +++ b/app/Api/V1/Controllers/UserController.php @@ -52,6 +52,7 @@ class UserController extends Controller /** * UserController constructor. + * @codeCoverageIgnore */ public function __construct() { @@ -69,10 +70,11 @@ class UserController extends Controller /** * Remove the specified resource from storage. * - * @param \FireflyIII\User $user + * @param User $user * * @return JsonResponse * @throws FireflyException + * @codeCoverageIgnore */ public function delete(User $user): JsonResponse { @@ -92,6 +94,7 @@ class UserController extends Controller * @param Request $request * * @return JsonResponse + * @codeCoverageIgnore */ public function index(Request $request): JsonResponse { @@ -127,9 +130,10 @@ class UserController extends Controller * Show a single user. * * @param Request $request - * @param User $user + * @param User $user * * @return JsonResponse + * @codeCoverageIgnore */ public function show(Request $request, User $user): JsonResponse { @@ -180,7 +184,7 @@ class UserController extends Controller * Update a user. * * @param UserRequest $request - * @param User $user + * @param User $user * * @return JsonResponse */ diff --git a/app/Api/V1/Requests/AccountRequest.php b/app/Api/V1/Requests/AccountRequest.php deleted file mode 100644 index 3485c75eaa..0000000000 --- a/app/Api/V1/Requests/AccountRequest.php +++ /dev/null @@ -1,141 +0,0 @@ -. - */ - -declare(strict_types=1); - -namespace FireflyIII\Api\V1\Requests; - -use FireflyIII\Rules\IsBoolean; - -/** - * Class AccountRequest - */ -class AccountRequest extends Request -{ - - /** - * Authorize logged in users. - * - * @return bool - */ - public function authorize(): bool - { - // Only allow authenticated users - return auth()->check(); - } - - /** - * Get all data from the request. - * - * @return array - */ - public function getAll(): array - { - $active = true; - $includeNetWorth = true; - if (null !== $this->get('active')) { - $active = $this->boolean('active'); - } - if (null !== $this->get('include_net_worth')) { - $includeNetWorth = $this->boolean('include_net_worth'); - } - - $data = [ - 'name' => $this->string('name'), - 'active' => $active, - 'include_net_worth' => $includeNetWorth, - 'accountType' => $this->string('type'), - 'account_type_id' => null, - 'currency_id' => $this->integer('currency_id'), - 'currency_code' => $this->string('currency_code'), - 'virtualBalance' => $this->string('virtual_balance'), - 'iban' => $this->string('iban'), - 'BIC' => $this->string('bic'), - 'accountNumber' => $this->string('account_number'), - 'accountRole' => $this->string('account_role'), - 'openingBalance' => $this->string('opening_balance'), - 'openingBalanceDate' => $this->date('opening_balance_date'), - 'ccType' => $this->string('credit_card_type'), - 'ccMonthlyPaymentDate' => $this->string('monthly_payment_date'), - 'notes' => $this->string('notes'), - 'interest' => $this->string('interest'), - 'interest_period' => $this->string('interest_period'), - ]; - - if ('liability' === $data['accountType']) { - $data['openingBalance'] = bcmul($this->string('liability_amount'), '-1'); - $data['openingBalanceDate'] = $this->date('liability_start_date'); - $data['accountType'] = $this->string('liability_type'); - $data['account_type_id'] = null; - } - - return $data; - } - - /** - * The rules that the incoming request must be matched against. - * - * @return array - */ - public function rules(): array - { - $accountRoles = implode(',', config('firefly.accountRoles')); - $types = implode(',', array_keys(config('firefly.subTitlesByIdentifier'))); - $ccPaymentTypes = implode(',', array_keys(config('firefly.ccTypes'))); - $rules = [ - 'name' => 'required|min:1|uniqueAccountForUser', - 'type' => 'required|in:' . $types, - 'iban' => 'iban|nullable', - 'bic' => 'bic|nullable', - 'account_number' => 'between:1,255|nullable|uniqueAccountNumberForUser', - 'opening_balance' => 'numeric|required_with:opening_balance_date|nullable', - 'opening_balance_date' => 'date|required_with:opening_balance|nullable', - 'virtual_balance' => 'numeric|nullable', - 'currency_id' => 'numeric|exists:transaction_currencies,id', - 'currency_code' => 'min:3|max:3|exists:transaction_currencies,code', - 'active' => [new IsBoolean], - 'include_net_worth' => [new IsBoolean], - 'account_role' => 'in:' . $accountRoles . '|required_if:type,asset', - 'credit_card_type' => 'in:' . $ccPaymentTypes . '|required_if:account_role,ccAsset', - 'monthly_payment_date' => 'date' . '|required_if:account_role,ccAsset|required_if:credit_card_type,monthlyFull', - 'liability_type' => 'required_if:type,liability|in:loan,debt,mortgage', - 'liability_amount' => 'required_if:type,liability|min:0|numeric', - 'liability_start_date' => 'required_if:type,liability|date', - 'interest' => 'required_if:type,liability|between:0,100|numeric', - 'interest_period' => 'required_if:type,liability|in:daily,monthly,yearly', - 'notes' => 'min:0|max:65536', - ]; - switch ($this->method()) { - default: - break; - case 'PUT': - case 'PATCH': - $account = $this->route()->parameter('account'); - $rules['name'] .= ':' . $account->id; - $rules['account_number'] .= ':' . $account->id; - $rules['type'] = 'in:' . $types; - break; - } - - return $rules; - } -} diff --git a/app/Api/V1/Requests/AccountStoreRequest.php b/app/Api/V1/Requests/AccountStoreRequest.php new file mode 100644 index 0000000000..ae7b739142 --- /dev/null +++ b/app/Api/V1/Requests/AccountStoreRequest.php @@ -0,0 +1,83 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Requests; + +use FireflyIII\Rules\IsBoolean; + +/** + * Class AccountStoreRequest + * @codeCoverageIgnore + */ +class AccountStoreRequest extends Request +{ + + /** + * Authorize logged in users. + * + * @return bool + */ + public function authorize(): bool + { + // Only allow authenticated users + return auth()->check(); + } + + /** + * The rules that the incoming request must be matched against. + * + * @return array + */ + public function rules(): array + { + $accountRoles = implode(',', config('firefly.accountRoles')); + $types = implode(',', array_keys(config('firefly.subTitlesByIdentifier'))); + $ccPaymentTypes = implode(',', array_keys(config('firefly.ccTypes'))); + $rules = [ + 'name' => 'required|min:1|uniqueAccountForUser', + 'type' => 'required|' . sprintf('in:%s', $types), + 'iban' => 'iban|nullable', + 'bic' => 'bic|nullable', + 'account_number' => 'between:1,255|nullable|uniqueAccountNumberForUser', + 'opening_balance' => 'numeric|required_with:opening_balance_date|nullable', + 'opening_balance_date' => 'date|required_with:opening_balance|nullable', + 'virtual_balance' => 'numeric|nullable', + 'currency_id' => 'numeric|exists:transaction_currencies,id', + 'currency_code' => 'min:3|max:3|exists:transaction_currencies,code', + 'active' => [new IsBoolean], + 'include_net_worth' => [new IsBoolean], + 'account_role' => sprintf('in:%s|required_if:type,asset', $accountRoles), + 'credit_card_type' => sprintf('in:%s|required_if:account_role,ccAsset', $ccPaymentTypes), + 'monthly_payment_date' => 'date' . '|required_if:account_role,ccAsset|required_if:credit_card_type,monthlyFull', + 'liability_type' => 'required_if:type,liability|in:loan,debt,mortgage', + 'liability_amount' => 'required_if:type,liability|min:0|numeric', + 'liability_start_date' => 'required_if:type,liability|date', + 'interest' => 'required_if:type,liability|between:0,100|numeric', + 'interest_period' => 'required_if:type,liability|in:daily,monthly,yearly', + 'notes' => 'min:0|max:65536', + ]; + + return $rules; + } +} diff --git a/app/Api/V1/Requests/AccountUpdateRequest.php b/app/Api/V1/Requests/AccountUpdateRequest.php new file mode 100644 index 0000000000..d8b9d57149 --- /dev/null +++ b/app/Api/V1/Requests/AccountUpdateRequest.php @@ -0,0 +1,84 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Requests; + +use FireflyIII\Rules\IsBoolean; + +/** + * Class AccountUpdateRequest + * @codeCoverageIgnore + */ +class AccountUpdateRequest extends Request +{ + + /** + * Authorize logged in users. + * + * @return bool + */ + public function authorize(): bool + { + // Only allow authenticated users + return auth()->check(); + } + + /** + * The rules that the incoming request must be matched against. + * + * @return array + */ + public function rules(): array + { + $account = $this->route()->parameter('account'); + $accountRoles = implode(',', config('firefly.accountRoles')); + $types = implode(',', array_keys(config('firefly.subTitlesByIdentifier'))); + $ccPaymentTypes = implode(',', array_keys(config('firefly.ccTypes'))); + $rules = [ + 'name' => sprintf('required|min:1|uniqueAccountForUser:%d', $account->id), + 'type' => sprintf('in:%s', $types), + 'iban' => 'iban|nullable', + 'bic' => 'bic|nullable', + 'account_number' => sprintf('between:1,255|nullable|uniqueAccountNumberForUser:%d', $account->id), + 'opening_balance' => 'numeric|required_with:opening_balance_date|nullable', + 'opening_balance_date' => 'date|required_with:opening_balance|nullable', + 'virtual_balance' => 'numeric|nullable', + 'currency_id' => 'numeric|exists:transaction_currencies,id', + 'currency_code' => 'min:3|max:3|exists:transaction_currencies,code', + 'active' => [new IsBoolean], + 'include_net_worth' => [new IsBoolean], + 'account_role' => sprintf('in:%s|required_if:type,asset', $accountRoles), + 'credit_card_type' => sprintf('in:%s|required_if:account_role,ccAsset', $ccPaymentTypes), + 'monthly_payment_date' => 'date' . '|required_if:account_role,ccAsset|required_if:credit_card_type,monthlyFull', + 'liability_type' => 'required_if:type,liability|in:loan,debt,mortgage', + 'liability_amount' => 'required_if:type,liability|min:0|numeric', + 'liability_start_date' => 'required_if:type,liability|date', + 'interest' => 'required_if:type,liability|between:0,100|numeric', + 'interest_period' => 'required_if:type,liability|in:daily,monthly,yearly', + 'notes' => 'min:0|max:65536', + ]; + + return $rules; + } +} diff --git a/app/Api/V1/Requests/AttachmentRequest.php b/app/Api/V1/Requests/AttachmentRequest.php index a5f51fe9e7..d571592c0f 100644 --- a/app/Api/V1/Requests/AttachmentRequest.php +++ b/app/Api/V1/Requests/AttachmentRequest.php @@ -25,12 +25,13 @@ namespace FireflyIII\Api\V1\Requests; use FireflyIII\Models\Bill; use FireflyIII\Models\ImportJob; -use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionJournal; use FireflyIII\Rules\IsValidAttachmentModel; /** * Class AttachmentRequest + * @codeCoverageIgnore + * TODO AFTER 4.8.0: split this into two request classes. */ class AttachmentRequest extends Request { @@ -69,12 +70,12 @@ class AttachmentRequest extends Request public function rules(): array { $models = implode( - ',', [ - str_replace('FireflyIII\\Models\\', '', Bill::class), - str_replace('FireflyIII\\Models\\', '', ImportJob::class), - str_replace('FireflyIII\\Models\\', '', TransactionJournal::class), - str_replace('FireflyIII\\Models\\', '', Transaction::class), - ] + ',', + [ + str_replace('FireflyIII\\Models\\', '', Bill::class), + str_replace('FireflyIII\\Models\\', '', ImportJob::class), + str_replace('FireflyIII\\Models\\', '', TransactionJournal::class), + ] ); $model = $this->string('model'); $rules = [ diff --git a/app/Api/V1/Requests/AvailableBudgetRequest.php b/app/Api/V1/Requests/AvailableBudgetRequest.php index a68999993c..d8de313bc0 100644 --- a/app/Api/V1/Requests/AvailableBudgetRequest.php +++ b/app/Api/V1/Requests/AvailableBudgetRequest.php @@ -25,6 +25,8 @@ namespace FireflyIII\Api\V1\Requests; /** * Class AvailableBudgetRequest + * + * @codeCoverageIgnore */ class AvailableBudgetRequest extends Request { diff --git a/app/Api/V1/Requests/BillRequest.php b/app/Api/V1/Requests/BillRequest.php index 58a7d6bbfe..a8c86c00f3 100644 --- a/app/Api/V1/Requests/BillRequest.php +++ b/app/Api/V1/Requests/BillRequest.php @@ -29,6 +29,10 @@ use Illuminate\Validation\Validator; /** * Class BillRequest + * + * TODO AFTER 4.8.0: split this into two request classes. + * + * @codeCoverageIgnore */ class BillRequest extends Request { @@ -76,6 +80,7 @@ class BillRequest extends Request * The rules that the incoming request must be matched against. * * @return array + * */ public function rules(): array { @@ -88,7 +93,6 @@ class BillRequest extends Request 'date' => 'required|date', 'repeat_freq' => 'required|in:weekly,monthly,quarterly,half-year,yearly', 'skip' => 'between:0,31', - 'automatch' => [new IsBoolean], 'active' => [new IsBoolean], 'notes' => 'between:1,65536', ]; @@ -108,14 +112,14 @@ class BillRequest extends Request /** * Configure the validator instance. * - * @param Validator $validator + * @param Validator $validator * * @return void */ public function withValidator(Validator $validator): void { $validator->after( - function (Validator $validator) { + static function (Validator $validator) { $data = $validator->getData(); $min = (float)($data['amount_min'] ?? 0); $max = (float)($data['amount_max'] ?? 0); diff --git a/app/Api/V1/Requests/BudgetLimitRequest.php b/app/Api/V1/Requests/BudgetLimitRequest.php index 9aff7066af..b69e52d0e4 100644 --- a/app/Api/V1/Requests/BudgetLimitRequest.php +++ b/app/Api/V1/Requests/BudgetLimitRequest.php @@ -23,9 +23,11 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Requests; - /** * Class BudgetLimitRequest + * + * @codeCoverageIgnore + * TODO AFTER 4.8.0: split this into two request classes. */ class BudgetLimitRequest extends Request { diff --git a/app/Api/V1/Requests/BudgetRequest.php b/app/Api/V1/Requests/BudgetRequest.php index 2a42362e7a..15ac238b3e 100644 --- a/app/Api/V1/Requests/BudgetRequest.php +++ b/app/Api/V1/Requests/BudgetRequest.php @@ -28,6 +28,8 @@ use FireflyIII\Rules\IsBoolean; /** * Class BudgetRequest + * @codeCoverageIgnore + * TODO AFTER 4.8.0: split this into two request classes. */ class BudgetRequest extends Request { diff --git a/app/Api/V1/Requests/CategoryRequest.php b/app/Api/V1/Requests/CategoryRequest.php index 369304ed3c..85e82ed124 100644 --- a/app/Api/V1/Requests/CategoryRequest.php +++ b/app/Api/V1/Requests/CategoryRequest.php @@ -27,6 +27,8 @@ use FireflyIII\Models\Category; /** * Class CategoryRequest + * @codeCoverageIgnore + * TODO AFTER 4.8.0: split this into two request classes. */ class CategoryRequest extends Request { diff --git a/app/Api/V1/Requests/ConfigurationRequest.php b/app/Api/V1/Requests/ConfigurationRequest.php index 09fb846894..1feeaf1789 100644 --- a/app/Api/V1/Requests/ConfigurationRequest.php +++ b/app/Api/V1/Requests/ConfigurationRequest.php @@ -28,6 +28,7 @@ use FireflyIII\Rules\IsBoolean; /** * Class ConfigurationRequest + * @codeCoverageIgnore */ class ConfigurationRequest extends Request { diff --git a/app/Api/V1/Requests/CurrencyRequest.php b/app/Api/V1/Requests/CurrencyRequest.php index 5a504cb2bf..7db6f10265 100644 --- a/app/Api/V1/Requests/CurrencyRequest.php +++ b/app/Api/V1/Requests/CurrencyRequest.php @@ -28,6 +28,8 @@ use FireflyIII\Rules\IsBoolean; /** * Class CurrencyRequest + * @codeCoverageIgnore + * TODO AFTER 4.8.0: split this into two request classes. */ class CurrencyRequest extends Request { diff --git a/app/Http/Requests/ReconciliationUpdateRequest.php b/app/Api/V1/Requests/DateRequest.php similarity index 55% rename from app/Http/Requests/ReconciliationUpdateRequest.php rename to app/Api/V1/Requests/DateRequest.php index 0f71e7a184..30ac436731 100644 --- a/app/Http/Requests/ReconciliationUpdateRequest.php +++ b/app/Api/V1/Requests/DateRequest.php @@ -1,7 +1,7 @@ . */ -declare(strict_types=1); -namespace FireflyIII\Http\Requests; +namespace FireflyIII\Api\V1\Requests; + /** - * Class ReconciliationUpdateRequest. + * Request class for end points that require date parameters. + * + * Class DateRequest */ -class ReconciliationUpdateRequest extends Request +class DateRequest extends Request { /** - * Verify the request. + * Authorize logged in users. * * @return bool */ public function authorize(): bool { - // Only allow logged in users + // Only allow authenticated users return auth()->check(); } /** - * Returns and validates the data required to update a reconciliation. + * Get all data from the request. * * @return array */ - public function getJournalData(): array + public function getAll(): array { - $data = [ - 'tags' => explode(',', $this->string('tags')), - 'amount' => $this->string('amount'), - 'category' => $this->string('category'), + return [ + 'start' => $this->date('start'), + 'end' => $this->date('end'), ]; - - return $data; } /** - * Rules for this request. + * The rules that the incoming request must be matched against. * * @return array */ public function rules(): array { - $rules = [ - 'amount' => 'numeric|required', - 'category' => 'between:1,255|nullable', + return [ + 'start' => 'required|date', + 'end' => 'required|date|after:start', ]; - - return $rules; } -} +} \ No newline at end of file diff --git a/app/Api/V1/Requests/LinkTypeRequest.php b/app/Api/V1/Requests/LinkTypeRequest.php index cacc793b42..598db93461 100644 --- a/app/Api/V1/Requests/LinkTypeRequest.php +++ b/app/Api/V1/Requests/LinkTypeRequest.php @@ -29,6 +29,8 @@ use Illuminate\Validation\Rule; /** * * Class LinkTypeRequest + * @codeCoverageIgnore + * TODO AFTER 4.8.0: split this into two request classes. */ class LinkTypeRequest extends Request { diff --git a/app/Api/V1/Requests/PiggyBankRequest.php b/app/Api/V1/Requests/PiggyBankRequest.php index 427915cc6d..1ab6a9b396 100644 --- a/app/Api/V1/Requests/PiggyBankRequest.php +++ b/app/Api/V1/Requests/PiggyBankRequest.php @@ -25,10 +25,13 @@ namespace FireflyIII\Api\V1\Requests; use FireflyIII\Models\PiggyBank; use FireflyIII\Rules\IsAssetAccountId; +use FireflyIII\Rules\ZeroOrMore; /** * * Class PiggyBankRequest + * @codeCoverageIgnore + * TODO AFTER 4.8.0: split this into two request classes. */ class PiggyBankRequest extends Request { @@ -75,7 +78,7 @@ class PiggyBankRequest extends Request 'name' => 'required|between:1,255|uniquePiggyBankForUser', 'account_id' => ['required', 'belongsToUser:accounts', new IsAssetAccountId], 'target_amount' => 'required|numeric|more:0', - 'current_amount' => 'numeric|more:0|lte:target_amount', + 'current_amount' => ['numeric', new ZeroOrMore, 'lte:target_amount'], 'start_date' => 'date|nullable', 'target_date' => 'date|nullable|after:start_date', 'notes' => 'max:65000', diff --git a/app/Api/V1/Requests/PreferenceRequest.php b/app/Api/V1/Requests/PreferenceRequest.php index 4c103370a2..7fe184e054 100644 --- a/app/Api/V1/Requests/PreferenceRequest.php +++ b/app/Api/V1/Requests/PreferenceRequest.php @@ -26,6 +26,7 @@ namespace FireflyIII\Api\V1\Requests; /** * * Class PreferenceRequest + * @codeCoverageIgnore */ class PreferenceRequest extends Request { diff --git a/app/Api/V1/Requests/RecurrenceRequest.php b/app/Api/V1/Requests/RecurrenceRequest.php deleted file mode 100644 index 99030ebb22..0000000000 --- a/app/Api/V1/Requests/RecurrenceRequest.php +++ /dev/null @@ -1,214 +0,0 @@ -. - */ - -declare(strict_types=1); - -namespace FireflyIII\Api\V1\Requests; - -use Carbon\Carbon; -use FireflyIII\Rules\BelongsUser; -use FireflyIII\Rules\IsBoolean; -use FireflyIII\Validation\RecurrenceValidation; -use FireflyIII\Validation\TransactionValidation; -use Illuminate\Validation\Validator; - -/** - * Class RecurrenceRequest - */ -class RecurrenceRequest extends Request -{ - use RecurrenceValidation, TransactionValidation; - - /** - * Authorize logged in users. - * - * @return bool - */ - public function authorize(): bool - { - // Only allow authenticated users - return auth()->check(); - } - - /** - * Get all data from the request. - * - * @return array - */ - public function getAll(): array - { - $active = true; - $applyRules = true; - if (null !== $this->get('active')) { - $active = $this->boolean('active'); - } - if (null !== $this->get('apply_rules')) { - $applyRules = $this->boolean('apply_rules'); - } - $return = [ - 'recurrence' => [ - 'type' => $this->string('type'), - 'title' => $this->string('title'), - 'description' => $this->string('description'), - 'first_date' => $this->date('first_date'), - 'repeat_until' => $this->date('repeat_until'), - 'repetitions' => $this->integer('nr_of_repetitions'), - 'apply_rules' => $applyRules, - 'active' => $active, - ], - 'meta' => [ - 'piggy_bank_id' => $this->integer('piggy_bank_id'), - 'piggy_bank_name' => $this->string('piggy_bank_name'), - 'tags' => explode(',', $this->string('tags')), - ], - 'transactions' => $this->getTransactionData(), - 'repetitions' => $this->getRepetitionData(), - ]; - - return $return; - } - - /** - * The rules that the incoming request must be matched against. - * - * @return array - */ - public function rules(): array - { - $today = Carbon::now()->addDay(); - - return [ - 'type' => 'required|in:withdrawal,transfer,deposit', - 'title' => 'required|between:1,255|uniqueObjectForUser:recurrences,title', - 'description' => 'between:1,65000', - 'first_date' => sprintf('required|date|after:%s', $today->format('Y-m-d')), - 'apply_rules' => [new IsBoolean], - 'active' => [new IsBoolean], - 'repeat_until' => sprintf('date|after:%s', $today->format('Y-m-d')), - 'nr_of_repetitions' => 'numeric|between:1,31', - 'tags' => 'between:1,64000', - 'piggy_bank_id' => 'numeric', - 'repetitions.*.type' => 'required|in:daily,weekly,ndom,monthly,yearly', - 'repetitions.*.moment' => 'between:0,10', - 'repetitions.*.skip' => 'required|numeric|between:0,31', - 'repetitions.*.weekend' => 'required|numeric|min:1|max:4', - 'transactions.*.description' => 'required|between:1,255', - 'transactions.*.amount' => 'required|numeric|more:0', - 'transactions.*.foreign_amount' => 'numeric|more:0', - 'transactions.*.currency_id' => 'numeric|exists:transaction_currencies,id', - 'transactions.*.currency_code' => 'min:3|max:3|exists:transaction_currencies,code', - 'transactions.*.foreign_currency_id' => 'numeric|exists:transaction_currencies,id', - 'transactions.*.foreign_currency_code' => 'min:3|max:3|exists:transaction_currencies,code', - 'transactions.*.budget_id' => ['mustExist:budgets,id', new BelongsUser], - 'transactions.*.category_name' => 'between:1,255|nullable', - 'transactions.*.source_id' => ['numeric', 'nullable', new BelongsUser], - 'transactions.*.source_name' => 'between:1,255|nullable', - 'transactions.*.destination_id' => ['numeric', 'nullable', new BelongsUser], - 'transactions.*.destination_name' => 'between:1,255|nullable', - - - ]; - } - - /** - * Configure the validator instance. - * - * @param Validator $validator - * - * @return void - */ - public function withValidator(Validator $validator): void - { - $validator->after( - function (Validator $validator) { - $this->validateOneTransaction($validator); - $this->validateOneRepetition($validator); - $this->validateRecurrenceRepetition($validator); - $this->validateRepetitionMoment($validator); - $this->validateForeignCurrencyInformation($validator); - $this->validateAccountInformation($validator); - } - ); - } - - - /** - * Returns the repetition data as it is found in the submitted data. - * - * @return array - */ - private function getRepetitionData(): array - { - $return = []; - // repetition data: - /** @var array $repetitions */ - $repetitions = $this->get('repetitions'); - /** @var array $repetition */ - foreach ($repetitions as $repetition) { - $return[] = [ - 'type' => $repetition['type'], - 'moment' => $repetition['moment'], - 'skip' => (int)$repetition['skip'], - 'weekend' => (int)$repetition['weekend'], - ]; - } - - return $return; - } - - /** - * Returns the transaction data as it is found in the submitted data. It's a complex method according to code - * standards but it just has a lot of ??-statements because of the fields that may or may not exist. - * - * @return array - * @SuppressWarnings(PHPMD.NPathComplexity) - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - */ - private function getTransactionData(): array - { - $return = []; - // transaction data: - /** @var array $transactions */ - $transactions = $this->get('transactions'); - /** @var array $transaction */ - foreach ($transactions as $transaction) { - $return[] = [ - 'amount' => $transaction['amount'], - 'currency_id' => isset($transaction['currency_id']) ? (int)$transaction['currency_id'] : null, - 'currency_code' => $transaction['currency_code'] ?? null, - 'foreign_amount' => $transaction['foreign_amount'] ?? null, - 'foreign_currency_id' => isset($transaction['foreign_currency_id']) ? (int)$transaction['foreign_currency_id'] : null, - 'foreign_currency_code' => $transaction['foreign_currency_code'] ?? null, - 'budget_id' => isset($transaction['budget_id']) ? (int)$transaction['budget_id'] : null, - 'budget_name' => $transaction['budget_name'] ?? null, - 'category_id' => isset($transaction['category_id']) ? (int)$transaction['category_id'] : null, - 'category_name' => $transaction['category_name'] ?? null, - 'source_id' => isset($transaction['source_id']) ? (int)$transaction['source_id'] : null, - 'source_name' => isset($transaction['source_name']) ? (string)$transaction['source_name'] : null, - 'destination_id' => isset($transaction['destination_id']) ? (int)$transaction['destination_id'] : null, - 'destination_name' => isset($transaction['destination_name']) ? (string)$transaction['destination_name'] : null, - 'description' => $transaction['description'], - ]; - } - - return $return; - } -} diff --git a/app/Api/V1/Requests/RecurrenceStoreRequest.php b/app/Api/V1/Requests/RecurrenceStoreRequest.php new file mode 100644 index 0000000000..e0da572014 --- /dev/null +++ b/app/Api/V1/Requests/RecurrenceStoreRequest.php @@ -0,0 +1,78 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Requests; + +use FireflyIII\Validation\RecurrenceValidation; +use FireflyIII\Validation\TransactionValidation; +use Illuminate\Validation\Validator; + +/** + * Class RecurrenceStoreRequest + */ +class RecurrenceStoreRequest extends Request +{ + use RecurrenceValidation, TransactionValidation; + + /** + * Authorize logged in users. + * + * @return bool + */ + public function authorize(): bool + { + // Only allow authenticated users + return auth()->check(); + } + + /** + * The rules that the incoming request must be matched against. + * + * @return array + */ + public function rules(): array + { + return $this->rulesRecurrence(); + } + + /** + * Configure the validator instance. + * + * @param Validator $validator + * + * @return void + */ + public function withValidator(Validator $validator): void + { + $validator->after( + function (Validator $validator) { + $this->validateOneRecurrenceTransaction($validator); + $this->validateOneRepetition($validator); + $this->validateRecurrenceRepetition($validator); + $this->validateRepetitionMoment($validator); + $this->validateForeignCurrencyInformation($validator); + $this->validateAccountInformation($validator); + } + ); + } +} diff --git a/app/Api/V1/Requests/RecurrenceUpdateRequest.php b/app/Api/V1/Requests/RecurrenceUpdateRequest.php new file mode 100644 index 0000000000..f487c8c0d4 --- /dev/null +++ b/app/Api/V1/Requests/RecurrenceUpdateRequest.php @@ -0,0 +1,78 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Requests; + +use FireflyIII\Validation\RecurrenceValidation; +use FireflyIII\Validation\TransactionValidation; +use Illuminate\Validation\Validator; + +/** + * Class RecurrenceUpdateRequest + */ +class RecurrenceUpdateRequest extends Request +{ + use RecurrenceValidation, TransactionValidation; + + /** + * Authorize logged in users. + * + * @return bool + */ + public function authorize(): bool + { + // Only allow authenticated users + return auth()->check(); + } + + /** + * The rules that the incoming request must be matched against. + * + * @return array + */ + public function rules(): array + { + return $this->rulesRecurrence(); + } + + /** + * Configure the validator instance. + * + * @param Validator $validator + * + * @return void + */ + public function withValidator(Validator $validator): void + { + $validator->after( + function (Validator $validator) { + $this->validateOneTransaction($validator); + $this->validateOneRepetition($validator); + $this->validateRecurrenceRepetition($validator); + $this->validateRepetitionMoment($validator); + $this->validateForeignCurrencyInformation($validator); + $this->validateAccountInformation($validator); + } + ); + } +} diff --git a/app/Api/V1/Requests/Request.php b/app/Api/V1/Requests/Request.php index 416818471c..2173e08560 100644 --- a/app/Api/V1/Requests/Request.php +++ b/app/Api/V1/Requests/Request.php @@ -24,16 +24,205 @@ declare(strict_types=1); namespace FireflyIII\Api\V1\Requests; +use Carbon\Carbon; use FireflyIII\Http\Requests\Request as FireflyIIIRequest; +use FireflyIII\Rules\BelongsUser; +use FireflyIII\Rules\IsBoolean; /** * Class Request. * * Technically speaking this class does not have to be extended like this but who knows what the future brings. * - * @SuppressWarnings(PHPMD.NumberOfChildren) */ class Request extends FireflyIIIRequest { + /** + * @return array + */ + public function getAllAccountData(): array + { + $active = true; + $includeNetWorth = true; + if (null !== $this->get('active')) { + $active = $this->boolean('active'); + } + if (null !== $this->get('include_net_worth')) { + $includeNetWorth = $this->boolean('include_net_worth'); + } + + $data = [ + 'name' => $this->string('name'), + 'active' => $active, + 'include_net_worth' => $includeNetWorth, + 'account_type' => $this->string('type'), + 'account_type_id' => null, + 'currency_id' => $this->integer('currency_id'), + 'currency_code' => $this->string('currency_code'), + 'virtual_balance' => $this->string('virtual_balance'), + 'iban' => $this->string('iban'), + 'BIC' => $this->string('bic'), + 'account_number' => $this->string('account_number'), + 'account_role' => $this->string('account_role'), + 'opening_balance' => $this->string('opening_balance'), + 'opening_balance_date' => $this->date('opening_balance_date'), + 'cc_type' => $this->string('credit_card_type'), + 'cc_Monthly_payment_date' => $this->string('monthly_payment_date'), + 'notes' => $this->string('notes'), + 'interest' => $this->string('interest'), + 'interest_period' => $this->string('interest_period'), + ]; + + if ('liability' === $data['account_type']) { + $data['opening_balance'] = bcmul($this->string('liability_amount'), '-1'); + $data['opening_balance_date'] = $this->date('liability_start_date'); + $data['account_type'] = $this->string('liability_type'); + $data['account_type_id'] = null; + } + + return $data; + } + + /** + * Get all data from the request. + * + * @return array + */ + public function getAllRecurrenceData(): array + { + $active = true; + $applyRules = true; + if (null !== $this->get('active')) { + $active = $this->boolean('active'); + } + if (null !== $this->get('apply_rules')) { + $applyRules = $this->boolean('apply_rules'); + } + $return = [ + 'recurrence' => [ + 'type' => $this->string('type'), + 'title' => $this->string('title'), + 'description' => $this->string('description'), + 'first_date' => $this->date('first_date'), + 'repeat_until' => $this->date('repeat_until'), + 'repetitions' => $this->integer('nr_of_repetitions'), + 'apply_rules' => $applyRules, + 'active' => $active, + ], + 'meta' => [ + 'piggy_bank_id' => $this->integer('piggy_bank_id'), + 'piggy_bank_name' => $this->string('piggy_bank_name'), + 'tags' => explode(',', $this->string('tags')), + ], + 'transactions' => $this->getRecurrenceTransactionData(), + 'repetitions' => $this->getRecurrenceRepetitionData(), + ]; + + return $return; + } + + /** + * The rules that the incoming request must be matched against. + * + * @return array + */ + protected function rulesRecurrence(): array + { + $today = Carbon::now()->addDay(); + + return [ + 'type' => 'required|in:withdrawal,transfer,deposit', + 'title' => 'required|between:1,255|uniqueObjectForUser:recurrences,title', + 'description' => 'between:1,65000', + 'first_date' => sprintf('required|date|after:%s', $today->format('Y-m-d')), + 'apply_rules' => [new IsBoolean], + 'active' => [new IsBoolean], + 'repeat_until' => sprintf('date|after:%s', $today->format('Y-m-d')), + 'nr_of_repetitions' => 'numeric|between:1,31', + 'tags' => 'between:1,64000', + 'piggy_bank_id' => 'numeric', + 'repetitions.*.type' => 'required|in:daily,weekly,ndom,monthly,yearly', + 'repetitions.*.moment' => 'between:0,10', + 'repetitions.*.skip' => 'required|numeric|between:0,31', + 'repetitions.*.weekend' => 'required|numeric|min:1|max:4', + 'transactions.*.description' => 'required|between:1,255', + 'transactions.*.amount' => 'required|numeric|more:0', + 'transactions.*.foreign_amount' => 'numeric|more:0', + 'transactions.*.currency_id' => 'numeric|exists:transaction_currencies,id', + 'transactions.*.currency_code' => 'min:3|max:3|exists:transaction_currencies,code', + 'transactions.*.foreign_currency_id' => 'numeric|exists:transaction_currencies,id', + 'transactions.*.foreign_currency_code' => 'min:3|max:3|exists:transaction_currencies,code', + 'transactions.*.budget_id' => ['mustExist:budgets,id', new BelongsUser], + 'transactions.*.category_name' => 'between:1,255|nullable', + 'transactions.*.source_id' => ['numeric', 'nullable', new BelongsUser], + 'transactions.*.source_name' => 'between:1,255|nullable', + 'transactions.*.destination_id' => ['numeric', 'nullable', new BelongsUser], + 'transactions.*.destination_name' => 'between:1,255|nullable', + + + ]; + } + + /** + * Returns the repetition data as it is found in the submitted data. + * + * @return array + */ + protected function getRecurrenceRepetitionData(): array + { + $return = []; + // repetition data: + /** @var array $repetitions */ + $repetitions = $this->get('repetitions'); + /** @var array $repetition */ + foreach ($repetitions as $repetition) { + $return[] = [ + 'type' => $repetition['type'], + 'moment' => $repetition['moment'], + 'skip' => (int)$repetition['skip'], + 'weekend' => (int)$repetition['weekend'], + ]; + } + + return $return; + } + + + /** + * Returns the transaction data as it is found in the submitted data. It's a complex method according to code + * standards but it just has a lot of ??-statements because of the fields that may or may not exist. + * + * @return array + */ + protected function getRecurrenceTransactionData(): array + { + $return = []; + // transaction data: + /** @var array $transactions */ + $transactions = $this->get('transactions'); + /** @var array $transaction */ + foreach ($transactions as $transaction) { + $return[] = [ + 'amount' => $transaction['amount'], + 'currency_id' => isset($transaction['currency_id']) ? (int)$transaction['currency_id'] : null, + 'currency_code' => $transaction['currency_code'] ?? null, + 'foreign_amount' => $transaction['foreign_amount'] ?? null, + 'foreign_currency_id' => isset($transaction['foreign_currency_id']) ? (int)$transaction['foreign_currency_id'] : null, + 'foreign_currency_code' => $transaction['foreign_currency_code'] ?? null, + 'budget_id' => isset($transaction['budget_id']) ? (int)$transaction['budget_id'] : null, + 'budget_name' => $transaction['budget_name'] ?? null, + 'category_id' => isset($transaction['category_id']) ? (int)$transaction['category_id'] : null, + 'category_name' => $transaction['category_name'] ?? null, + 'source_id' => isset($transaction['source_id']) ? (int)$transaction['source_id'] : null, + 'source_name' => isset($transaction['source_name']) ? (string)$transaction['source_name'] : null, + 'destination_id' => isset($transaction['destination_id']) ? (int)$transaction['destination_id'] : null, + 'destination_name' => isset($transaction['destination_name']) ? (string)$transaction['destination_name'] : null, + 'description' => $transaction['description'], + 'type' => $this->string('type'), + ]; + } + + return $return; + } } diff --git a/app/Api/V1/Requests/RuleGroupRequest.php b/app/Api/V1/Requests/RuleGroupRequest.php index 92cef35728..1b1760a893 100644 --- a/app/Api/V1/Requests/RuleGroupRequest.php +++ b/app/Api/V1/Requests/RuleGroupRequest.php @@ -28,8 +28,9 @@ use FireflyIII\Rules\IsBoolean; /** - * + * @codeCoverageIgnore * Class RuleGroupRequest + * TODO AFTER 4.8.0: split this into two request classes. */ class RuleGroupRequest extends Request { diff --git a/app/Api/V1/Requests/RuleGroupTestRequest.php b/app/Api/V1/Requests/RuleGroupTestRequest.php new file mode 100644 index 0000000000..1b3e9a4c75 --- /dev/null +++ b/app/Api/V1/Requests/RuleGroupTestRequest.php @@ -0,0 +1,144 @@ +. + */ + +namespace FireflyIII\Api\V1\Requests; + + +use Carbon\Carbon; +use FireflyIII\Models\Account; +use FireflyIII\Models\AccountType; +use FireflyIII\Repositories\Account\AccountRepositoryInterface; +use Illuminate\Support\Collection; +use Log; + +/** + * Class RuleGroupTestRequest + */ +class RuleGroupTestRequest extends Request +{ + /** + * Authorize logged in users. + * + * @return bool + */ + public function authorize(): bool + { + // Only allow authenticated users + return auth()->check(); + } + + /** + * @return array + */ + public function getTestParameters(): array + { + $return = [ + 'page' => $this->getPage(), + 'start_date' => $this->getDate('start_date'), + 'end_date' => $this->getDate('end_date'), + 'search_limit' => $this->getSearchLimit(), + 'trigger_limit' => $this->getTriggerLimit(), + 'accounts' => $this->getAccounts(), + ]; + + + return $return; + } + + /** + * @return array + */ + public function rules(): array + { + return []; + } + + /** + * @param string $field + * @return Carbon|null + */ + private function getDate(string $field): ?Carbon + { + /** @var Carbon $result */ + $result = null === $this->query($field) ? null : Carbon::createFromFormat('Y-m-d', $this->query($field)); + + return $result; + } + + /** + * @return int + */ + private function getPage(): int + { + return 0 === (int)$this->query('page') ? 1 : (int)$this->query('page'); + + } + + /** + * @return int + */ + private function getSearchLimit(): int + { + return 0 === (int)$this->query('search_limit') ? (int)config('firefly.test-triggers.limit') : (int)$this->query('search_limit'); + } + + /** + * @return int + */ + private function getTriggerLimit(): int + { + return 0 === (int)$this->query('triggered_limit') ? (int)config('firefly.test-triggers.range') : (int)$this->query('triggered_limit'); + } + + /** + * @return Collection + */ + private function getAccounts(): Collection + { + $accountList = '' === (string)$this->query('accounts') ? [] : explode(',', $this->query('accounts')); + $accounts = new Collection; + + /** @var AccountRepositoryInterface $accountRepository */ + $accountRepository = app(AccountRepositoryInterface::class); + + foreach ($accountList as $accountId) { + Log::debug(sprintf('Searching for asset account with id "%s"', $accountId)); + $account = $accountRepository->findNull((int)$accountId); + if ($this->validAccount($account)) { + /** @noinspection NullPointerExceptionInspection */ + Log::debug(sprintf('Found account #%d ("%s") and its an asset account', $account->id, $account->name)); + $accounts->push($account); + } + } + + return $accounts; + } + + /** + * @param Account|null $account + * @return bool + */ + private function validAccount(?Account $account): bool + { + return null !== $account && AccountType::ASSET === $account->accountType->type; + } + +} \ No newline at end of file diff --git a/app/Api/V1/Requests/RuleGroupTriggerRequest.php b/app/Api/V1/Requests/RuleGroupTriggerRequest.php new file mode 100644 index 0000000000..a9912ae845 --- /dev/null +++ b/app/Api/V1/Requests/RuleGroupTriggerRequest.php @@ -0,0 +1,119 @@ +. + */ + +namespace FireflyIII\Api\V1\Requests; + + +use Carbon\Carbon; +use FireflyIII\Models\Account; +use FireflyIII\Models\AccountType; +use FireflyIII\Repositories\Account\AccountRepositoryInterface; +use Illuminate\Support\Collection; +use Log; + +/** + * Class RuleGroupTriggerRequest + */ +class RuleGroupTriggerRequest extends Request +{ + /** + * Authorize logged in users. + * + * @return bool + */ + public function authorize(): bool + { + // Only allow authenticated users + return auth()->check(); + } + + /** + * @return array + */ + public function getTriggerParameters(): array + { + $return = [ + 'start_date' => $this->getDate('start_date'), + 'end_date' => $this->getDate('end_date'), + 'accounts' => $this->getAccounts(), + ]; + + + return $return; + } + + /** + * @return array + */ + public function rules(): array + { + return [ + 'start_date' => 'required|date', + 'end_date' => 'required|date|after:start_date', + ]; + } + + /** + * @param string $field + * @return Carbon|null + */ + private function getDate(string $field): ?Carbon + { + /** @var Carbon $result */ + $result = null === $this->query($field) ? null : Carbon::createFromFormat('Y-m-d', $this->query($field)); + + return $result; + } + + /** + * @return Collection + */ + private function getAccounts(): Collection + { + $accountList = '' === (string)$this->query('accounts') ? [] : explode(',', $this->query('accounts')); + $accounts = new Collection; + + /** @var AccountRepositoryInterface $accountRepository */ + $accountRepository = app(AccountRepositoryInterface::class); + + foreach ($accountList as $accountId) { + Log::debug(sprintf('Searching for asset account with id "%s"', $accountId)); + $account = $accountRepository->findNull((int)$accountId); + if ($this->validAccount($account)) { + /** @noinspection NullPointerExceptionInspection */ + Log::debug(sprintf('Found account #%d ("%s") and its an asset account', $account->id, $account->name)); + $accounts->push($account); + } + } + + return $accounts; + } + + /** + * @param Account|null $account + * @return bool + */ + private function validAccount(?Account $account): bool + { + return null !== $account && AccountType::ASSET === $account->accountType->type; + } + +} \ No newline at end of file diff --git a/app/Api/V1/Requests/RuleRequest.php b/app/Api/V1/Requests/RuleRequest.php index 36ae924e7c..3924ef956f 100644 --- a/app/Api/V1/Requests/RuleRequest.php +++ b/app/Api/V1/Requests/RuleRequest.php @@ -25,10 +25,12 @@ namespace FireflyIII\Api\V1\Requests; use FireflyIII\Rules\IsBoolean; use Illuminate\Validation\Validator; +use function is_array; /** * Class RuleRequest + * */ class RuleRequest extends Request { @@ -117,7 +119,7 @@ class RuleRequest extends Request /** * Configure the validator instance. * - * @param Validator $validator + * @param Validator $validator * * @return void */ @@ -136,13 +138,13 @@ class RuleRequest extends Request * * @param Validator $validator */ - protected function atLeastOneAction(Validator $validator): void + protected function atLeastOneTrigger(Validator $validator): void { - $data = $validator->getData(); - $actions = $data['actions'] ?? []; + $data = $validator->getData(); + $triggers = $data['triggers'] ?? []; // need at least one trigger - if (0 === \count($actions)) { - $validator->errors()->add('title', (string)trans('validation.at_least_one_action')); + if (0 === count($triggers)) { + $validator->errors()->add('title', (string)trans('validation.at_least_one_trigger')); } } @@ -151,37 +153,16 @@ class RuleRequest extends Request * * @param Validator $validator */ - protected function atLeastOneTrigger(Validator $validator): void + protected function atLeastOneAction(Validator $validator): void { - $data = $validator->getData(); - $triggers = $data['triggers'] ?? []; - // need at least one trugger - if (0 === \count($triggers)) { - $validator->errors()->add('title', (string)trans('validation.at_least_one_trigger')); + $data = $validator->getData(); + $actions = $data['actions'] ?? []; + // need at least one trigger + if (0 === count($actions)) { + $validator->errors()->add('title', (string)trans('validation.at_least_one_action')); } } - /** - * @return array - */ - private function getRuleActions(): array - { - $actions = $this->get('actions'); - $return = []; - if (\is_array($actions)) { - foreach ($actions as $action) { - $return[] = [ - 'type' => $action['type'], - 'value' => $action['value'], - 'active' => $this->convertBoolean((string)($action['active'] ?? 'false')), - 'stop_processing' => $this->convertBoolean((string)($action['stop_processing'] ?? 'false')), - ]; - } - } - - return $return; - } - /** * @return array */ @@ -189,7 +170,7 @@ class RuleRequest extends Request { $triggers = $this->get('triggers'); $return = []; - if (\is_array($triggers)) { + if (is_array($triggers)) { foreach ($triggers as $trigger) { $return[] = [ 'type' => $trigger['type'], @@ -202,4 +183,25 @@ class RuleRequest extends Request return $return; } + + /** + * @return array + */ + private function getRuleActions(): array + { + $actions = $this->get('actions'); + $return = []; + if (is_array($actions)) { + foreach ($actions as $action) { + $return[] = [ + 'type' => $action['type'], + 'value' => $action['value'], + 'active' => $this->convertBoolean((string)($action['active'] ?? 'false')), + 'stop_processing' => $this->convertBoolean((string)($action['stop_processing'] ?? 'false')), + ]; + } + } + + return $return; + } } diff --git a/app/Api/V1/Requests/RuleTestRequest.php b/app/Api/V1/Requests/RuleTestRequest.php new file mode 100644 index 0000000000..44ab7c8114 --- /dev/null +++ b/app/Api/V1/Requests/RuleTestRequest.php @@ -0,0 +1,144 @@ +. + */ + +namespace FireflyIII\Api\V1\Requests; + + +use Carbon\Carbon; +use FireflyIII\Models\Account; +use FireflyIII\Models\AccountType; +use FireflyIII\Repositories\Account\AccountRepositoryInterface; +use Illuminate\Support\Collection; +use Log; + +/** + * Class RuleTestRequest + */ +class RuleTestRequest extends Request +{ + /** + * Authorize logged in users. + * + * @return bool + */ + public function authorize(): bool + { + // Only allow authenticated users + return auth()->check(); + } + + /** + * @return array + */ + public function getTestParameters(): array + { + $return = [ + 'page' => $this->getPage(), + 'start_date' => $this->getDate('start_date'), + 'end_date' => $this->getDate('end_date'), + 'search_limit' => $this->getSearchLimit(), + 'trigger_limit' => $this->getTriggerLimit(), + 'accounts' => $this->getAccounts(), + ]; + + + return $return; + } + + /** + * @return array + */ + public function rules(): array + { + return []; + } + + /** + * @param string $field + * @return Carbon|null + */ + private function getDate(string $field): ?Carbon + { + /** @var Carbon $result */ + $result = null === $this->query($field) ? null : Carbon::createFromFormat('Y-m-d', $this->query($field)); + + return $result; + } + + /** + * @return int + */ + private function getPage(): int + { + return 0 === (int)$this->query('page') ? 1 : (int)$this->query('page'); + + } + + /** + * @return int + */ + private function getSearchLimit(): int + { + return 0 === (int)$this->query('search_limit') ? (int)config('firefly.test-triggers.limit') : (int)$this->query('search_limit'); + } + + /** + * @return int + */ + private function getTriggerLimit(): int + { + return 0 === (int)$this->query('triggered_limit') ? (int)config('firefly.test-triggers.range') : (int)$this->query('triggered_limit'); + } + + /** + * @return Collection + */ + private function getAccounts(): Collection + { + $accountList = '' === (string)$this->query('accounts') ? [] : explode(',', $this->query('accounts')); + $accounts = new Collection; + + /** @var AccountRepositoryInterface $accountRepository */ + $accountRepository = app(AccountRepositoryInterface::class); + + foreach ($accountList as $accountId) { + Log::debug(sprintf('Searching for asset account with id "%s"', $accountId)); + $account = $accountRepository->findNull((int)$accountId); + if ($this->validAccount($account)) { + /** @noinspection NullPointerExceptionInspection */ + Log::debug(sprintf('Found account #%d ("%s") and its an asset account', $account->id, $account->name)); + $accounts->push($account); + } + } + + return $accounts; + } + + /** + * @param Account|null $account + * @return bool + */ + private function validAccount(?Account $account): bool + { + return null !== $account && AccountType::ASSET === $account->accountType->type; + } + +} \ No newline at end of file diff --git a/app/Api/V1/Requests/RuleTriggerRequest.php b/app/Api/V1/Requests/RuleTriggerRequest.php new file mode 100644 index 0000000000..570232751d --- /dev/null +++ b/app/Api/V1/Requests/RuleTriggerRequest.php @@ -0,0 +1,119 @@ +. + */ + +namespace FireflyIII\Api\V1\Requests; + + +use Carbon\Carbon; +use FireflyIII\Models\Account; +use FireflyIII\Models\AccountType; +use FireflyIII\Repositories\Account\AccountRepositoryInterface; +use Illuminate\Support\Collection; +use Log; + +/** + * Class RuleTriggerRequest + */ +class RuleTriggerRequest extends Request +{ + /** + * Authorize logged in users. + * + * @return bool + */ + public function authorize(): bool + { + // Only allow authenticated users + return auth()->check(); + } + + /** + * @return array + */ + public function getTriggerParameters(): array + { + $return = [ + 'start_date' => $this->getDate('start_date'), + 'end_date' => $this->getDate('end_date'), + 'accounts' => $this->getAccounts(), + ]; + + + return $return; + } + + /** + * @return array + */ + public function rules(): array + { + return [ + 'start_date' => 'required|date', + 'end_date' => 'required|date|after:start_date', + ]; + } + + /** + * @param string $field + * @return Carbon|null + */ + private function getDate(string $field): ?Carbon + { + /** @var Carbon $result */ + $result = null === $this->query($field) ? null : Carbon::createFromFormat('Y-m-d', $this->query($field)); + + return $result; + } + + /** + * @return Collection + */ + private function getAccounts(): Collection + { + $accountList = '' === (string)$this->query('accounts') ? [] : explode(',', $this->query('accounts')); + $accounts = new Collection; + + /** @var AccountRepositoryInterface $accountRepository */ + $accountRepository = app(AccountRepositoryInterface::class); + + foreach ($accountList as $accountId) { + Log::debug(sprintf('Searching for asset account with id "%s"', $accountId)); + $account = $accountRepository->findNull((int)$accountId); + if ($this->validAccount($account)) { + /** @noinspection NullPointerExceptionInspection */ + Log::debug(sprintf('Found account #%d ("%s") and its an asset account', $account->id, $account->name)); + $accounts->push($account); + } + } + + return $accounts; + } + + /** + * @param Account|null $account + * @return bool + */ + private function validAccount(?Account $account): bool + { + return null !== $account && AccountType::ASSET === $account->accountType->type; + } + +} \ No newline at end of file diff --git a/app/Api/V1/Requests/TagRequest.php b/app/Api/V1/Requests/TagRequest.php index 78dad9c6c1..e463dc72de 100644 --- a/app/Api/V1/Requests/TagRequest.php +++ b/app/Api/V1/Requests/TagRequest.php @@ -28,6 +28,10 @@ use FireflyIII\Models\Tag; /** * Class TagRequest + * + * @codeCoverageIgnore + * + * TODO AFTER 4.8.0: split this into two request classes. */ class TagRequest extends Request { diff --git a/app/Api/V1/Requests/TransactionLinkRequest.php b/app/Api/V1/Requests/TransactionLinkRequest.php index 77a4306a9b..a5d37e70ea 100644 --- a/app/Api/V1/Requests/TransactionLinkRequest.php +++ b/app/Api/V1/Requests/TransactionLinkRequest.php @@ -81,7 +81,7 @@ class TransactionLinkRequest extends Request /** * Configure the validator instance. * - * @param Validator $validator + * @param Validator $validator * * @return void */ diff --git a/app/Api/V1/Requests/TransactionRequest.php b/app/Api/V1/Requests/TransactionRequest.php deleted file mode 100644 index a9b024cc37..0000000000 --- a/app/Api/V1/Requests/TransactionRequest.php +++ /dev/null @@ -1,222 +0,0 @@ -. - */ - -declare(strict_types=1); - -namespace FireflyIII\Api\V1\Requests; - -use FireflyIII\Rules\BelongsUser; -use FireflyIII\Rules\IsBoolean; -use FireflyIII\Rules\IsDateOrTime; -use FireflyIII\Validation\TransactionValidation; -use Illuminate\Validation\Validator; - - -/** - * Class TransactionRequest - */ -class TransactionRequest extends Request -{ - use TransactionValidation; - - /** - * Authorize logged in users. - * - * @return bool - */ - public function authorize(): bool - { - // Only allow authenticated users - return auth()->check(); - } - - /** - * Get all data. Is pretty complex because of all the ??-statements. - * - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * @SuppressWarnings(PHPMD.NPathComplexity) - * @return array - */ - public function getAll(): array - { - $data = [ - 'type' => $this->string('type'), - 'date' => $this->dateTime('date'), - 'description' => $this->string('description'), - 'piggy_bank_id' => $this->integer('piggy_bank_id'), - 'piggy_bank_name' => $this->string('piggy_bank_name'), - 'bill_id' => $this->integer('bill_id'), - 'bill_name' => $this->string('bill_name'), - 'tags' => explode(',', $this->string('tags')), - 'notes' => $this->string('notes'), - 'sepa-cc' => $this->string('sepa_cc'), - 'sepa-ct-op' => $this->string('sepa_ct_op'), - 'sepa-ct-id' => $this->string('sepa_ct_id'), - 'sepa-db' => $this->string('sepa_db'), - 'sepa-country' => $this->string('sepa_country'), - 'sepa-ep' => $this->string('sepa_ep'), - 'sepa-ci' => $this->string('sepa_ci'), - 'sepa-batch-id' => $this->string('sepa_batch_id'), - 'interest_date' => $this->date('interest_date'), - 'book_date' => $this->date('book_date'), - 'process_date' => $this->date('process_date'), - 'due_date' => $this->date('due_date'), - 'payment_date' => $this->date('payment_date'), - 'invoice_date' => $this->date('invoice_date'), - 'internal_reference' => $this->string('internal_reference'), - 'bunq_payment_id' => $this->string('bunq_payment_id'), - 'external_id' => $this->string('external_id'), - 'original-source' => sprintf('ff3-v%s|api-v%s', config('firefly.version'), config('firefly.api_version')), - 'transactions' => $this->getTransactionData(), - ]; - - return $data; - } - - /** - * The rules that the incoming request must be matched against. - * - * @return array - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - */ - public function rules(): array - { - $rules = [ - // basic fields for journal: - 'type' => 'required|in:withdrawal,deposit,transfer,opening-balance,reconciliation', - 'description' => 'between:1,255', - 'date' => ['required', new IsDateOrTime], - 'piggy_bank_id' => ['numeric', 'nullable', 'mustExist:piggy_banks,id', new BelongsUser], - 'piggy_bank_name' => ['between:1,255', 'nullable', new BelongsUser], - 'bill_id' => ['numeric', 'nullable', 'mustExist:bills,id', new BelongsUser], - 'bill_name' => ['between:1,255', 'nullable', new BelongsUser], - 'tags' => 'between:1,255', - - // then, custom fields for journal - 'notes' => 'min:1,max:50000|nullable', - - // SEPA fields: - 'sepa_cc' => 'min:1,max:255|nullable', - 'sepa_ct_op' => 'min:1,max:255|nullable', - 'sepa_ct_id' => 'min:1,max:255|nullable', - 'sepa_db' => 'min:1,max:255|nullable', - 'sepa_country' => 'min:1,max:255|nullable', - 'sepa_ep' => 'min:1,max:255|nullable', - 'sepa_ci' => 'min:1,max:255|nullable', - 'sepa_batch_id' => 'min:1,max:255|nullable', - - // dates - 'interest_date' => 'date|nullable', - 'book_date' => 'date|nullable', - 'process_date' => 'date|nullable', - 'due_date' => 'date|nullable', - 'payment_date' => 'date|nullable', - 'invoice_date' => 'date|nullable', - 'internal_reference' => 'min:1,max:255|nullable', - 'bunq_payment_id' => 'min:1,max:255|nullable', - 'external_id' => 'min:1,max:255|nullable', - - // transaction rules (in array for splits): - 'transactions.*.amount' => 'required|numeric|more:0', - 'transactions.*.description' => 'nullable|between:1,255', - 'transactions.*.currency_id' => 'numeric|exists:transaction_currencies,id', - 'transactions.*.currency_code' => 'min:3|max:3|exists:transaction_currencies,code', - 'transactions.*.foreign_amount' => 'numeric|more:0', - 'transactions.*.foreign_currency_id' => 'numeric|exists:transaction_currencies,id', - 'transactions.*.foreign_currency_code' => 'min:3|max:3|exists:transaction_currencies,code', - 'transactions.*.budget_id' => ['mustExist:budgets,id', new BelongsUser], - 'transactions.*.budget_name' => ['between:1,255', 'nullable', new BelongsUser], - 'transactions.*.category_id' => ['mustExist:categories,id', new BelongsUser], - 'transactions.*.category_name' => 'between:1,255|nullable', - 'transactions.*.reconciled' => [new IsBoolean], - 'transactions.*.source_id' => ['numeric', 'nullable', new BelongsUser], - 'transactions.*.source_name' => 'between:1,255|nullable', - 'transactions.*.destination_id' => ['numeric', 'nullable', new BelongsUser], - 'transactions.*.destination_name' => 'between:1,255|nullable', - ]; - - if ('PUT' === $this->method()) { - unset($rules['type'], $rules['piggy_bank_id'], $rules['piggy_bank_name']); - } - - return $rules; - - - } - - /** - * Configure the validator instance. - * - * @param Validator $validator - * - * @return void - */ - public function withValidator(Validator $validator): void - { - $validator->after( - function (Validator $validator) { - $this->validateOneTransaction($validator); - $this->validateDescriptions($validator); - $this->validateJournalDescription($validator); - $this->validateSplitDescriptions($validator); - $this->validateForeignCurrencyInformation($validator); - $this->validateAccountInformation($validator); - $this->validateSplitAccounts($validator); - } - ); - } - - /** - * Get transaction data. - * - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * @SuppressWarnings(PHPMD.NPathComplexity) - * @return array - */ - private function getTransactionData(): array - { - $return = []; - foreach ($this->get('transactions') as $index => $transaction) { - $return[] = [ - 'amount' => $transaction['amount'], - 'description' => $transaction['description'] ?? null, - 'currency_id' => isset($transaction['currency_id']) ? (int)$transaction['currency_id'] : null, - 'currency_code' => $transaction['currency_code'] ?? null, - 'foreign_amount' => $transaction['foreign_amount'] ?? null, - 'foreign_currency_id' => isset($transaction['foreign_currency_id']) ? (int)$transaction['foreign_currency_id'] : null, - 'foreign_currency_code' => $transaction['foreign_currency_code'] ?? null, - 'budget_id' => isset($transaction['budget_id']) ? (int)$transaction['budget_id'] : null, - 'budget_name' => $transaction['budget_name'] ?? null, - 'category_id' => isset($transaction['category_id']) ? (int)$transaction['category_id'] : null, - 'category_name' => $transaction['category_name'] ?? null, - 'source_id' => isset($transaction['source_id']) ? (int)$transaction['source_id'] : null, - 'source_name' => isset($transaction['source_name']) ? (string)$transaction['source_name'] : null, - 'destination_id' => isset($transaction['destination_id']) ? (int)$transaction['destination_id'] : null, - 'destination_name' => isset($transaction['destination_name']) ? (string)$transaction['destination_name'] : null, - 'reconciled' => $this->convertBoolean((string)($transaction['reconciled'] ?? 'false')), - 'identifier' => $index, - ]; - } - - return $return; - } -} diff --git a/app/Api/V1/Requests/TransactionStoreRequest.php b/app/Api/V1/Requests/TransactionStoreRequest.php new file mode 100644 index 0000000000..7b267cf4d8 --- /dev/null +++ b/app/Api/V1/Requests/TransactionStoreRequest.php @@ -0,0 +1,278 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Requests; + +use FireflyIII\Rules\BelongsUser; +use FireflyIII\Rules\IsBoolean; +use FireflyIII\Rules\IsDateOrTime; +use FireflyIII\Support\NullArrayObject; +use FireflyIII\Validation\TransactionValidation; +use Illuminate\Validation\Validator; + + +/** + * Class TransactionStoreRequest + */ +class TransactionStoreRequest extends Request +{ + use TransactionValidation; + + /** + * Authorize logged in users. + * + * @return bool + */ + public function authorize(): bool + { + // Only allow authenticated users + return auth()->check(); + } + + /** + * Get all data. Is pretty complex because of all the ??-statements. + * + * @return array + */ + public function getAll(): array + { + $data = [ + 'group_title' => $this->string('group_title'), + 'transactions' => $this->getTransactionData(), + ]; + + return $data; + } + + /** + * The rules that the incoming request must be matched against. + * + * @return array + */ + public function rules(): array + { + $rules = [ + // basic fields for group: + 'group_title' => 'between:1,255', + + // transaction rules (in array for splits): + 'transactions.*.type' => 'required|in:withdrawal,deposit,transfer,opening-balance,reconciliation', + 'transactions.*.date' => ['required', new IsDateOrTime], + 'transactions.*.order' => 'numeric|min:0', + + // currency info + 'transactions.*.currency_id' => 'numeric|exists:transaction_currencies,id', + 'transactions.*.currency_code' => 'min:3|max:3|exists:transaction_currencies,code', + 'transactions.*.foreign_currency_id' => 'numeric|exists:transaction_currencies,id', + 'transactions.*.foreign_currency_code' => 'min:3|max:3|exists:transaction_currencies,code', + + // amount + 'transactions.*.amount' => 'required|numeric|more:0', + 'transactions.*.foreign_amount' => 'numeric|more:0', + + // description + 'transactions.*.description' => 'nullable|between:1,255', + + // source of transaction + 'transactions.*.source_id' => ['numeric', 'nullable', new BelongsUser], + 'transactions.*.source_name' => 'between:1,255|nullable', + + // destination of transaction + 'transactions.*.destination_id' => ['numeric', 'nullable', new BelongsUser], + 'transactions.*.destination_name' => 'between:1,255|nullable', + + // budget, category, bill and piggy + 'transactions.*.budget_id' => ['mustExist:budgets,id', new BelongsUser], + 'transactions.*.budget_name' => ['between:1,255', 'nullable', new BelongsUser], + 'transactions.*.category_id' => ['mustExist:categories,id', new BelongsUser], + 'transactions.*.category_name' => 'between:1,255|nullable', + 'transactions.*.bill_id' => ['numeric', 'nullable', 'mustExist:bills,id', new BelongsUser], + 'transactions.*.bill_name' => ['between:1,255', 'nullable', new BelongsUser], + 'transactions.*.piggy_bank_id' => ['numeric', 'nullable', 'mustExist:piggy_banks,id', new BelongsUser], + 'transactions.*.piggy_bank_name' => ['between:1,255', 'nullable', new BelongsUser], + + // other interesting fields + 'transactions.*.reconciled' => [new IsBoolean], + 'transactions.*.notes' => 'min:1,max:50000|nullable', + 'transactions.*.tags' => 'between:1,255', + + // meta info fields + 'transactions.*.internal_reference' => 'min:1,max:255|nullable', + 'transactions.*.external_id' => 'min:1,max:255|nullable', + 'transactions.*.recurrence_id' => 'min:1,max:255|nullable', + 'transactions.*.bunq_payment_id' => 'min:1,max:255|nullable', + + // SEPA fields: + 'transactions.*.sepa_cc' => 'min:1,max:255|nullable', + 'transactions.*.sepa_ct_op' => 'min:1,max:255|nullable', + 'transactions.*.sepa_ct_id' => 'min:1,max:255|nullable', + 'transactions.*.sepa_db' => 'min:1,max:255|nullable', + 'transactions.*.sepa_country' => 'min:1,max:255|nullable', + 'transactions.*.sepa_ep' => 'min:1,max:255|nullable', + 'transactions.*.sepa_ci' => 'min:1,max:255|nullable', + 'transactions.*.sepa_batch_id' => 'min:1,max:255|nullable', + + // dates + 'transactions.*.interest_date' => 'date|nullable', + 'transactions.*.book_date' => 'date|nullable', + 'transactions.*.process_date' => 'date|nullable', + 'transactions.*.due_date' => 'date|nullable', + 'transactions.*.payment_date' => 'date|nullable', + 'transactions.*.invoice_date' => 'date|nullable', + ]; + + return $rules; + + + } + + /** + * Configure the validator instance. + * + * @param Validator $validator + * + * @return void + */ + public function withValidator(Validator $validator): void + { + $validator->after( + function (Validator $validator) { + // must submit at least one transaction. + $this->validateOneTransaction($validator); + + // all journals must have a description + $this->validateDescriptions($validator); + + // all transaction types must be equal: + $this->validateTransactionTypes($validator); + + // validate foreign currency info + $this->validateForeignCurrencyInformation($validator); + + // validate all account info + $this->validateAccountInformation($validator); + + // validate source/destination is equal, depending on the transaction journal type. + $this->validateEqualAccounts($validator); + + // the group must have a description if > 1 journal. + $this->validateGroupDescription($validator); + + } + ); + } + + /** + * Get transaction data. + * + * @return array + */ + private function getTransactionData(): array + { + $return = []; + /** + * @var int $index + * @var array $transaction + */ + foreach ($this->get('transactions') as $index => $transaction) { + $object = new NullArrayObject($transaction); + $return[] = [ + 'type' => $this->stringFromValue($object['type']), + 'date' => $this->dateFromValue($object['date']), + 'order' => $this->integerFromValue((string)$object['order']), + + 'currency_id' => $this->integerFromValue($object['currency_id']), + 'currency_code' => $this->stringFromValue($object['currency_code']), + + // foreign currency info: + 'foreign_currency_id' => $this->integerFromValue((string)$object['foreign_currency_id']), + 'foreign_currency_code' => $this->stringFromValue($object['foreign_currency_code']), + + // amount and foreign amount. Cannot be 0. + 'amount' => $this->stringFromValue((string)$object['amount']), + 'foreign_amount' => $this->stringFromValue((string)$object['foreign_amount']), + + // description. + 'description' => $this->stringFromValue($object['description']), + + // source of transaction. If everything is null, assume cash account. + 'source_id' => $this->integerFromValue((string)$object['source_id']), + 'source_name' => $this->stringFromValue($object['source_name']), + + // destination of transaction. If everything is null, assume cash account. + 'destination_id' => $this->integerFromValue((string)$object['destination_id']), + 'destination_name' => $this->stringFromValue($object['destination_name']), + + // budget info + 'budget_id' => $this->integerFromValue((string)$object['budget_id']), + 'budget_name' => $this->stringFromValue($object['budget_name']), + + // category info + 'category_id' => $this->integerFromValue((string)$object['category_id']), + 'category_name' => $this->stringFromValue($object['category_name']), + + // journal bill reference. Optional. Will only work for withdrawals + 'bill_id' => $this->integerFromValue((string)$object['bill_id']), + 'bill_name' => $this->stringFromValue($object['bill_name']), + + // piggy bank reference. Optional. Will only work for transfers + 'piggy_bank_id' => $this->integerFromValue((string)$object['piggy_bank_id']), + 'piggy_bank_name' => $this->stringFromValue($object['piggy_bank_name']), + + // some other interesting properties + 'reconciled' => $this->convertBoolean((string)$object['reconciled']), + 'notes' => $this->stringFromValue($object['notes']), + 'tags' => $this->arrayFromValue($object['tags']), + + // all custom fields: + 'internal_reference' => $this->stringFromValue($object['internal_reference']), + 'external_id' => $this->stringFromValue($object['external_id']), + 'original_source' => sprintf('ff3-v%s|api-v%s', config('firefly.version'), config('firefly.api_version')), + 'recurrence_id' => $this->integerFromValue($object['recurrence_id']), + 'bunq_payment_id' => $this->stringFromValue($object['bunq_payment_id']), + + 'sepa_cc' => $this->stringFromValue($object['sepa_cc']), + 'sepa_ct_op' => $this->stringFromValue($object['sepa_ct_op']), + 'sepa_ct_id' => $this->stringFromValue($object['sepa_ct_id']), + 'sepa_db' => $this->stringFromValue($object['sepa_db']), + 'sepa_country' => $this->stringFromValue($object['sepa_country']), + 'sepa_ep' => $this->stringFromValue($object['sepa_ep']), + 'sepa_ci' => $this->stringFromValue($object['sepa_ci']), + 'sepa_batch_id' => $this->stringFromValue($object['sepa_batch_id']), + + + // custom date fields. Must be Carbon objects. Presence is optional. + 'interest_date' => $this->dateFromValue($object['interest_date']), + 'book_date' => $this->dateFromValue($object['book_date']), + 'process_date' => $this->dateFromValue($object['process_date']), + 'due_date' => $this->dateFromValue($object['due_date']), + 'payment_date' => $this->dateFromValue($object['payment_date']), + 'invoice_date' => $this->dateFromValue($object['invoice_date']), + + ]; + } + + return $return; + } +} diff --git a/app/Api/V1/Requests/TransactionUpdateRequest.php b/app/Api/V1/Requests/TransactionUpdateRequest.php new file mode 100644 index 0000000000..128124737e --- /dev/null +++ b/app/Api/V1/Requests/TransactionUpdateRequest.php @@ -0,0 +1,324 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Api\V1\Requests; + +use FireflyIII\Models\TransactionGroup; +use FireflyIII\Rules\BelongsUser; +use FireflyIII\Rules\IsBoolean; +use FireflyIII\Rules\IsDateOrTime; +use FireflyIII\Validation\TransactionValidation; +use Illuminate\Validation\Validator; +use Log; + +/** + * Class TransactionUpdateRequest + */ +class TransactionUpdateRequest extends Request +{ + use TransactionValidation; + + /** @var array Array values. */ + private $arrayFields; + /** @var array Boolean values. */ + private $booleanFields; + /** @var array Fields that contain date values. */ + private $dateFields; + /** @var array Fields that contain integer values. */ + private $integerFields; + /** @var array Fields that contain string values. */ + private $stringFields; + + /** + * Authorize logged in users. + * + * @return bool + */ + public function authorize(): bool + { + // Only allow authenticated users + return auth()->check(); + } + + /** + * Get all data. Is pretty complex because of all the ??-statements. + * + * @return array + */ + public function getAll(): array + { + $this->integerFields = [ + 'order', + 'currency_id', + 'foreign_currency_id', + 'transaction_journal_id', + 'source_id', + 'destination_id', + 'budget_id', + 'category_id', + 'bill_id', + 'recurrence_id', + ]; + + $this->dateFields = [ + 'date', + 'interest_date', + 'book_date', + 'process_date', + 'due_date', + 'payment_date', + 'invoice_date', + ]; + + $this->stringFields = [ + 'type', + 'currency_code', + 'foreign_currency_code', + 'amount', + 'foreign_amount', + 'description', + 'source_name', + 'destination_name', + 'budget_name', + 'category_name', + 'bill_name', + 'notes', + 'internal_reference', + 'external_id', + 'bunq_payment_id', + 'sepa_cc', + 'sepa_ct_op', + 'sepa_ct_id', + 'sepa_db', + 'sepa_country', + 'sepa_ep', + 'sepa_ci', + 'sepa_batch_id', + ]; + $this->booleanFields = [ + 'reconciled', + ]; + + $this->arrayFields = [ + 'tags', + ]; + + + $data = [ + 'transactions' => $this->getTransactionData(), + ]; + if ($this->has('group_title')) { + $data['group_title'] = $this->string('group_title'); + } + + return $data; + } + + /** + * The rules that the incoming request must be matched against. + * + * @return array + */ + public function rules(): array + { + $rules = [ + // basic fields for group: + 'group_title' => 'between:1,255', + + // transaction rules (in array for splits): + 'transactions.*.type' => 'in:withdrawal,deposit,transfer,opening-balance,reconciliation', + 'transactions.*.date' => [new IsDateOrTime], + 'transactions.*.order' => 'numeric|min:0', + + // currency info + 'transactions.*.currency_id' => 'numeric|exists:transaction_currencies,id', + 'transactions.*.currency_code' => 'min:3|max:3|exists:transaction_currencies,code', + 'transactions.*.foreign_currency_id' => 'numeric|exists:transaction_currencies,id', + 'transactions.*.foreign_currency_code' => 'min:3|max:3|exists:transaction_currencies,code', + + // amount + 'transactions.*.amount' => 'numeric|more:0', + 'transactions.*.foreign_amount' => 'numeric|gte:0', + + // description + 'transactions.*.description' => 'nullable|between:1,255', + + // source of transaction + 'transactions.*.source_id' => ['numeric', 'nullable', new BelongsUser], + 'transactions.*.source_name' => 'between:1,255|nullable', + + // destination of transaction + 'transactions.*.destination_id' => ['numeric', 'nullable', new BelongsUser], + 'transactions.*.destination_name' => 'between:1,255|nullable', + + // budget, category, bill and piggy + 'transactions.*.budget_id' => ['mustExist:budgets,id', new BelongsUser], + 'transactions.*.budget_name' => ['between:1,255', 'nullable', new BelongsUser], + 'transactions.*.category_id' => ['mustExist:categories,id', new BelongsUser], + 'transactions.*.category_name' => 'between:1,255|nullable', + 'transactions.*.bill_id' => ['numeric', 'nullable', 'mustExist:bills,id', new BelongsUser], + 'transactions.*.bill_name' => ['between:1,255', 'nullable', new BelongsUser], + + // other interesting fields + 'transactions.*.reconciled' => [new IsBoolean], + 'transactions.*.notes' => 'min:1,max:50000|nullable', + 'transactions.*.tags' => 'between:0,255', + + // meta info fields + 'transactions.*.internal_reference' => 'min:1,max:255|nullable', + 'transactions.*.external_id' => 'min:1,max:255|nullable', + 'transactions.*.recurrence_id' => 'min:1,max:255|nullable', + 'transactions.*.bunq_payment_id' => 'min:1,max:255|nullable', + + // SEPA fields: + 'transactions.*.sepa_cc' => 'min:1,max:255|nullable', + 'transactions.*.sepa_ct_op' => 'min:1,max:255|nullable', + 'transactions.*.sepa_ct_id' => 'min:1,max:255|nullable', + 'transactions.*.sepa_db' => 'min:1,max:255|nullable', + 'transactions.*.sepa_country' => 'min:1,max:255|nullable', + 'transactions.*.sepa_ep' => 'min:1,max:255|nullable', + 'transactions.*.sepa_ci' => 'min:1,max:255|nullable', + 'transactions.*.sepa_batch_id' => 'min:1,max:255|nullable', + + // dates + 'transactions.*.interest_date' => 'date|nullable', + 'transactions.*.book_date' => 'date|nullable', + 'transactions.*.process_date' => 'date|nullable', + 'transactions.*.due_date' => 'date|nullable', + 'transactions.*.payment_date' => 'date|nullable', + 'transactions.*.invoice_date' => 'date|nullable', + ]; + + return $rules; + } + + /** + * Configure the validator instance. + * + * @param Validator $validator + * + * @return void + */ + public function withValidator(Validator $validator): void + { + /** @var TransactionGroup $transactionGroup */ + $transactionGroup = $this->route()->parameter('transactionGroup'); + $validator->after( + function (Validator $validator) use ($transactionGroup) { + // must submit at least one transaction. + $this->validateOneTransaction($validator); + + // if more than one, verify that there are journal ID's present. + $this->validateJournalIds($validator, $transactionGroup); + + // all transaction types must be equal: + $this->validateTransactionTypesForUpdate($validator); + + // validate source/destination is equal, depending on the transaction journal type. + $this->validateEqualAccountsForUpdate($validator, $transactionGroup); + + // If type is set, source + destination info is mandatory. + // Not going to do this. Not sure where the demand came from. + + // validate that the currency fits the source and/or destination account. + // validate all account info + $this->validateAccountInformationUpdate($validator); + + // The currency info must match the accounts involved. + // Instead will ignore currency info as much as possible. + + // TODO if the transaction_journal_id is empty, some fields are mandatory, like the amount! + + // all journals must have a description + //$this->validateDescriptions($validator); + + // // validate foreign currency info + // $this->validateForeignCurrencyInformation($validator); + // + // + + // + // // make sure all splits have valid source + dest info + // $this->validateSplitAccounts($validator); + // the group must have a description if > 1 journal. + // $this->validateGroupDescription($validator); + } + ); + } + + /** + * Get transaction data. + * + * @return array + */ + private function getTransactionData(): array + { + Log::debug('Now in getTransactionData()'); + $return = []; + /** + * @var int $index + * @var array $transaction + */ + foreach ($this->get('transactions') as $index => $transaction) { + // default response is to update nothing in the transaction: + $current = []; + + // for each field, add it to the array if a reference is present in the request: + foreach ($this->integerFields as $fieldName) { + if (array_key_exists($fieldName, $transaction)) { + $current[$fieldName] = $this->integerFromValue((string)$transaction[$fieldName]); + } + } + + foreach ($this->stringFields as $fieldName) { + if (array_key_exists($fieldName, $transaction)) { + $current[$fieldName] = $this->stringFromValue((string)$transaction[$fieldName]); + } + } + + foreach ($this->dateFields as $fieldName) { + Log::debug(sprintf('Now at date field %s', $fieldName)); + if (array_key_exists($fieldName, $transaction)) { + $current[$fieldName] = $this->dateFromValue((string)$transaction[$fieldName]); + Log::debug(sprintf('New value: "%s"', (string)$transaction[$fieldName])); + } + } + + foreach ($this->booleanFields as $fieldName) { + if (array_key_exists($fieldName, $transaction)) { + $current[$fieldName] = $this->convertBoolean((string)$transaction[$fieldName]); + } + } + + foreach ($this->arrayFields as $fieldName) { + if (array_key_exists($fieldName, $transaction)) { + $current[$fieldName] = $this->arrayFromValue($transaction[$fieldName]); + } + } + $return[] = $current; + } + + return $return; + } +} diff --git a/app/Api/V1/Requests/UserRequest.php b/app/Api/V1/Requests/UserRequest.php index 71100881eb..4cb67c73b3 100644 --- a/app/Api/V1/Requests/UserRequest.php +++ b/app/Api/V1/Requests/UserRequest.php @@ -31,6 +31,8 @@ use FireflyIII\User; /** * Class UserRequest + * @codeCoverageIgnore + * TODO AFTER 4.8.0: split this into two request classes. */ class UserRequest extends Request { diff --git a/app/Console/Commands/ApplyRules.php b/app/Console/Commands/ApplyRules.php deleted file mode 100644 index c8d051acab..0000000000 --- a/app/Console/Commands/ApplyRules.php +++ /dev/null @@ -1,420 +0,0 @@ -. - */ - -declare(strict_types=1); - -namespace FireflyIII\Console\Commands; - -use Carbon\Carbon; -use FireflyIII\Helpers\Collector\TransactionCollectorInterface; -use FireflyIII\Models\AccountType; -use FireflyIII\Models\Rule; -use FireflyIII\Models\RuleGroup; -use FireflyIII\Models\Transaction; -use FireflyIII\Repositories\Account\AccountRepositoryInterface; -use FireflyIII\Repositories\Journal\JournalRepositoryInterface; -use FireflyIII\Repositories\Rule\RuleRepositoryInterface; -use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface; -use FireflyIII\TransactionRules\Processor; -use Illuminate\Console\Command; -use Illuminate\Support\Collection; - -/** - * - * Class ApplyRules - * - * @codeCoverageIgnore - */ -class ApplyRules extends Command -{ - use VerifiesAccessToken; - - /** - * The console command description. - * - * @var string - */ - protected $description = 'This command will apply your rules and rule groups on a selection of your transactions.'; - /** - * The name and signature of the console command. - * - * @var string - */ - protected $signature - = 'firefly:apply-rules - {--user=1 : The user ID that the import should import for.} - {--token= : The user\'s access token.} - {--accounts= : A comma-separated list of asset accounts or liabilities to apply your rules to.} - {--rule_groups= : A comma-separated list of rule groups to apply. Take the ID\'s of these rule groups from the Firefly III interface.} - {--rules= : A comma-separated list of rules to apply. Take the ID\'s of these rules from the Firefly III interface. Using this option overrules the option that selects rule groups.} - {--all_rules : If set, will overrule both settings and simply apply ALL of your rules.} - {--start_date= : The date of the earliest transaction to be included (inclusive). If omitted, will be your very first transaction ever. Format: YYYY-MM-DD} - {--end_date= : The date of the latest transaction to be included (inclusive). If omitted, will be your latest transaction ever. Format: YYYY-MM-DD}'; - /** @var Collection */ - private $accounts; - /** @var Carbon */ - private $endDate; - /** @var Collection */ - private $results; - /** @var Collection */ - private $ruleGroups; - /** @var Collection */ - private $rules; - /** @var Carbon */ - private $startDate; - - /** - * Create a new command instance. - * - * @return void - */ - public function __construct() - { - parent::__construct(); - $this->accounts = new Collection; - $this->rules = new Collection; - $this->ruleGroups = new Collection; - $this->results = new Collection; - } - - /** - * Execute the console command. - * - * @return int - * @throws \FireflyIII\Exceptions\FireflyException - */ - public function handle(): int - { - if (!$this->verifyAccessToken()) { - $this->error('Invalid access token.'); - - return 1; - } - - $result = $this->verifyInput(); - if (false === $result) { - return 1; - } - - - // get transactions from asset accounts. - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setUser($this->getUser()); - $collector->setAccounts($this->accounts); - $collector->setRange($this->startDate, $this->endDate); - $transactions = $collector->getTransactions(); - $count = $transactions->count(); - - // first run all rule groups: - /** @var RuleGroupRepositoryInterface $ruleGroupRepos */ - $ruleGroupRepos = app(RuleGroupRepositoryInterface::class); - $ruleGroupRepos->setUser($this->getUser()); - - /** @var RuleGroup $ruleGroup */ - foreach ($this->ruleGroups as $ruleGroup) { - $this->line(sprintf('Going to apply rule group "%s" to %d transaction(s).', $ruleGroup->title, $count)); - $rules = $ruleGroupRepos->getActiveStoreRules($ruleGroup); - $this->applyRuleSelection($rules, $transactions, true); - } - - // then run all rules (rule groups should be empty). - if ($this->rules->count() > 0) { - - $this->line(sprintf('Will apply %d rule(s) to %d transaction(s)', $this->rules->count(), $transactions->count())); - $this->applyRuleSelection($this->rules, $transactions, false); - } - - // filter results: - $this->results = $this->results->unique( - function (Transaction $transaction) { - return (int)$transaction->journal_id; - } - ); - - $this->line(''); - if (0 === $this->results->count()) { - $this->line('The rules were fired but did not influence any transactions.'); - } - if ($this->results->count() > 0) { - $this->line(sprintf('The rule(s) was/were fired, and influenced %d transaction(s).', $this->results->count())); - foreach ($this->results as $result) { - $this->line( - vsprintf( - 'Transaction #%d: "%s" (%s %s)', - [ - $result->journal_id, - $result->description, - $result->transaction_currency_code, - round($result->transaction_amount, $result->transaction_currency_dp), - ] - ) - ); - } - } - - return 0; - } - - /** - * @param Collection $rules - * @param Collection $transactions - * @param bool $breakProcessing - * - * @throws \FireflyIII\Exceptions\FireflyException - */ - private function applyRuleSelection(Collection $rules, Collection $transactions, bool $breakProcessing): void - { - $bar = $this->output->createProgressBar($rules->count() * $transactions->count()); - - /** @var Rule $rule */ - foreach ($rules as $rule) { - /** @var Processor $processor */ - $processor = app(Processor::class); - $processor->make($rule, true); - - /** @var Transaction $transaction */ - foreach ($transactions as $transaction) { - /** @noinspection DisconnectedForeachInstructionInspection */ - $bar->advance(); - $result = $processor->handleTransaction($transaction); - if (true === $result) { - $this->results->push($transaction); - } - } - if (true === $rule->stop_processing && true === $breakProcessing) { - $this->line(''); - $this->line(sprintf('Rule #%d ("%s") says to stop processing.', $rule->id, $rule->title)); - - return; - } - } - $this->line(''); - } - - /** - * - * @throws \FireflyIII\Exceptions\FireflyException - */ - private function grabAllRules(): void - { - if (true === $this->option('all_rules')) { - /** @var RuleRepositoryInterface $ruleRepos */ - $ruleRepos = app(RuleRepositoryInterface::class); - $ruleRepos->setUser($this->getUser()); - $this->rules = $ruleRepos->getAll(); - - // reset rule groups. - $this->ruleGroups = new Collection; - } - } - - /** - * - * @throws \FireflyIII\Exceptions\FireflyException - */ - private function parseDates(): void - { - // parse start date. - $startDate = Carbon::now()->startOfMonth(); - $startString = $this->option('start_date'); - if (null === $startString) { - /** @var JournalRepositoryInterface $repository */ - $repository = app(JournalRepositoryInterface::class); - $repository->setUser($this->getUser()); - $first = $repository->firstNull(); - if (null !== $first) { - $startDate = $first->date; - } - } - if (null !== $startString && '' !== $startString) { - $startDate = Carbon::createFromFormat('Y-m-d', $startString); - } - - // parse end date - $endDate = Carbon::now(); - $endString = $this->option('end_date'); - if (null !== $endString && '' !== $endString) { - $endDate = Carbon::createFromFormat('Y-m-d', $endString); - } - - if ($startDate > $endDate) { - [$endDate, $startDate] = [$startDate, $endDate]; - } - - $this->startDate = $startDate; - $this->endDate = $endDate; - } - - /** - * @return bool - * @throws \FireflyIII\Exceptions\FireflyException - */ - private function verifyInput(): bool - { - // verify account. - $result = $this->verifyInputAccounts(); - if (false === $result) { - return $result; - } - - // verify rule groups. - $result = $this->verifyRuleGroups(); - if (false === $result) { - return $result; - } - - // verify rules. - $result = $this->verifyRules(); - if (false === $result) { - return $result; - } - - $this->grabAllRules(); - $this->parseDates(); - - //$this->line('Number of rules found: ' . $this->rules->count()); - $this->line('Start date is ' . $this->startDate->format('Y-m-d')); - $this->line('End date is ' . $this->endDate->format('Y-m-d')); - - return true; - } - - /** - * @return bool - * @throws \FireflyIII\Exceptions\FireflyException - */ - private function verifyInputAccounts(): bool - { - $accountString = $this->option('accounts'); - if (null === $accountString || '' === $accountString) { - $this->error('Please use the --accounts to indicate the accounts to apply rules to.'); - - return false; - } - $finalList = new Collection; - $accountList = explode(',', $accountString); - - if (0 === \count($accountList)) { - $this->error('Please use the --accounts to indicate the accounts to apply rules to.'); - - return false; - } - - /** @var AccountRepositoryInterface $accountRepository */ - $accountRepository = app(AccountRepositoryInterface::class); - $accountRepository->setUser($this->getUser()); - - foreach ($accountList as $accountId) { - $accountId = (int)$accountId; - $account = $accountRepository->findNull($accountId); - if (null !== $account - && \in_array( - $account->accountType->type, [AccountType::DEFAULT, AccountType::DEBT, AccountType::ASSET, AccountType::LOAN, AccountType::MORTGAGE], true - )) { - $finalList->push($account); - } - } - - if (0 === $finalList->count()) { - $this->error('Please make sure all accounts in --accounts are asset accounts or liabilities.'); - - return false; - } - $this->accounts = $finalList; - - return true; - - } - - /** - * @return bool - * @throws \FireflyIII\Exceptions\FireflyException - */ - private function verifyRuleGroups(): bool - { - $ruleGroupString = $this->option('rule_groups'); - if (null === $ruleGroupString || '' === $ruleGroupString) { - // can be empty. - return true; - } - $ruleGroupList = explode(',', $ruleGroupString); - - if (0 === \count($ruleGroupList)) { - // can be empty. - - return true; - } - /** @var RuleGroupRepositoryInterface $ruleGroupRepos */ - $ruleGroupRepos = app(RuleGroupRepositoryInterface::class); - $ruleGroupRepos->setUser($this->getUser()); - - foreach ($ruleGroupList as $ruleGroupId) { - $ruleGroupId = (int)$ruleGroupId; - $ruleGroup = $ruleGroupRepos->find($ruleGroupId); - $this->ruleGroups->push($ruleGroup); - } - - return true; - } - - /** - * @return bool - * @throws \FireflyIII\Exceptions\FireflyException - */ - private function verifyRules(): bool - { - $ruleString = $this->option('rules'); - if (null === $ruleString || '' === $ruleString) { - // can be empty. - return true; - } - $finalList = new Collection; - $ruleList = explode(',', $ruleString); - - if (0 === \count($ruleList)) { - // can be empty. - - return true; - } - /** @var RuleRepositoryInterface $ruleRepos */ - $ruleRepos = app(RuleRepositoryInterface::class); - $ruleRepos->setUser($this->getUser()); - - foreach ($ruleList as $ruleId) { - $ruleId = (int)$ruleId; - $rule = $ruleRepos->find($ruleId); - if (null !== $rule) { - $finalList->push($rule); - } - } - if ($finalList->count() > 0) { - // reset rule groups. - $this->ruleGroups = new Collection; - $this->rules = $finalList; - } - - return true; - } - - -} diff --git a/app/Console/Commands/Correction/CorrectDatabase.php b/app/Console/Commands/Correction/CorrectDatabase.php new file mode 100644 index 0000000000..c95b7b9db7 --- /dev/null +++ b/app/Console/Commands/Correction/CorrectDatabase.php @@ -0,0 +1,83 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Console\Commands\Correction; + + +use Artisan; +use Illuminate\Console\Command; +use Schema; + +/** + * Class CorrectDatabase + * @codeCoverageIgnore + */ +class CorrectDatabase extends Command +{ + /** + * The console command description. + * + * @var string + */ + protected $description = 'Will correct the integrity of your database, if necessary.'; + /** + * The name and signature of the console command. + * + * @var string + */ + protected $signature = 'firefly-iii:correct-database'; + + /** + * Execute the console command. + */ + public function handle(): int + { + // if table does not exist, return false + if (!Schema::hasTable('users')) { + return 1; + } + $commands = [ + 'firefly-iii:fix-piggies', + 'firefly-iii:create-link-types', + 'firefly-iii:create-access-tokens', + 'firefly-iii:remove-bills', + 'firefly-iii:enable-currencies', + 'firefly-iii:fix-transfer-budgets', + 'firefly-iii:fix-uneven-amount', + 'firefly-iii:delete-zero-amount', + 'firefly-iii:delete-orphaned-transactions', + 'firefly-iii:delete-empty-journals', + 'firefly-iii:delete-empty-groups', + 'firefly-iii:fix-account-types', + 'firefly-iii:rename-meta-fields', + ]; + foreach ($commands as $command) { + $this->line(sprintf('Now executing %s', $command)); + Artisan::call($command); + $result = Artisan::output(); + echo $result; + } + + return 0; + } +} \ No newline at end of file diff --git a/app/Console/Commands/Correction/CorrectionSkeleton.php.stub b/app/Console/Commands/Correction/CorrectionSkeleton.php.stub new file mode 100644 index 0000000000..1e514b5177 --- /dev/null +++ b/app/Console/Commands/Correction/CorrectionSkeleton.php.stub @@ -0,0 +1,57 @@ +. + */ + +namespace FireflyIII\Console\Commands\Correction; + +use Illuminate\Console\Command; + +/** + * Class CorrectionSkeleton + */ +class CorrectionSkeleton extends Command +{ + /** + * The console command description. + * + * @var string + */ + protected $description = 'DESCRIPTION HERE'; + /** + * The name and signature of the console command. + * + * @var string + */ + protected $signature = 'firefly-iii:CORR_COMMAND'; + + /** + * Execute the console command. + * + * @return int + */ + public function handle(): int + { + // + $this->warn('Congrats, you found the skeleton command. Boo!'); + + + return 0; + } +} diff --git a/app/Console/Commands/Correction/CreateAccessTokens.php b/app/Console/Commands/Correction/CreateAccessTokens.php new file mode 100644 index 0000000000..d2f8602d21 --- /dev/null +++ b/app/Console/Commands/Correction/CreateAccessTokens.php @@ -0,0 +1,80 @@ +. + */ + +namespace FireflyIII\Console\Commands\Correction; + +use Exception; +use FireflyIII\Repositories\User\UserRepositoryInterface; +use FireflyIII\User; +use Illuminate\Console\Command; + +/** + * Class CreateAccessTokens + */ +class CreateAccessTokens extends Command +{ + /** + * The console command description. + * + * @var string + */ + protected $description = 'Creates user access tokens which are used for command line access to personal data.'; + /** + * The name and signature of the console command. + * + * @var string + */ + protected $signature = 'firefly-iii:create-access-tokens'; + + /** + * Execute the console command. + * + * @return int + * @throws Exception + */ + public function handle(): int + { + // make repository: + /** @var UserRepositoryInterface $repository */ + $repository = app(UserRepositoryInterface::class); + + $start = microtime(true); + $count = 0; + $users = $repository->all(); + /** @var User $user */ + foreach ($users as $user) { + $pref = app('preferences')->getForUser($user, 'access_token', null); + if (null === $pref) { + $token = $user->generateAccessToken(); + app('preferences')->setForUser($user, 'access_token', $token); + $this->line(sprintf('Generated access token for user %s', $user->email)); + ++$count; + } + } + if (0 === $count) { + $this->info('All access tokens OK!'); + } + $end = round(microtime(true) - $start, 2); + $this->info(sprintf('Verify access tokens in %s seconds.', $end)); + + return 0; + } +} diff --git a/app/Console/Commands/Correction/CreateLinkTypes.php b/app/Console/Commands/Correction/CreateLinkTypes.php new file mode 100644 index 0000000000..dfb9ade9b6 --- /dev/null +++ b/app/Console/Commands/Correction/CreateLinkTypes.php @@ -0,0 +1,82 @@ +. + */ + +namespace FireflyIII\Console\Commands\Correction; + +use FireflyIII\Models\LinkType; +use Illuminate\Console\Command; + +/** + * Class CreateLinkTypes. Created all link types in case a migration hasn't fired. + */ +class CreateLinkTypes extends Command +{ + /** + * The console command description. + * + * @var string + */ + protected $description = 'Creates all link types.'; + /** + * The name and signature of the console command. + * + * @var string + */ + protected $signature = 'firefly-iii:create-link-types'; + + /** + * Execute the console command. + * + * @return mixed + */ + public function handle(): int + { + $start = microtime(true); + $count = 0; + $set = [ + 'Related' => ['relates to', 'relates to'], + 'Refund' => ['(partially) refunds', 'is (partially) refunded by'], + 'Paid' => ['(partially) pays for', 'is (partially) paid for by'], + 'Reimbursement' => ['(partially) reimburses', 'is (partially) reimbursed by'], + ]; + foreach ($set as $name => $values) { + $link = LinkType::where('name', $name) + ->first(); + if (null === $link) { + $link = new LinkType; + $link->name = $name; + $link->inward = $values[1]; + $link->outward = $values[0]; + ++$count; + $this->line(sprintf('Created missing link type "%s"', $name)); + } + $link->editable = false; + $link->save(); + } + if (0 === $count) { + $this->info('All link types OK!'); + } + $end = round(microtime(true) - $start, 2); + $this->info(sprintf('Verified link types in %s seconds', $end)); + + return 0; + } +} diff --git a/app/Console/Commands/Correction/DeleteEmptyGroups.php b/app/Console/Commands/Correction/DeleteEmptyGroups.php new file mode 100644 index 0000000000..2a45cab77b --- /dev/null +++ b/app/Console/Commands/Correction/DeleteEmptyGroups.php @@ -0,0 +1,70 @@ +. + */ + +namespace FireflyIII\Console\Commands\Correction; + +use Exception; +use FireflyIII\Models\TransactionGroup; +use FireflyIII\Models\TransactionJournal; +use Illuminate\Console\Command; + +/** + * Class DeleteEmptyGroups + */ +class DeleteEmptyGroups extends Command +{ + /** + * The console command description. + * + * @var string + */ + protected $description = 'Delete empty transaction groups.'; + /** + * The name and signature of the console command. + * + * @var string + */ + protected $signature = 'firefly-iii:delete-empty-groups'; + + /** + * Execute the console command. + * + * @return mixed + * @throws Exception; + */ + public function handle(): int + { + $start = microtime(true); + $groups = array_unique(TransactionJournal::get(['transaction_group_id'])->pluck('transaction_group_id')->toArray()); + $count = TransactionGroup::whereNull('deleted_at')->whereNotIn('id', $groups)->count(); + if (0 === $count) { + $this->info('No empty transaction groups.'); + } + if ($count > 0) { + $this->info(sprintf('Deleted %d empty transaction group(s).', $count)); + TransactionGroup::whereNull('deleted_at')->whereNotIn('id', $groups)->delete(); + } + $end = round(microtime(true) - $start, 2); + $this->info(sprintf('Verified empty groups in %s seconds', $end)); + + return 0; + } +} diff --git a/app/Console/Commands/Correction/DeleteEmptyJournals.php b/app/Console/Commands/Correction/DeleteEmptyJournals.php new file mode 100644 index 0000000000..54bd7fd05f --- /dev/null +++ b/app/Console/Commands/Correction/DeleteEmptyJournals.php @@ -0,0 +1,123 @@ +. + */ + +namespace FireflyIII\Console\Commands\Correction; + +use DB; +use Exception; +use FireflyIII\Models\Transaction; +use FireflyIII\Models\TransactionJournal; +use Illuminate\Console\Command; +use Log; + +/** + * Class DeleteEmptyJournals + */ +class DeleteEmptyJournals extends Command +{ + /** + * The console command description. + * + * @var string + */ + protected $description = 'Delete empty and uneven transaction journals.'; + /** + * The name and signature of the console command. + * + * @var string + */ + protected $signature = 'firefly-iii:delete-empty-journals'; + + /** + * Execute the console command. + * + * @return int + */ + public function handle(): int + { + $this->deleteUnevenJournals(); + $this->deleteEmptyJournals(); + + + return 0; + } + + private function deleteEmptyJournals(): void + { + $start = microtime(true); + $count = 0; + $set = TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') + ->groupBy('transaction_journals.id') + ->whereNull('transactions.transaction_journal_id') + ->get(['transaction_journals.id']); + + foreach ($set as $entry) { + try { + TransactionJournal::find($entry->id)->delete(); + // @codeCoverageIgnoreStart + } catch (Exception $e) { + Log::info(sprintf('Could not delete entry: %s', $e->getMessage())); + } + // @codeCoverageIgnoreEnd + + $this->info(sprintf('Deleted empty transaction journal #%d', $entry->id)); + ++$count; + } + if (0 === $count) { + $this->info('No empty transaction journals.'); + } + $end = round(microtime(true) - $start, 2); + $this->info(sprintf('Verified empty journals in %s seconds', $end)); + } + + /** + * Delete transactions and their journals if they have an uneven number of transactions. + */ + private function deleteUnevenJournals(): void + { + $set = Transaction + ::whereNull('deleted_at') + ->groupBy('transactions.transaction_journal_id') + ->get([DB::raw('COUNT(transactions.transaction_journal_id) as the_count'), 'transaction_journal_id']); + $total = 0; + foreach ($set as $row) { + $count = (int)$row->the_count; + if (1 === $count % 2) { + // uneven number, delete journal and transactions: + try { + TransactionJournal::find((int)$row->transaction_journal_id)->delete(); + // @codeCoverageIgnoreStart + } catch (Exception $e) { + Log::info(sprintf('Could not delete journal: %s', $e->getMessage())); + } + // @codeCoverageIgnoreEnd + + Transaction::where('transaction_journal_id', (int)$row->transaction_journal_id)->delete(); + $this->info(sprintf('Deleted transaction journal #%d because it had an uneven number of transactions.', $row->transaction_journal_id)); + $total++; + } + } + if (0 === $total) { + $this->info('No uneven transaction journals.'); + } + } + +} diff --git a/app/Console/Commands/Correction/DeleteOrphanedTransactions.php b/app/Console/Commands/Correction/DeleteOrphanedTransactions.php new file mode 100644 index 0000000000..7b433ba3f4 --- /dev/null +++ b/app/Console/Commands/Correction/DeleteOrphanedTransactions.php @@ -0,0 +1,137 @@ +. + */ + +namespace FireflyIII\Console\Commands\Correction; + +use Exception; +use FireflyIII\Models\Transaction; +use FireflyIII\Models\TransactionJournal; +use Illuminate\Console\Command; +use Log; +use stdClass; + +/** + * Deletes transactions where the journal has been deleted. + */ +class DeleteOrphanedTransactions extends Command +{ + /** + * The console command description. + * + * @var string + */ + protected $description = 'Deletes orphaned transactions.'; + /** + * The name and signature of the console command. + * + * @var string + */ + protected $signature = 'firefly-iii:delete-orphaned-transactions'; + + /** + * Execute the console command. + * + * @return int + * @throws Exception + */ + public function handle(): int + { + $start = microtime(true); + $this->deleteOrphanedTransactions(); + $this->deleteFromOrphanedAccounts(); + $end = round(microtime(true) - $start, 2); + $this->info(sprintf('Verified orphans in %s seconds', $end)); + + return 0; + } + + /** + * + */ + private function deleteFromOrphanedAccounts(): void + { + $set + = Transaction + ::leftJoin('accounts', 'transactions.account_id', '=', 'accounts.id') + ->whereNotNull('accounts.deleted_at') + ->get(['transactions.*']); + $count = 0; + /** @var Transaction $transaction */ + foreach ($set as $transaction) { + // delete journals + $journal = TransactionJournal::find((int)$transaction->transaction_journal_id); + if ($journal) { + try { + $journal->delete(); + // @codeCoverageIgnoreStart + } catch (Exception $e) { + Log::info(sprintf('Could not delete journal %s', $e->getMessage())); + } + // @codeCoverageIgnoreEnd + } + Transaction::where('transaction_journal_id', (int)$transaction->transaction_journal_id)->delete(); + $this->line( + sprintf('Deleted transaction journal #%d because account #%d was already deleted.', + $transaction->transaction_journal_id, $transaction->account_id) + ); + $count++; + } + if (0 === $count) { + $this->info('No orphaned accounts.'); + } + } + + /** + * @throws Exception + */ + private function deleteOrphanedTransactions(): void + { + $count = 0; + $set = Transaction + ::leftJoin('transaction_journals', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') + ->whereNotNull('transaction_journals.deleted_at') + ->whereNull('transactions.deleted_at') + ->whereNotNull('transactions.id') + ->get( + [ + 'transaction_journals.id as journal_id', + 'transactions.id as transaction_id', + ] + ); + /** @var stdClass $entry */ + foreach ($set as $entry) { + $transaction = Transaction::find((int)$entry->transaction_id); + $transaction->delete(); + $this->info( + sprintf( + 'Transaction #%d (part of deleted transaction journal #%d) has been deleted as well.', + $entry->transaction_id, + $entry->journal_id + ) + ); + ++$count; + } + if (0 === $count) { + $this->info('No orphaned transactions.'); + } + + } +} diff --git a/app/Console/Commands/Correction/DeleteZeroAmount.php b/app/Console/Commands/Correction/DeleteZeroAmount.php new file mode 100644 index 0000000000..c71d0443f3 --- /dev/null +++ b/app/Console/Commands/Correction/DeleteZeroAmount.php @@ -0,0 +1,80 @@ +. + */ + +namespace FireflyIII\Console\Commands\Correction; + +use Exception; +use FireflyIII\Models\Transaction; +use FireflyIII\Models\TransactionJournal; +use Illuminate\Console\Command; +use Illuminate\Support\Collection; + +/** + * Class DeleteZeroAmount + */ +class DeleteZeroAmount extends Command +{ + /** + * The console command description. + * + * @var string + */ + protected $description = 'Delete transactions with zero amount.'; + /** + * The name and signature of the console command. + * + * @var string + */ + protected $signature = 'firefly-iii:delete-zero-amount'; + + /** + * Execute the console command. + * @return int + */ + public function handle(): int + { + $start = microtime(true); + $set = Transaction::where('amount', 0)->get(['transaction_journal_id'])->pluck('transaction_journal_id')->toArray(); + $set = array_unique($set); + /** @var Collection $journals */ + $journals = TransactionJournal::whereIn('id', $set)->get(); + /** @var TransactionJournal $journal */ + foreach ($journals as $journal) { + $this->info(sprintf('Deleted transaction journal #%d because the amount is zero (0.00).', $journal->id)); + try { + $journal->delete(); + // @codeCoverageIgnoreStart + } catch (Exception $e) { + $this->line($e->getMessage()); + } + // @codeCoverageIgnoreEnd + Transaction::where('transaction_journal_id', $journal->id)->delete(); + } + if (0 === $journals->count()) { + $this->info('No zero-amount transaction journals.'); + } + + $end = round(microtime(true) - $start, 2); + $this->info(sprintf('Verified zero-amount integrity in %s seconds', $end)); + + return 0; + } +} diff --git a/app/Console/Commands/Correction/EnableCurrencies.php b/app/Console/Commands/Correction/EnableCurrencies.php new file mode 100644 index 0000000000..acfb43f05a --- /dev/null +++ b/app/Console/Commands/Correction/EnableCurrencies.php @@ -0,0 +1,104 @@ +. + */ + +namespace FireflyIII\Console\Commands\Correction; + +use FireflyIII\Models\AccountMeta; +use FireflyIII\Models\BudgetLimit; +use FireflyIII\Models\Transaction; +use FireflyIII\Models\TransactionCurrency; +use FireflyIII\Models\TransactionJournal; +use Illuminate\Console\Command; +use Illuminate\Support\Collection; + +/** + * Class EnableCurrencies + */ +class EnableCurrencies extends Command +{ + /** + * The console command description. + * + * @var string + */ + protected $description = 'Enables all currencies in use.'; + /** + * The name and signature of the console command. + * + * @var string + */ + protected $signature = 'firefly-iii:enable-currencies'; + + /** + * Execute the console command. + * + * @return mixed + */ + public function handle(): int + { + $start = microtime(true); + $found = []; + // get all meta entries + /** @var Collection $meta */ + $meta = AccountMeta::where('name', 'currency_id')->groupBy('data')->get(['data']); + foreach ($meta as $entry) { + $found[] = (int)$entry->data; + } + + // get all from journals: + /** @var Collection $journals */ + $journals = TransactionJournal::groupBy('transaction_currency_id')->get(['transaction_currency_id']); + foreach ($journals as $entry) { + $found[] = (int)$entry->transaction_currency_id; + } + + // get all from transactions + /** @var Collection $transactions */ + $transactions = Transaction::groupBy('transaction_currency_id')->get(['transaction_currency_id']); + foreach ($transactions as $entry) { + $found[] = (int)$entry->transaction_currency_id; + } + + // get all from budget limits + /** @var Collection $limits */ + $limits = BudgetLimit::groupBy('transaction_currency_id')->get(['transaction_currency_id']); + foreach ($limits as $entry) { + $found[] = (int)$entry->transaction_currency_id; + } + + $found = array_unique($found); + $this->info(sprintf('%d different currencies are currently in use.', count($found))); + + $disabled = TransactionCurrency::whereIn('id', $found)->where('enabled', false)->count(); + if ($disabled > 0) { + $this->info(sprintf('%d were (was) still disabled. This has been corrected.', $disabled)); + } + if (0 === $disabled) { + $this->info('All currencies are correctly enabled or disabled.'); + } + TransactionCurrency::whereIn('id', $found)->update(['enabled' => true]); + + $end = round(microtime(true) - $start, 2); + $this->info(sprintf('Verified currencies in %s seconds.', $end)); + + return 0; + } +} diff --git a/app/Console/Commands/Correction/FixAccountTypes.php b/app/Console/Commands/Correction/FixAccountTypes.php new file mode 100644 index 0000000000..0fd0b16af7 --- /dev/null +++ b/app/Console/Commands/Correction/FixAccountTypes.php @@ -0,0 +1,255 @@ +. + */ + +namespace FireflyIII\Console\Commands\Correction; + +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Factory\AccountFactory; +use FireflyIII\Models\AccountType; +use FireflyIII\Models\Transaction; +use FireflyIII\Models\TransactionJournal; +use FireflyIII\Models\TransactionType; +use Illuminate\Console\Command; + +/** + * Class FixAccountTypes + */ +class FixAccountTypes extends Command +{ + /** + * The console command description. + * + * @var string + */ + protected $description = 'Make sure all journals have the correct from/to account types.'; + /** + * The name and signature of the console command. + * + * @var string + */ + protected $signature = 'firefly-iii:fix-account-types'; + /** @var array */ + private $expected; + /** @var AccountFactory */ + private $factory; + /** @var array */ + private $fixable; + /** @var int */ + private $count; + + /** + * Execute the console command. + * + * @return int + * @throws FireflyException + */ + public function handle(): int + { + $this->stupidLaravel(); + $start = microtime(true); + $this->factory = app(AccountFactory::class); + // some combinations can be fixed by this script: + $this->fixable = [ + // transfers from asset to liability and vice versa + sprintf('%s%s%s', TransactionType::TRANSFER, AccountType::ASSET, AccountType::LOAN), + sprintf('%s%s%s', TransactionType::TRANSFER, AccountType::ASSET, AccountType::DEBT), + sprintf('%s%s%s', TransactionType::TRANSFER, AccountType::ASSET, AccountType::MORTGAGE), + sprintf('%s%s%s', TransactionType::TRANSFER, AccountType::LOAN, AccountType::ASSET), + sprintf('%s%s%s', TransactionType::TRANSFER, AccountType::DEBT, AccountType::ASSET), + sprintf('%s%s%s', TransactionType::TRANSFER, AccountType::MORTGAGE, AccountType::ASSET), + + // withdrawals with a revenue account as destination instead of an expense account. + sprintf('%s%s%s', TransactionType::WITHDRAWAL, AccountType::ASSET, AccountType::REVENUE), + + // deposits with an expense account as source instead of a revenue account. + sprintf('%s%s%s', TransactionType::DEPOSIT, AccountType::EXPENSE, AccountType::ASSET), + ]; + + + $this->expected = config('firefly.source_dests'); + $journals = TransactionJournal::with(['TransactionType', 'transactions', 'transactions.account', 'transactions.account.accounttype'])->get(); + foreach ($journals as $journal) { + $this->inspectJournal($journal); + } + if (0 === $this->count) { + $this->info('All account types are OK!'); + } + if (0 !== $this->count) { + $this->info(sprintf('Acted on %d transaction(s)!', $this->count)); + } + + $end = round(microtime(true) - $start, 2); + $this->info(sprintf('Verifying account types took %s seconds', $end)); + + return 0; + } + + /** + * Laravel will execute ALL __construct() methods for ALL commands whenever a SINGLE command is + * executed. This leads to noticeable slow-downs and class calls. To prevent this, this method should + * be called from the handle method instead of using the constructor to initialize the command. + * + * @codeCoverageIgnore + */ + private function stupidLaravel(): void + { + $this->count = 0; + } + + /** + * @param TransactionJournal $journal + * @param string $type + * @param Transaction $source + * @param Transaction $dest + * @throws FireflyException + */ + private function fixJournal(TransactionJournal $journal, string $type, Transaction $source, Transaction $dest): void + { + $this->count++; + // variables: + $combination = sprintf('%s%s%s', $type, $source->account->accountType->type, $dest->account->accountType->type); + + switch ($combination) { + case sprintf('%s%s%s', TransactionType::TRANSFER, AccountType::ASSET, AccountType::LOAN): + case sprintf('%s%s%s', TransactionType::TRANSFER, AccountType::ASSET, AccountType::DEBT): + case sprintf('%s%s%s', TransactionType::TRANSFER, AccountType::ASSET, AccountType::MORTGAGE): + // from an asset to a liability should be a withdrawal: + $withdrawal = TransactionType::whereType(TransactionType::WITHDRAWAL)->first(); + $journal->transactionType()->associate($withdrawal); + $journal->save(); + $this->info(sprintf('Converted transaction #%d from a transfer to a withdrawal.', $journal->id)); + // check it again: + $this->inspectJournal($journal); + break; + case sprintf('%s%s%s', TransactionType::TRANSFER, AccountType::LOAN, AccountType::ASSET): + case sprintf('%s%s%s', TransactionType::TRANSFER, AccountType::DEBT, AccountType::ASSET): + case sprintf('%s%s%s', TransactionType::TRANSFER, AccountType::MORTGAGE, AccountType::ASSET): + // from a liability to an asset should be a deposit. + $deposit = TransactionType::whereType(TransactionType::DEPOSIT)->first(); + $journal->transactionType()->associate($deposit); + $journal->save(); + $this->info(sprintf('Converted transaction #%d from a transfer to a deposit.', $journal->id)); + // check it again: + $this->inspectJournal($journal); + + break; + case sprintf('%s%s%s', TransactionType::WITHDRAWAL, AccountType::ASSET, AccountType::REVENUE): + // withdrawals with a revenue account as destination instead of an expense account. + $this->factory->setUser($journal->user); + $oldDest = $dest->account; + $result = $this->factory->findOrCreate($dest->account->name, AccountType::EXPENSE); + $dest->account()->associate($result); + $dest->save(); + $this->info( + sprintf( + 'Transaction journal #%d, destination account changed from #%d ("%s") to #%d ("%s").', $journal->id, + $oldDest->id, $oldDest->name, + $result->id, $result->name + ) + ); + $this->inspectJournal($journal); + break; + case sprintf('%s%s%s', TransactionType::DEPOSIT, AccountType::EXPENSE, AccountType::ASSET): + // deposits with an expense account as source instead of a revenue account. + // find revenue account. + $this->factory->setUser($journal->user); + $result = $this->factory->findOrCreate($source->account->name, AccountType::REVENUE); + $oldSource = $dest->account; + $source->account()->associate($result); + $source->save(); + $this->info( + sprintf( + 'Transaction journal #%d, source account changed from #%d ("%s") to #%d ("%s").', $journal->id, + $oldSource->id, $oldSource->name, + $result->id, $result->name + ) + ); + $this->inspectJournal($journal); + break; + default: + $this->info(sprintf('The source account of %s #%d cannot be of type "%s".', $type, $journal->id, $source->account->accountType->type)); + $this->info(sprintf('The destination account of %s #%d cannot be of type "%s".', $type, $journal->id, $dest->account->accountType->type)); + + break; + + } + + } + + /** + * @param TransactionJournal $journal + * + * @return Transaction + */ + private function getDestinationTransaction(TransactionJournal $journal): Transaction + { + return $journal->transactions->firstWhere('amount', '>', 0); + } + + /** + * @param TransactionJournal $journal + * + * @return Transaction + */ + private function getSourceTransaction(TransactionJournal $journal): Transaction + { + return $journal->transactions->firstWhere('amount', '<', 0); + } + + /** + * @param TransactionJournal $journal + * + * @throws FireflyException + */ + private function inspectJournal(TransactionJournal $journal): void + { + $count = $journal->transactions()->count(); + if (2 !== $count) { + $this->info(sprintf('Cannot inspect transaction journal #%d because it has %d transaction(s) instead of 2.', $journal->id, $count)); + + return; + } + $type = $journal->transactionType->type; + $sourceTransaction = $this->getSourceTransaction($journal); + $sourceAccount = $sourceTransaction->account; + $sourceAccountType = $sourceAccount->accountType->type; + $destTransaction = $this->getDestinationTransaction($journal); + $destAccount = $destTransaction->account; + $destAccountType = $destAccount->accountType->type; + if (!isset($this->expected[$type])) { + // @codeCoverageIgnoreStart + $this->info(sprintf('No source/destination info for transaction type %s.', $type)); + + return; + // @codeCoverageIgnoreEnd + } + if (!isset($this->expected[$type][$sourceAccountType])) { + $this->fixJournal($journal, $type, $sourceTransaction, $destTransaction); + + return; + } + $expectedTypes = $this->expected[$type][$sourceAccountType]; + if (!in_array($destAccountType, $expectedTypes, true)) { + $this->fixJournal($journal, $type, $sourceTransaction, $destTransaction); + } + } + +} diff --git a/app/Console/Commands/Correction/FixPiggies.php b/app/Console/Commands/Correction/FixPiggies.php new file mode 100644 index 0000000000..5cfcd81571 --- /dev/null +++ b/app/Console/Commands/Correction/FixPiggies.php @@ -0,0 +1,101 @@ +. + */ + +namespace FireflyIII\Console\Commands\Correction; + +use FireflyIII\Models\PiggyBankEvent; +use FireflyIII\Models\TransactionJournal; +use FireflyIII\Models\TransactionType; +use Illuminate\Console\Command; + +/** + * Report (and fix) piggy banks. Make sure there are only transfers linked to piggy bank events. + * + * Class FixPiggies + */ +class FixPiggies extends Command +{ + /** + * The console command description. + * + * @var string + */ + protected $description = 'Fixes common issues with piggy banks.'; + /** + * The name and signature of the console command. + * + * @var string + */ + protected $signature = 'firefly-iii:fix-piggies'; + + /** @var int */ + private $count; + + /** + * Execute the console command. + * + * @return mixed + */ + public function handle(): int + { + $this->count = 0; + $start = microtime(true); + $set = PiggyBankEvent::with(['PiggyBank', 'TransactionJournal', 'TransactionJournal.TransactionType'])->get(); + + /** @var PiggyBankEvent $event */ + foreach ($set as $event) { + + if (null === $event->transaction_journal_id) { + continue; + } + /** @var TransactionJournal $journal */ + $journal = $event->transactionJournal; + // @codeCoverageIgnoreStart + if (null === $journal) { + $event->transaction_journal_id = null; + $event->save(); + $this->count++; + continue; + } + // @codeCoverageIgnoreEnd + + $type = $journal->transactionType->type; + if (TransactionType::TRANSFER !== $type) { + $event->transaction_journal_id = null; + $event->save(); + $this->line(sprintf('Piggy bank #%d was referenced by an invalid event. This has been fixed.', $event->piggy_bank_id)); + $this->count++; + continue; + } + } + if (0 === $this->count) { + $this->line('All piggy bank events are correct.'); + } + if (0 !== $this->count) { + $this->line(sprintf('Fixed %d piggy bank event(s).', $this->count)); + } + + $end = round(microtime(true) - $start, 2); + $this->line(sprintf('Verified the content of %d piggy bank events in %s seconds.', $set->count(), $end)); + + return 0; + } +} diff --git a/app/Console/Commands/Correction/FixUnevenAmount.php b/app/Console/Commands/Correction/FixUnevenAmount.php new file mode 100644 index 0000000000..251d22bca4 --- /dev/null +++ b/app/Console/Commands/Correction/FixUnevenAmount.php @@ -0,0 +1,102 @@ +. + */ + +namespace FireflyIII\Console\Commands\Correction; + +use DB; +use FireflyIII\Models\Transaction; +use FireflyIII\Models\TransactionJournal; +use Illuminate\Console\Command; +use stdClass; + +/** + * Class FixUnevenAmount + */ +class FixUnevenAmount extends Command +{ + /** + * The console command description. + * + * @var string + */ + protected $description = 'Fix journals with uneven amounts.'; + /** + * The name and signature of the console command. + * + * @var string + */ + protected $signature = 'firefly-iii:fix-uneven-amount'; + + /** + * Execute the console command. + * + * @return int + */ + public function handle(): int + { + $start = microtime(true); + $count = 0; + // get invalid journals + $journals = DB::table('transactions') + ->groupBy('transaction_journal_id') + ->whereNull('deleted_at') + ->get(['transaction_journal_id', DB::raw('SUM(amount) AS the_sum')]); + /** @var stdClass $entry */ + foreach ($journals as $entry) { + if (0 !== bccomp((string)$entry->the_sum, '0')) { + $this->fixJournal((int)$entry->transaction_journal_id); + $count++; + } + } + if (0 === $count) { + $this->info('Amount integrity OK!'); + } + + $end = round(microtime(true) - $start, 2); + $this->info(sprintf('Verified amount integrity in %s seconds', $end)); + + return 0; + } + + /** + * @param int $param + */ + private function fixJournal(int $param): void + { + // one of the transactions is bad. + $journal = TransactionJournal::find($param); + if (!$journal) { + return; // @codeCoverageIgnore + } + /** @var Transaction $source */ + $source = $journal->transactions()->where('amount', '<', 0)->first(); + $amount = bcmul('-1', (string)$source->amount); + + // fix amount of destination: + /** @var Transaction $destination */ + $destination = $journal->transactions()->where('amount', '>', 0)->first(); + $destination->amount = $amount; + $destination->save(); + + $message = sprintf('Corrected amount in transaction journal #%d', $param); + $this->line($message); + } +} diff --git a/app/Console/Commands/Correction/RemoveBills.php b/app/Console/Commands/Correction/RemoveBills.php new file mode 100644 index 0000000000..2dfb2f8588 --- /dev/null +++ b/app/Console/Commands/Correction/RemoveBills.php @@ -0,0 +1,74 @@ +. + */ + +namespace FireflyIII\Console\Commands\Correction; + +use FireflyIII\Models\TransactionJournal; +use FireflyIII\Models\TransactionType; +use Illuminate\Console\Command; + +/** + * Class RemoveBills + */ +class RemoveBills extends Command +{ + /** + * The console command description. + * + * @var string + */ + protected $description = 'Remove bills from transactions that shouldn\'t have one.'; + /** + * The name and signature of the console command. + * + * @var string + */ + protected $signature = 'firefly-iii:remove-bills'; + + /** + * Execute the console command. + * + * @return mixed + */ + public function handle(): int + { + $start = microtime(true); + /** @var TransactionType $withdrawal */ + $withdrawal = TransactionType::where('type', TransactionType::WITHDRAWAL)->first(); + $journals = TransactionJournal::whereNotNull('bill_id')->where('transaction_type_id', '!=', $withdrawal->id)->get(); + /** @var TransactionJournal $journal */ + foreach ($journals as $journal) { + $this->line(sprintf('Transaction journal #%d should not be linked to bill #%d.', $journal->id, $journal->bill_id)); + $journal->bill_id = null; + $journal->save(); + } + if (0 === $journals->count()) { + $this->info('All transaction journals have correct bill information.'); + } + if ($journals->count() > 0) { + $this->info('Fixed all transaction journals so they have correct bill information.'); + } + $end = round(microtime(true) - $start, 2); + $this->info(sprintf('Verified bills / journals in %s seconds', $end)); + + return 0; + } +} diff --git a/app/Console/Commands/Correction/RenameMetaFields.php b/app/Console/Commands/Correction/RenameMetaFields.php new file mode 100644 index 0000000000..5758376a9c --- /dev/null +++ b/app/Console/Commands/Correction/RenameMetaFields.php @@ -0,0 +1,98 @@ +. + */ + +namespace FireflyIII\Console\Commands\Correction; + +use DB; +use Illuminate\Console\Command; + +/** + * Class RenameMetaFields + */ +class RenameMetaFields extends Command +{ + /** + * The console command description. + * + * @var string + */ + protected $description = 'Rename changed meta fields.'; + /** + * The name and signature of the console command. + * + * @var string + */ + protected $signature = 'firefly-iii:rename-meta-fields'; + + /** @var int */ + private $count; + + /** + * Execute the console command. + * + * @return int + */ + public function handle(): int + { + $this->count = 0; + $start = microtime(true); + + $changes = [ + 'original-source' => 'original_source', + 'importHash' => 'import_hash', + 'importHashV2' => 'import_hash_v2', + 'sepa-cc' => 'sepa_cc', + 'sepa-ct-op' => 'sepa_ct_op', + 'sepa-ct-id' => 'sepa_ct_id', + 'sepa-db' => 'sepa_db', + 'sepa-country' => 'sepa_country', + 'sepa-ep' => 'sepa_ep', + 'sepa-ci' => 'sepa_ci', + 'sepa-batch-id' => 'sepa_batch_id', + ]; + foreach ($changes as $original => $update) { + $this->rename($original, $update); + } + if (0 === $this->count) { + $this->line('All meta fields are correct.'); + } + if (0 !== $this->count) { + $this->line(sprintf('Renamed %d meta field(s).', $this->count)); + } + + $end = round(microtime(true) - $start, 2); + $this->info(sprintf('Renamed meta fields in %s seconds', $end)); + + return 0; + } + + /** + * @param string $original + * @param string $update + */ + private function rename(string $original, string $update): void + { + $count = DB::table('journal_meta') + ->where('name', '=', $original) + ->update(['name' => $update]); + $this->count += $count; + } +} diff --git a/app/Console/Commands/Correction/TransferBudgets.php b/app/Console/Commands/Correction/TransferBudgets.php new file mode 100644 index 0000000000..533192c96c --- /dev/null +++ b/app/Console/Commands/Correction/TransferBudgets.php @@ -0,0 +1,77 @@ +. + */ + +namespace FireflyIII\Console\Commands\Correction; + +use FireflyIII\Models\TransactionJournal; +use FireflyIII\Models\TransactionType; +use Illuminate\Console\Command; + +/** + * Class TransferBudgets + */ +class TransferBudgets extends Command +{ + /** + * The console command description. + * + * @var string + */ + protected $description = 'Removes budgets from transfers.'; + /** + * The name and signature of the console command. + * + * @var string + */ + protected $signature = 'firefly-iii:fix-transfer-budgets'; + + /** + * Execute the console command. + * + * @return int + */ + public function handle(): int + { + $start = microtime(true); + $set = TransactionJournal::distinct() + ->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id') + ->leftJoin('budget_transaction_journal', 'transaction_journals.id', '=', 'budget_transaction_journal.transaction_journal_id') + ->whereNotIn('transaction_types.type', [TransactionType::WITHDRAWAL]) + ->whereNotNull('budget_transaction_journal.budget_id')->get(['transaction_journals.*']); + $count = 0; + /** @var TransactionJournal $entry */ + foreach ($set as $entry) { + $this->info(sprintf('Transaction journal #%d is a %s, so has no longer a budget.', $entry->id, $entry->transactionType->type)); + $entry->budgets()->sync([]); + $count++; + } + if (0 === $count) { + $this->info('No invalid budget/journal entries.'); + } + if (0 !== $count) { + $this->line(sprintf('Corrected %d invalid budget/journal entries (entry).', $count)); + } + $end = round(microtime(true) - $start, 2); + $this->info(sprintf('Verified budget/journals in %s seconds.', $end)); + + return 0; + } +} diff --git a/app/Console/Commands/CreateExport.php b/app/Console/Commands/CreateExport.php deleted file mode 100644 index 75390480f9..0000000000 --- a/app/Console/Commands/CreateExport.php +++ /dev/null @@ -1,150 +0,0 @@ -. - */ - -/** @noinspection MultipleReturnStatementsInspection */ - -declare(strict_types=1); - -namespace FireflyIII\Console\Commands; - -use Carbon\Carbon; -use FireflyIII\Export\ProcessorInterface; -use FireflyIII\Models\AccountType; -use FireflyIII\Repositories\Account\AccountRepositoryInterface; -use FireflyIII\Repositories\ExportJob\ExportJobRepositoryInterface; -use FireflyIII\Repositories\Journal\JournalRepositoryInterface; -use FireflyIII\Repositories\User\UserRepositoryInterface; -use Illuminate\Console\Command; -use Illuminate\Support\Facades\Storage; - -/** - * Class CreateExport. - * - * Generates export from the command line. - * - * @codeCoverageIgnore - */ -class CreateExport extends Command -{ - use VerifiesAccessToken; - - /** - * The console command description. - * - * @var string - */ - protected $description = 'Use this command to create a new import. Your user ID can be found on the /profile page.'; - - /** - * The name and signature of the console command. - * - * @var string - */ - protected $signature - = 'firefly:create-export - {--user= : The user ID that the import should import for.} - {--token= : The user\'s access token.} - {--with_attachments : Include user\'s attachments?} - {--with_uploads : Include user\'s uploads?}'; - - /** - * Execute the console command. - * - * @return int - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - */ - public function handle(): int - { - if (!$this->verifyAccessToken()) { - $this->error('Invalid access token.'); - - return 1; - } - $this->line('Full export is running...'); - // make repositories - /** @var UserRepositoryInterface $userRepository */ - $userRepository = app(UserRepositoryInterface::class); - /** @var ExportJobRepositoryInterface $jobRepository */ - $jobRepository = app(ExportJobRepositoryInterface::class); - /** @var AccountRepositoryInterface $accountRepository */ - $accountRepository = app(AccountRepositoryInterface::class); - /** @var JournalRepositoryInterface $journalRepository */ - $journalRepository = app(JournalRepositoryInterface::class); - - // set user - $user = $userRepository->findNull((int)$this->option('user')); - if (null === $user) { - return 1; - } - $jobRepository->setUser($user); - $journalRepository->setUser($user); - $accountRepository->setUser($user); - - // first date - $firstJournal = $journalRepository->firstNull(); - $first = new Carbon; - if (null !== $firstJournal) { - $first = $firstJournal->date; - } - - // create job and settings. - $job = $jobRepository->create(); - $settings = [ - 'accounts' => $accountRepository->getAccountsByType([AccountType::ASSET, AccountType::DEFAULT]), - 'startDate' => $first, - 'endDate' => new Carbon, - 'exportFormat' => 'csv', - 'includeAttachments' => $this->option('with_attachments'), - 'includeOldUploads' => $this->option('with_uploads'), - 'job' => $job, - ]; - - /** @var ProcessorInterface $processor */ - $processor = app(ProcessorInterface::class); - $processor->setSettings($settings); - - $processor->collectJournals(); - $processor->convertJournals(); - $processor->exportJournals(); - if ($settings['includeAttachments']) { - $processor->collectAttachments(); - } - - if ($settings['includeOldUploads']) { - $processor->collectOldUploads(); - } - - $processor->createZipFile(); - $disk = Storage::disk('export'); - $fileName = sprintf('export-%s.zip', date('Y-m-d_H-i-s')); - $localPath = storage_path('export') . '/' . $job->key . '.zip'; - - // "move" from local to export disk - $disk->put($fileName, file_get_contents($localPath)); - unlink($localPath); - - $this->line('The export has finished! You can find the ZIP file in export disk with file name:'); - $this->line($fileName); - - return 0; - } -} diff --git a/app/Console/Commands/CreateImport.php b/app/Console/Commands/CreateImport.php deleted file mode 100644 index eacf0a54b3..0000000000 --- a/app/Console/Commands/CreateImport.php +++ /dev/null @@ -1,304 +0,0 @@ -. - */ - -/** @noinspection MultipleReturnStatementsInspection */ - -declare(strict_types=1); - -namespace FireflyIII\Console\Commands; - -use Exception; -use FireflyIII\Exceptions\FireflyException; -use FireflyIII\Import\Prerequisites\PrerequisitesInterface; -use FireflyIII\Import\Routine\RoutineInterface; -use FireflyIII\Import\Storage\ImportArrayStorage; -use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; -use FireflyIII\Repositories\User\UserRepositoryInterface; -use Illuminate\Console\Command; -use Log; - -/** - * Class CreateImport. - * - * @codeCoverageIgnore - */ -class CreateImport extends Command -{ - use VerifiesAccessToken; - /** - * The console command description. - * - * @var string - */ - protected $description = 'Use this command to create a new import. Your user ID can be found on the /profile page.'; - - /** - * The name and signature of the console command. - * - * @var string - */ - protected $signature - = 'firefly:create-import - {file? : The file to import.} - {configuration? : The configuration file to use for the import.} - {--type=csv : The file type of the import.} - {--provider=file : The file type of the import.} - {--user=1 : The user ID that the import should import for.} - {--token= : The user\'s access token.} - {--start : Starts the job immediately.}'; - - /** - * Run the command. - * - * @throws FireflyException - */ - public function handle(): int - { - if (!$this->verifyAccessToken()) { - $this->errorLine('Invalid access token.'); - - return 1; - } - /** @var UserRepositoryInterface $userRepository */ - $userRepository = app(UserRepositoryInterface::class); - $file = (string)$this->argument('file'); - $configuration = (string)$this->argument('configuration'); - $user = $userRepository->findNull((int)$this->option('user')); - $cwd = getcwd(); - $provider = strtolower((string)$this->option('provider')); - $configurationData = []; - - if (null === $user) { - $this->errorLine('User is NULL.'); - - return 1; - } - - if (!$this->validArguments()) { - $this->errorLine('Invalid arguments.'); - - return 1; - } - if ('' !== $configuration) { - $configurationData = json_decode(file_get_contents($configuration), true); - if (null === $configurationData) { - $this->errorLine(sprintf('Firefly III cannot read the contents of configuration file "%s" (working directory: "%s").', $configuration, $cwd)); - - return 1; - } - } - - - $this->infoLine(sprintf('Going to create a job to import file: %s', $file)); - $this->infoLine(sprintf('Using configuration file: %s', $configuration)); - $this->infoLine(sprintf('Import into user: #%d (%s)', $user->id, $user->email)); - $this->infoLine(sprintf('Type of import: %s', $provider)); - - /** @var ImportJobRepositoryInterface $jobRepository */ - $jobRepository = app(ImportJobRepositoryInterface::class); - $jobRepository->setUser($user); - $importJob = $jobRepository->create($provider); - $this->infoLine(sprintf('Created job "%s"', $importJob->key)); - - // make sure that job has no prerequisites. - if ((bool)config(sprintf('import.has_prereq.%s', $provider))) { - // make prerequisites thing. - $class = (string)config(sprintf('import.prerequisites.%s', $provider)); - if (!class_exists($class)) { - throw new FireflyException(sprintf('No class to handle prerequisites for "%s".', $provider)); // @codeCoverageIgnore - } - /** @var PrerequisitesInterface $object */ - $object = app($class); - $object->setUser($user); - if (!$object->isComplete()) { - $this->errorLine(sprintf('Import provider "%s" has prerequisites that can only be filled in using the browser.', $provider)); - - return 1; - } - } - - // store file as attachment. - if ('' !== $file) { - $messages = $jobRepository->storeCLIUpload($importJob, 'import_file', $file); - if ($messages->count() > 0) { - $this->errorLine($messages->first()); - - return 1; - } - $this->infoLine('File content saved.'); - } - - $this->infoLine('Job configuration saved.'); - $jobRepository->setConfiguration($importJob, $configurationData); - $jobRepository->setStatus($importJob, 'ready_to_run'); - - - if (true === $this->option('start')) { - $this->infoLine('The import routine has started. The process is not visible. Please wait.'); - Log::debug('Go for import!'); - - // run it! - $key = sprintf('import.routine.%s', $provider); - $className = config($key); - if (null === $className || !class_exists($className)) { - // @codeCoverageIgnoreStart - $this->errorLine(sprintf('No routine for provider "%s"', $provider)); - - return 1; - // @codeCoverageIgnoreEnd - } - - // keep repeating this call until job lands on "provider_finished" - $valid = ['provider_finished']; - $count = 0; - while (!\in_array($importJob->status, $valid, true) && $count < 6) { - Log::debug(sprintf('Now in loop #%d.', $count + 1)); - /** @var RoutineInterface $routine */ - $routine = app($className); - $routine->setImportJob($importJob); - try { - $routine->run(); - } catch (FireflyException|Exception $e) { - $message = 'The import routine crashed: ' . $e->getMessage(); - Log::error($message); - Log::error($e->getTraceAsString()); - - // set job errored out: - $jobRepository->setStatus($importJob, 'error'); - $this->errorLine($message); - - return 1; - } - $count++; - } - if ('provider_finished' === $importJob->status) { - $this->infoLine('Import has finished. Please wait for storage of data.'); - // set job to be storing data: - $jobRepository->setStatus($importJob, 'storing_data'); - - /** @var ImportArrayStorage $storage */ - $storage = app(ImportArrayStorage::class); - $storage->setImportJob($importJob); - - try { - $storage->store(); - } catch (FireflyException|Exception $e) { - $message = 'The import routine crashed: ' . $e->getMessage(); - Log::error($message); - Log::error($e->getTraceAsString()); - - // set job errored out: - $jobRepository->setStatus($importJob, 'error'); - $this->errorLine($message); - - return 1; - } - // set storage to be finished: - $jobRepository->setStatus($importJob, 'storage_finished'); - } - - // give feedback: - $this->infoLine('Job has finished.'); - if (null !== $importJob->tag) { - $this->infoLine(sprintf('%d transaction(s) have been imported.', $importJob->tag->transactionJournals->count())); - $this->infoLine(sprintf('You can find your transactions under tag "%s"', $importJob->tag->tag)); - } - - if (null === $importJob->tag) { - $this->errorLine('No transactions have been imported :(.'); - } - if (\count($importJob->errors) > 0) { - $this->infoLine(sprintf('%d error(s) occurred:', \count($importJob->errors))); - foreach ($importJob->errors as $err) { - $this->errorLine('- ' . $err); - } - } - } - // clear cache for user: - app('preferences')->setForUser($user, 'lastActivity', microtime()); - - return 0; - } - - /** - * @param string $message - * @param array|null $data - */ - private function errorLine(string $message, array $data = null): void - { - Log::error($message, $data ?? []); - $this->error($message); - - } - - /** - * @param string $message - * @param array $data - */ - private function infoLine(string $message, array $data = null): void - { - Log::info($message, $data ?? []); - $this->line($message); - } - - /** - * Verify user inserts correct arguments. - * - * @noinspection MultipleReturnStatementsInspection - * @return bool - */ - private function validArguments(): bool - { - $file = (string)$this->argument('file'); - $configuration = (string)$this->argument('configuration'); - $cwd = getcwd(); - $validTypes = config('import.options.file.import_formats'); - $type = strtolower($this->option('type')); - $provider = strtolower($this->option('provider')); - $enabled = (bool)config(sprintf('import.enabled.%s', $provider)); - - if (false === $enabled) { - $this->errorLine(sprintf('Provider "%s" is not enabled.', $provider)); - - return false; - } - - if ('file' === $provider && !\in_array($type, $validTypes, true)) { - $this->errorLine(sprintf('Cannot import file of type "%s"', $type)); - - return false; - } - - if ('file' === $provider && !file_exists($file)) { - $this->errorLine(sprintf('Firefly III cannot find file "%s" (working directory: "%s").', $file, $cwd)); - - return false; - } - - if ('file' === $provider && !file_exists($configuration)) { - $this->errorLine(sprintf('Firefly III cannot find configuration file "%s" (working directory: "%s").', $configuration, $cwd)); - - return false; - } - - return true; - } -} diff --git a/app/Console/Commands/DecryptAttachment.php b/app/Console/Commands/DecryptAttachment.php deleted file mode 100644 index 450eedd8bd..0000000000 --- a/app/Console/Commands/DecryptAttachment.php +++ /dev/null @@ -1,111 +0,0 @@ -. - */ - -/** @noinspection MultipleReturnStatementsInspection */ - -declare(strict_types=1); - -namespace FireflyIII\Console\Commands; - -use FireflyIII\Repositories\Attachment\AttachmentRepositoryInterface; -use Illuminate\Console\Command; -use Log; - -/** - * Class DecryptAttachment. - * - * @codeCoverageIgnore - */ -class DecryptAttachment extends Command -{ - /** - * The console command description. - * - * @var string - */ - protected $description = 'Decrypts an attachment and dumps the content in a file in the given directory.'; - - /** - * The name and signature of the console command. - * - * @var string - */ - protected $signature - = 'firefly:decrypt-attachment {id:The ID of the attachment.} {name:The file name of the attachment.} - {directory:Where the file must be stored.}'; - - /** - * Execute the console command. - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - * - * @return int - */ - public function handle(): int - { - /** @var AttachmentRepositoryInterface $repository */ - $repository = app(AttachmentRepositoryInterface::class); - $attachmentId = (int)$this->argument('id'); - $attachment = $repository->findWithoutUser($attachmentId); - $attachmentName = trim((string)$this->argument('name')); - $storagePath = realpath(trim((string)$this->argument('directory'))); - if (null === $attachment) { - $this->error(sprintf('No attachment with id #%d', $attachmentId)); - Log::error(sprintf('DecryptAttachment: No attachment with id #%d', $attachmentId)); - - return 1; - } - - if ($attachmentName !== $attachment->filename) { - $this->error('File name does not match.'); - Log::error('DecryptAttachment: File name does not match.'); - - return 1; - } - - if (!is_dir($storagePath)) { - $this->error(sprintf('Path "%s" is not a directory.', $storagePath)); - Log::error(sprintf('DecryptAttachment: Path "%s" is not a directory.', $storagePath)); - - return 1; - } - - if (!is_writable($storagePath)) { - $this->error(sprintf('Path "%s" is not writable.', $storagePath)); - Log::error(sprintf('DecryptAttachment: Path "%s" is not writable.', $storagePath)); - - return 1; - } - - $fullPath = $storagePath . DIRECTORY_SEPARATOR . $attachment->filename; - $content = $repository->getContent($attachment); - $this->line(sprintf('Going to write content for attachment #%d into file "%s"', $attachment->id, $fullPath)); - $result = file_put_contents($fullPath, $content); - if (false === $result) { - $this->error('Could not write to file.'); - - return 1; - } - $this->info(sprintf('%d bytes written. Exiting now..', $result)); - - return 0; - } -} diff --git a/app/Console/Commands/DecryptDatabase.php b/app/Console/Commands/DecryptDatabase.php index 5885712bc2..437226fe2e 100644 --- a/app/Console/Commands/DecryptDatabase.php +++ b/app/Console/Commands/DecryptDatabase.php @@ -28,7 +28,6 @@ use Crypt; use DB; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\Preference; -use FireflyIII\Support\Facades\FireflyConfig; use Illuminate\Console\Command; use Illuminate\Contracts\Encryption\DecryptException; use Log; @@ -50,14 +49,15 @@ class DecryptDatabase extends Command * * @var string */ - protected $signature = 'firefly:decrypt-all'; + protected $signature = 'firefly-iii:decrypt-all'; /** * Execute the console command. * - * @return mixed + * @return int + * @throws FireflyException */ - public function handle() + public function handle(): int { $this->line('Going to decrypt the database.'); $tables = [ @@ -113,7 +113,7 @@ class DecryptDatabase extends Command $this->line(sprintf('Decrypted the data in table "%s".', $table)); // mark as decrypted: $configName = sprintf('is_decrypted_%s', $table); - FireflyConfig::set($configName, true); + app('fireflyconfig')->set($configName, true); } $this->info('Done!'); @@ -129,7 +129,7 @@ class DecryptDatabase extends Command private function isDecrypted(string $table): bool { $configName = sprintf('is_decrypted_%s', $table); - $configVar = FireflyConfig::get($configName, false); + $configVar = app('fireflyconfig')->get($configName, false); if (null !== $configVar) { return (bool)$configVar->data; } @@ -139,9 +139,11 @@ class DecryptDatabase extends Command /** - * @param $value + * Tries to decrypt data. Will only throw an exception when the MAC is invalid. * - * @return mixed + * @param $value + * @return string + * @throws FireflyException */ private function tryDecrypt($value) { @@ -149,7 +151,7 @@ class DecryptDatabase extends Command $value = Crypt::decrypt($value); } catch (DecryptException $e) { if ('The MAC is invalid.' === $e->getMessage()) { - throw new FireflyException($e->getMessage()); + throw new FireflyException($e->getMessage()); // @codeCoverageIgnore } Log::debug(sprintf('Could not decrypt. %s', $e->getMessage())); } diff --git a/app/Console/Commands/Import.php b/app/Console/Commands/Import.php deleted file mode 100644 index 7ee59ea03b..0000000000 --- a/app/Console/Commands/Import.php +++ /dev/null @@ -1,165 +0,0 @@ -. - */ - -/** @noinspection MultipleReturnStatementsInspection */ -/** @noinspection PhpDynamicAsStaticMethodCallInspection */ - -declare(strict_types=1); - -namespace FireflyIII\Console\Commands; - -use FireflyIII\Exceptions\FireflyException; -use FireflyIII\Import\Routine\RoutineInterface; -use FireflyIII\Models\ImportJob; -use FireflyIII\Models\Tag; -use Illuminate\Console\Command; -use Log; - -/** - * Class Import. - * - * @codeCoverageIgnore - */ -class Import extends Command -{ - /** - * The console command description. - * - * @var string - */ - protected $description = 'This will start a new import.'; - - /** - * The name and signature of the console command. - * - * @var string - */ - protected $signature = 'firefly:start-import {key}'; - - /** - * Run the import routine. - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - * - * @throws FireflyException - */ - public function handle(): int - { - Log::debug('Start start-import command'); - $jobKey = (string)$this->argument('key'); - /** @var ImportJob $job */ - $job = ImportJob::where('key', $jobKey)->first(); - if (null === $job) { - $this->errorLine(sprintf('No job found with key "%s"', $jobKey)); - - return 1; - } - if (!$this->isValid($job)) { - $this->errorLine('Job is not valid for some reason. Exit.'); - - return 1; - } - - $this->infoLine(sprintf('Going to import job with key "%s" of type "%s"', $job->key, $job->file_type)); - - // actually start job: - $type = 'csv' === $job->file_type ? 'file' : $job->file_type; - $key = sprintf('import.routine.%s', $type); - $className = config($key); - if (null === $className || !class_exists($className)) { - throw new FireflyException(sprintf('Cannot find import routine class for job of type "%s".', $type)); // @codeCoverageIgnore - } - - /** @var RoutineInterface $routine */ - $routine = app($className); - $routine->setImportJob($job); - $routine->run(); - - /** - * @var int $index - * @var string $error - */ - foreach ($job->errors as $index => $error) { - $this->errorLine(sprintf('Error importing line #%d: %s', $index, $error)); - } - - /** @var Tag $tag */ - $tag = $job->tag()->first(); - $count = 0; - if (null === $tag) { - $count = $tag->transactionJournals()->count(); - } - - $this->infoLine(sprintf('The import has finished. %d transactions have been imported.', $count)); - - return 0; - } - - /** - * Displays an error. - * - * @param string $message - * @param array|null $data - */ - private function errorLine(string $message, array $data = null): void - { - Log::error($message, $data ?? []); - $this->error($message); - - } - - /** - * Displays an informational message. - * - * @param string $message - * @param array $data - */ - private function infoLine(string $message, array $data = null): void - { - Log::info($message, $data ?? []); - $this->line($message); - } - - /** - * Check if job is valid to be imported. - * - * @param ImportJob $job - * - * @return bool - */ - private function isValid(ImportJob $job): bool - { - if (null === $job) { - $this->errorLine('This job does not seem to exist.'); - - return false; - } - - if ('configured' !== $job->status) { - Log::error(sprintf('This job is not ready to be imported (status is %s).', $job->status)); - $this->errorLine('This job is not ready to be imported.'); - - return false; - } - - return true; - } -} diff --git a/app/Console/Commands/Import/CreateCSVImport.php b/app/Console/Commands/Import/CreateCSVImport.php new file mode 100644 index 0000000000..27909afafe --- /dev/null +++ b/app/Console/Commands/Import/CreateCSVImport.php @@ -0,0 +1,332 @@ +. + */ + +/** @noinspection MultipleReturnStatementsInspection */ + +declare(strict_types=1); + +namespace FireflyIII\Console\Commands\Import; + +use Exception; +use FireflyIII\Console\Commands\VerifiesAccessToken; +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Import\Routine\RoutineInterface; +use FireflyIII\Import\Storage\ImportArrayStorage; +use FireflyIII\Models\ImportJob; +use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; +use FireflyIII\Repositories\User\UserRepositoryInterface; +use FireflyIII\User; +use Illuminate\Console\Command; +use Log; + +/** + * Class CreateCSVImport. + */ +class CreateCSVImport extends Command +{ + use VerifiesAccessToken; + /** + * The console command description. + * + * @var string + */ + protected $description = 'Use this command to create a new CSV file import.'; + /** + * The name and signature of the console command. + * + * @var string + */ + protected $signature + = 'firefly-iii:csv-import + {file? : The CSV file to import.} + {configuration? : The configuration file to use for the import.} + {--user=1 : The user ID that the import should import for.} + {--token= : The user\'s access token.}'; + /** @var UserRepositoryInterface */ + private $userRepository; + /** @var ImportJobRepositoryInterface */ + private $importRepository; + /** @var ImportJob */ + private $importJob; + + /** + * Run the command. + */ + public function handle(): int + { + $this->stupidLaravel(); + // @codeCoverageIgnoreStart + if (!$this->verifyAccessToken()) { + $this->errorLine('Invalid access token.'); + + return 1; + } + + if (!$this->validArguments()) { + $this->errorLine('Invalid arguments.'); + + return 1; + } + // @codeCoverageIgnoreEnd + /** @var User $user */ + $user = $this->userRepository->findNull((int)$this->option('user')); + $file = (string)$this->argument('file'); + $configuration = (string)$this->argument('configuration'); + + $this->importRepository->setUser($user); + + $configurationData = json_decode(file_get_contents($configuration), true); + $this->importJob = $this->importRepository->create('file'); + + + // inform user (and log it) + $this->infoLine(sprintf('Import file : %s', $file)); + $this->infoLine(sprintf('Configuration file : %s', $configuration)); + $this->infoLine(sprintf('User : #%d (%s)', $user->id, $user->email)); + $this->infoLine(sprintf('Job : %s', $this->importJob->key)); + + try { + $this->storeFile($file); + } catch (FireflyException $e) { + $this->errorLine($e->getMessage()); + + return 1; + } + + // job is ready to go + $this->importRepository->setConfiguration($this->importJob, $configurationData); + $this->importRepository->setStatus($this->importJob, 'ready_to_run'); + + $this->infoLine('The import routine has started. The process is not visible. Please wait.'); + Log::debug('Go for import!'); + + + // keep repeating this call until job lands on "provider_finished" + try { + $this->processFile(); + } catch (FireflyException $e) { + $this->errorLine($e->getMessage()); + + return 1; + } + + // then store data: + try { + $this->storeData(); + } catch (FireflyException $e) { + $this->errorLine($e->getMessage()); + + return 1; + } + + // give feedback: + $this->giveFeedback(); + + // clear cache for user: + app('preferences')->setForUser($user, 'lastActivity', microtime()); + + return 0; + } + + /** + * Laravel will execute ALL __construct() methods for ALL commands whenever a SINGLE command is + * executed. This leads to noticeable slow-downs and class calls. To prevent this, this method should + * be called from the handle method instead of using the constructor to initialize the command. + * + * @codeCoverageIgnore + */ + private function stupidLaravel(): void + { + $this->userRepository = app(UserRepositoryInterface::class); + $this->importRepository = app(ImportJobRepositoryInterface::class); + } + + /** + * @param string $message + * @param array|null $data + * @codeCoverageIgnore + */ + private function errorLine(string $message, array $data = null): void + { + Log::error($message, $data ?? []); + $this->error($message); + + } + + /** + * @param string $message + * @param array $data + * @codeCoverageIgnore + */ + private function infoLine(string $message, array $data = null): void + { + Log::info($message, $data ?? []); + $this->line($message); + } + + /** + * Verify user inserts correct arguments. + * + * @noinspection MultipleReturnStatementsInspection + * @return bool + * @codeCoverageIgnore + */ + private function validArguments(): bool + { + $file = (string)$this->argument('file'); + $configuration = (string)$this->argument('configuration'); + $cwd = getcwd(); + $enabled = (bool)config('import.enabled.file'); + + if (false === $enabled) { + $this->errorLine('CSV Provider is not enabled.'); + + return false; + } + + if (!file_exists($file)) { + $this->errorLine(sprintf('Firefly III cannot find file "%s" (working directory: "%s").', $file, $cwd)); + + return false; + } + + if (!file_exists($configuration)) { + $this->errorLine(sprintf('Firefly III cannot find configuration file "%s" (working directory: "%s").', $configuration, $cwd)); + + return false; + } + + $configurationData = json_decode(file_get_contents($configuration), true); + if (null === $configurationData) { + $this->errorLine(sprintf('Firefly III cannot read the contents of configuration file "%s" (working directory: "%s").', $configuration, $cwd)); + + return false; + } + + return true; + } + + /** + * Store the supplied file as an attachment to this job. + * + * @param string $file + * @throws FireflyException + */ + private function storeFile(string $file): void + { + // store file as attachment. + if ('' !== $file) { + $messages = $this->importRepository->storeCLIUpload($this->importJob, 'import_file', $file); + if ($messages->count() > 0) { + throw new FireflyException($messages->first()); + } + } + } + + /** + * Keep repeating import call until job lands on "provider_finished". + * + * @throws FireflyException + */ + private function processFile(): void + { + $className = config('import.routine.file'); + $valid = ['provider_finished']; + $count = 0; + + while (!in_array($this->importJob->status, $valid, true) && $count < 6) { + Log::debug(sprintf('Now in loop #%d.', $count + 1)); + /** @var RoutineInterface $routine */ + $routine = app($className); + $routine->setImportJob($this->importJob); + try { + $routine->run(); + } catch (FireflyException|Exception $e) { + $message = 'The import routine crashed: ' . $e->getMessage(); + Log::error($message); + Log::error($e->getTraceAsString()); + + // set job errored out: + $this->importRepository->setStatus($this->importJob, 'error'); + throw new FireflyException($message); + } + $count++; + } + $this->importRepository->setStatus($this->importJob, 'provider_finished'); + $this->importJob->status = 'provider_finished'; + } + + /** + * + * @throws FireflyException + */ + private function storeData(): void + { + if ('provider_finished' === $this->importJob->status) { + $this->infoLine('Import has finished. Please wait for storage of data.'); + // set job to be storing data: + $this->importRepository->setStatus($this->importJob, 'storing_data'); + + /** @var ImportArrayStorage $storage */ + $storage = app(ImportArrayStorage::class); + $storage->setImportJob($this->importJob); + + try { + $storage->store(); + } catch (FireflyException|Exception $e) { + $message = 'The import routine crashed: ' . $e->getMessage(); + Log::error($message); + Log::error($e->getTraceAsString()); + + // set job errored out: + $this->importRepository->setStatus($this->importJob, 'error'); + throw new FireflyException($message); + + } + // set storage to be finished: + $this->importRepository->setStatus($this->importJob, 'storage_finished'); + } + } + + /** + * + */ + private function giveFeedback(): void + { + $this->infoLine('Job has finished.'); + + + if (null !== $this->importJob->tag) { + $this->infoLine(sprintf('%d transaction(s) have been imported.', $this->importJob->tag->transactionJournals->count())); + $this->infoLine(sprintf('You can find your transactions under tag "%s"', $this->importJob->tag->tag)); + } + + if (null === $this->importJob->tag) { + $this->errorLine('No transactions have been imported :(.'); + } + if (count($this->importJob->errors) > 0) { + $this->infoLine(sprintf('%d error(s) occurred:', count($this->importJob->errors))); + foreach ($this->importJob->errors as $err) { + $this->errorLine('- ' . $err); + } + } + } +} diff --git a/app/Console/Commands/Integrity/ReportEmptyObjects.php b/app/Console/Commands/Integrity/ReportEmptyObjects.php new file mode 100644 index 0000000000..8c368b3037 --- /dev/null +++ b/app/Console/Commands/Integrity/ReportEmptyObjects.php @@ -0,0 +1,188 @@ +. + */ + +namespace FireflyIII\Console\Commands\Integrity; + +use FireflyIII\Models\Account; +use FireflyIII\Models\Budget; +use FireflyIII\Models\Category; +use FireflyIII\Models\Tag; +use Illuminate\Console\Command; +use stdClass; + +/** + * Class ReportEmptyObjects + */ +class ReportEmptyObjects extends Command +{ + /** + * The console command description. + * + * @var string + */ + protected $description = 'Reports on empty database objects.'; + /** + * The name and signature of the console command. + * + * @var string + */ + protected $signature = 'firefly-iii:report-empty-objects'; + + /** + * Execute the console command. + * + * @return int + */ + public function handle(): int + { + $start = microtime(true); + $this->reportEmptyBudgets(); + $this->reportEmptyCategories(); + $this->reportEmptyTags(); + $this->reportAccounts(); + $this->reportBudgetLimits(); + $end = round(microtime(true) - $start, 2); + $this->info(sprintf('Report on empty objects finished in %s seconds', $end)); + + return 0; + } + + /** + * Reports on accounts with no transactions. + */ + private function reportAccounts(): void + { + $set = Account::leftJoin('transactions', 'transactions.account_id', '=', 'accounts.id') + ->leftJoin('users', 'accounts.user_id', '=', 'users.id') + ->groupBy(['accounts.id', 'accounts.encrypted', 'accounts.name', 'accounts.user_id', 'users.email']) + ->whereNull('transactions.account_id') + ->get( + ['accounts.id', 'accounts.encrypted', 'accounts.name', 'accounts.user_id', 'users.email'] + ); + + /** @var stdClass $entry */ + foreach ($set as $entry) { + $line = 'User #%d (%s) has account #%d ("%s") which has no transactions.'; + $line = sprintf($line, $entry->user_id, $entry->email, $entry->id, $entry->name); + $this->line($line); + } + } + + /** + * Reports on budgets with no budget limits (which makes them pointless). + */ + private function reportBudgetLimits(): void + { + $set = Budget::leftJoin('budget_limits', 'budget_limits.budget_id', '=', 'budgets.id') + ->leftJoin('users', 'budgets.user_id', '=', 'users.id') + ->groupBy(['budgets.id', 'budgets.name', 'budgets.encrypted', 'budgets.user_id', 'users.email']) + ->whereNull('budget_limits.id') + ->get(['budgets.id', 'budgets.name', 'budgets.user_id', 'budgets.encrypted', 'users.email']); + + /** @var Budget $entry */ + foreach ($set as $entry) { + $line = sprintf( + 'User #%d (%s) has budget #%d ("%s") which has no budget limits.', + $entry->user_id, + $entry->email, + $entry->id, + $entry->name + ); + $this->line($line); + } + } + + /** + * Report on budgets with no transactions or journals. + */ + private function reportEmptyBudgets(): void + { + $set = Budget::leftJoin('budget_transaction_journal', 'budgets.id', '=', 'budget_transaction_journal.budget_id') + ->leftJoin('users', 'budgets.user_id', '=', 'users.id') + ->distinct() + ->whereNull('budget_transaction_journal.budget_id') + ->whereNull('budgets.deleted_at') + ->get(['budgets.id', 'budgets.name', 'budgets.user_id', 'users.email']); + + /** @var stdClass $entry */ + foreach ($set as $entry) { + $line = sprintf( + 'User #%d (%s) has budget #%d ("%s") which has no transaction journals.', + $entry->user_id, + $entry->email, + $entry->id, + $entry->name + ); + $this->line($line); + } + } + + /** + * Report on categories with no transactions or journals. + */ + private function reportEmptyCategories(): void + { + $set = Category::leftJoin('category_transaction_journal', 'categories.id', '=', 'category_transaction_journal.category_id') + ->leftJoin('users', 'categories.user_id', '=', 'users.id') + ->distinct() + ->whereNull('category_transaction_journal.category_id') + ->whereNull('categories.deleted_at') + ->get(['categories.id', 'categories.name', 'categories.user_id', 'users.email']); + + /** @var stdClass $entry */ + foreach ($set as $entry) { + $line = sprintf( + 'User #%d (%s) has category #%d ("%s") which has no transaction journals.', + $entry->user_id, + $entry->email, + $entry->id, + $entry->name + ); + $this->line($line); + } + } + + /** + * + */ + private function reportEmptyTags(): void + { + $set = Tag::leftJoin('tag_transaction_journal', 'tags.id', '=', 'tag_transaction_journal.tag_id') + ->leftJoin('users', 'tags.user_id', '=', 'users.id') + ->distinct() + ->whereNull('tag_transaction_journal.tag_id') + ->whereNull('tags.deleted_at') + ->get(['tags.id', 'tags.tag', 'tags.user_id', 'users.email']); + + /** @var stdClass $entry */ + foreach ($set as $entry) { + + $line = sprintf( + 'User #%d (%s) has tag #%d ("%s") which has no transaction journals.', + $entry->user_id, + $entry->email, + $entry->id, + $entry->tag + ); + $this->line($line); + } + } +} diff --git a/app/Console/Commands/EncryptFile.php b/app/Console/Commands/Integrity/ReportIntegrity.php similarity index 55% rename from app/Console/Commands/EncryptFile.php rename to app/Console/Commands/Integrity/ReportIntegrity.php index 6c0e6d4272..4384a62e5c 100644 --- a/app/Console/Commands/EncryptFile.php +++ b/app/Console/Commands/Integrity/ReportIntegrity.php @@ -1,7 +1,7 @@ argument('file'); - $key = (string)$this->argument('key'); - /** @var EncryptService $service */ - $service = app(EncryptService::class); - - try { - $service->encrypt($file, $key); - } catch (FireflyException $e) { - $this->error($e->getMessage()); - $code = 1; + // if table does not exist, return false + if (!Schema::hasTable('users')) { + return 1; + } + $commands = [ + 'firefly-iii:report-empty-objects', + 'firefly-iii:report-sum', + ]; + foreach ($commands as $command) { + $this->line(sprintf('Now executing %s', $command)); + Artisan::call($command); + $result = Artisan::output(); + echo $result; } - return $code; + return 0; } -} +} \ No newline at end of file diff --git a/app/Console/Commands/Integrity/ReportSkeleton.php.stub b/app/Console/Commands/Integrity/ReportSkeleton.php.stub new file mode 100644 index 0000000000..16b26c0ab0 --- /dev/null +++ b/app/Console/Commands/Integrity/ReportSkeleton.php.stub @@ -0,0 +1,56 @@ +. + */ + +namespace FireflyIII\Console\Commands\Integrity; + +use Illuminate\Console\Command; + +/** + * Class ReportSkeleton + */ +class ReportSkeleton extends Command +{ + /** + * The console command description. + * + * @var string + */ + protected $description = 'DESCRIPTION HERE'; + /** + * The name and signature of the console command. + * + * @var string + */ + protected $signature = 'firefly-iii:INT_COMMAND'; + + /** + * Execute the console command. + * + * @return int + */ + public function handle(): int + { + // + $this->warn('Congrats, you found the skeleton command. Boo!'); + + return 0; + } +} diff --git a/app/Console/Commands/Integrity/ReportSum.php b/app/Console/Commands/Integrity/ReportSum.php new file mode 100644 index 0000000000..e4b3c2cb43 --- /dev/null +++ b/app/Console/Commands/Integrity/ReportSum.php @@ -0,0 +1,83 @@ +. + */ + +namespace FireflyIII\Console\Commands\Integrity; + +use FireflyIII\Repositories\User\UserRepositoryInterface; +use FireflyIII\User; +use Illuminate\Console\Command; + +/** + * Class ReportSkeleton + */ +class ReportSum extends Command +{ + /** + * The console command description. + * + * @var string + */ + protected $description = 'Report on the total sum of transactions. Must be 0.'; + /** + * The name and signature of the console command. + * + * @var string + */ + protected $signature = 'firefly-iii:report-sum'; + + /** + * Execute the console command. + * + * @return int + */ + public function handle(): int + { + $this->reportSum(); + + return 0; + } + + + /** + * Reports for each user when the sum of their transactions is not zero. + */ + private function reportSum(): void + { + $start = microtime(true); + /** @var UserRepositoryInterface $userRepository */ + $userRepository = app(UserRepositoryInterface::class); + + /** @var User $user */ + foreach ($userRepository->all() as $user) { + $sum = (string)$user->transactions()->sum('amount'); + if (0 !== bccomp($sum, '0')) { + $message = sprintf('Error: Transactions for user #%d (%s) are off by %s!', $user->id, $user->email, $sum); + $this->error($message); + } + if (0 === bccomp($sum, '0')) { + $this->info(sprintf('Amount integrity OK for user #%d', $user->id)); + } + } + $end = round(microtime(true) - $start, 2); + $this->info(sprintf('Report on total sum finished in %s seconds', $end)); + + } +} diff --git a/app/Console/Commands/ScanAttachments.php b/app/Console/Commands/ScanAttachments.php index 034a1c2fa3..d6218c2f7e 100644 --- a/app/Console/Commands/ScanAttachments.php +++ b/app/Console/Commands/ScanAttachments.php @@ -75,10 +75,10 @@ class ScanAttachments extends Command $this->error(sprintf('Could not decrypt data of attachment #%d: %s', $attachment->id, $e->getMessage())); continue; } - $tmpfname = tempnam(sys_get_temp_dir(), 'FireflyIII'); - file_put_contents($tmpfname, $decrypted); - $md5 = md5_file($tmpfname); - $mime = mime_content_type($tmpfname); + $tempFileName = tempnam(sys_get_temp_dir(), 'FireflyIII'); + file_put_contents($tempFileName, $decrypted); + $md5 = md5_file($tempFileName); + $mime = mime_content_type($tempFileName); $attachment->md5 = $md5; $attachment->mime = $mime; $attachment->save(); diff --git a/app/Console/Commands/Tools/ApplyRules.php b/app/Console/Commands/Tools/ApplyRules.php new file mode 100644 index 0000000000..1bc1467072 --- /dev/null +++ b/app/Console/Commands/Tools/ApplyRules.php @@ -0,0 +1,398 @@ +. + */ + +namespace FireflyIII\Console\Commands\Tools; + + +use Carbon\Carbon; +use FireflyIII\Console\Commands\VerifiesAccessToken; +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Helpers\Collector\GroupCollectorInterface; +use FireflyIII\Models\AccountType; +use FireflyIII\Models\Rule; +use FireflyIII\Models\RuleGroup; +use FireflyIII\Repositories\Account\AccountRepositoryInterface; +use FireflyIII\Repositories\Journal\JournalRepositoryInterface; +use FireflyIII\Repositories\Rule\RuleRepositoryInterface; +use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface; +use FireflyIII\TransactionRules\Engine\RuleEngine; +use Illuminate\Console\Command; +use Illuminate\Support\Collection; +use Log; + +/** + * Class ApplyRules + */ +class ApplyRules extends Command +{ + use VerifiesAccessToken; + + /** + * The console command description. + * + * @var string + */ + protected $description = 'This command will apply your rules and rule groups on a selection of your transactions.'; + /** + * The name and signature of the console command. + * + * @var string + */ + protected $signature + = 'firefly-iii:apply-rules + {--user=1 : The user ID that the import should import for.} + {--token= : The user\'s access token.} + {--accounts= : A comma-separated list of asset accounts or liabilities to apply your rules to.} + {--rule_groups= : A comma-separated list of rule groups to apply. Take the ID\'s of these rule groups from the Firefly III interface.} + {--rules= : A comma-separated list of rules to apply. Take the ID\'s of these rules from the Firefly III interface. Using this option overrules the option that selects rule groups.} + {--all_rules : If set, will overrule both settings and simply apply ALL of your rules.} + {--start_date= : The date of the earliest transaction to be included (inclusive). If omitted, will be your very first transaction ever. Format: YYYY-MM-DD} + {--end_date= : The date of the latest transaction to be included (inclusive). If omitted, will be your latest transaction ever. Format: YYYY-MM-DD}'; + + /** @var Collection */ + private $accounts; + /** @var array */ + private $acceptedAccounts; + /** @var Carbon */ + private $endDate; + /** @var array */ + private $ruleGroupSelection; + /** @var array */ + private $ruleSelection; + /** @var Carbon */ + private $startDate; + /** @var Collection */ + private $groups; + /** @var bool */ + private $allRules; + + /** @var RuleRepositoryInterface */ + private $ruleRepository; + + /** @var RuleGroupRepositoryInterface */ + private $ruleGroupRepository; + + /** + * Execute the console command. + * + * @return int + * @throws FireflyException + */ + public function handle(): int + { + $this->stupidLaravel(); + // @codeCoverageIgnoreStart + if (!$this->verifyAccessToken()) { + $this->error('Invalid access token.'); + + return 1; + } + // @codeCoverageIgnoreEnd + + // set user: + $this->ruleRepository->setUser($this->getUser()); + $this->ruleGroupRepository->setUser($this->getUser()); + + $result = $this->verifyInput(); + if (false === $result) { + return 1; + } + + $this->allRules = $this->option('all_rules'); + + // always get all the rules of the user. + $this->grabAllRules(); + + // loop all groups and rules and indicate if they're included: + $rulesToApply = $this->getRulesToApply(); + $count = count($rulesToApply); + if (0 === $count) { + $this->error('No rules or rule groups have been included.'); + $this->warn('Make a selection using:'); + $this->warn(' --rules=1,2,...'); + $this->warn(' --rule_groups=1,2,...'); + $this->warn(' --all_rules'); + } + + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setUser($this->getUser()); + $collector->setAccounts($this->accounts); + $collector->setRange($this->startDate, $this->endDate); + $journals = $collector->getExtractedJournals(); + + // start running rules. + $this->line(sprintf('Will apply %d rule(s) to %d transaction(s).', $count, count($journals))); + + // start looping. + /** @var RuleEngine $ruleEngine */ + $ruleEngine = app(RuleEngine::class); + $ruleEngine->setUser($this->getUser()); + $ruleEngine->setRulesToApply($rulesToApply); + + // for this call, the rule engine only includes "store" rules: + $ruleEngine->setTriggerMode(RuleEngine::TRIGGER_STORE); + + $bar = $this->output->createProgressBar(count($journals)); + Log::debug(sprintf('Now looping %d transactions.', count($journals))); + /** @var array $journal */ + foreach ($journals as $journal) { + Log::debug('Start of new journal.'); + $ruleEngine->processJournalArray($journal); + Log::debug('Done with all rules for this group + done with journal.'); + /** @noinspection DisconnectedForeachInstructionInspection */ + $bar->advance(); + } + $this->line(''); + $this->line('Done!'); + + return 0; + } + + /** + * Laravel will execute ALL __construct() methods for ALL commands whenever a SINGLE command is + * executed. This leads to noticeable slow-downs and class calls. To prevent this, this method should + * be called from the handle method instead of using the constructor to initialize the command. + * + * @codeCoverageIgnore + */ + private function stupidLaravel(): void + { + $this->allRules = false; + $this->accounts = new Collection; + $this->ruleSelection = []; + $this->ruleGroupSelection = []; + $this->ruleRepository = app(RuleRepositoryInterface::class); + $this->ruleGroupRepository = app(RuleGroupRepositoryInterface::class); + $this->acceptedAccounts = [AccountType::DEFAULT, AccountType::DEBT, AccountType::ASSET, AccountType::LOAN, AccountType::MORTGAGE]; + $this->groups = new Collection; + } + + /** + * @return bool + * @throws FireflyException + */ + private function verifyInput(): bool + { + // verify account. + $result = $this->verifyInputAccounts(); + if (false === $result) { + return $result; + } + + // verify rule groups. + $this->verifyInputRuleGroups(); + + // verify rules. + $this->verifyInputRules(); + + $this->verifyInputDates(); + + return true; + } + + /** + * @return bool + * @throws FireflyException + */ + private function verifyInputAccounts(): bool + { + $accountString = $this->option('accounts'); + if (null === $accountString || '' === $accountString) { + $this->error('Please use the --accounts option to indicate the accounts to apply rules to.'); + + return false; + } + $finalList = new Collection; + $accountList = explode(',', $accountString); + + // @codeCoverageIgnoreStart + if (0 === count($accountList)) { + $this->error('Please use the --accounts option to indicate the accounts to apply rules to.'); + + return false; + } + // @codeCoverageIgnoreEnd + + /** @var AccountRepositoryInterface $accountRepository */ + $accountRepository = app(AccountRepositoryInterface::class); + $accountRepository->setUser($this->getUser()); + + + foreach ($accountList as $accountId) { + $accountId = (int)$accountId; + $account = $accountRepository->findNull($accountId); + if (null !== $account && in_array($account->accountType->type, $this->acceptedAccounts, true)) { + $finalList->push($account); + } + } + + if (0 === $finalList->count()) { + $this->error('Please make sure all accounts in --accounts are asset accounts or liabilities.'); + + return false; + } + $this->accounts = $finalList; + + return true; + + } + + /** + * @return bool + */ + private function verifyInputRuleGroups(): bool + { + $ruleGroupString = $this->option('rule_groups'); + if (null === $ruleGroupString || '' === $ruleGroupString) { + // can be empty. + return true; + } + $ruleGroupList = explode(',', $ruleGroupString); + // @codeCoverageIgnoreStart + if (0 === count($ruleGroupList)) { + // can be empty. + return true; + } + // @codeCoverageIgnoreEnd + foreach ($ruleGroupList as $ruleGroupId) { + $ruleGroup = $this->ruleGroupRepository->find((int)$ruleGroupId); + if ($ruleGroup->active) { + $this->ruleGroupSelection[] = $ruleGroup->id; + } + if (false === $ruleGroup->active) { + $this->warn(sprintf('Will ignore inactive rule group #%d ("%s")', $ruleGroup->id, $ruleGroup->title)); + } + } + + return true; + } + + /** + * @return bool + */ + private function verifyInputRules(): bool + { + $ruleString = $this->option('rules'); + if (null === $ruleString || '' === $ruleString) { + // can be empty. + return true; + } + $ruleList = explode(',', $ruleString); + + // @codeCoverageIgnoreStart + if (0 === count($ruleList)) { + // can be empty. + + return true; + } + // @codeCoverageIgnoreEnd + + foreach ($ruleList as $ruleId) { + $rule = $this->ruleRepository->find((int)$ruleId); + if (null !== $rule && $rule->active) { + $this->ruleSelection[] = $rule->id; + } + } + + return true; + } + + /** + * @throws FireflyException + */ + private function verifyInputDates(): void + { + // parse start date. + $startDate = Carbon::now()->startOfMonth(); + $startString = $this->option('start_date'); + if (null === $startString) { + /** @var JournalRepositoryInterface $repository */ + $repository = app(JournalRepositoryInterface::class); + $repository->setUser($this->getUser()); + $first = $repository->firstNull(); + if (null !== $first) { + $startDate = $first->date; + } + } + if (null !== $startString && '' !== $startString) { + $startDate = Carbon::createFromFormat('Y-m-d', $startString); + } + + // parse end date + $endDate = Carbon::now(); + $endString = $this->option('end_date'); + if (null !== $endString && '' !== $endString) { + $endDate = Carbon::createFromFormat('Y-m-d', $endString); + } + + if ($startDate > $endDate) { + [$endDate, $startDate] = [$startDate, $endDate]; + } + + $this->startDate = $startDate; + $this->endDate = $endDate; + } + + /** + */ + private function grabAllRules(): void + { + $this->groups = $this->ruleGroupRepository->getActiveGroups(); + } + + /** + * @param Rule $rule + * @param RuleGroup $group + * @return bool + */ + private function includeRule(Rule $rule, RuleGroup $group): bool + { + return in_array($group->id, $this->ruleGroupSelection, true) || + in_array($rule->id, $this->ruleSelection, true) || + $this->allRules; + } + + /** + * @return array + */ + private function getRulesToApply(): array + { + $rulesToApply = []; + /** @var RuleGroup $group */ + foreach ($this->groups as $group) { + $rules = $this->ruleGroupRepository->getActiveStoreRules($group); + /** @var Rule $rule */ + foreach ($rules as $rule) { + // if in rule selection, or group in selection or all rules, it's included. + $test = $this->includeRule($rule, $group); + if (true === $test) { + Log::debug(sprintf('Will include rule #%d "%s"', $rule->id, $rule->title)); + $rulesToApply[] = $rule->id; + } + } + } + + return $rulesToApply; + } +} diff --git a/app/Console/Commands/Cron.php b/app/Console/Commands/Tools/Cron.php similarity index 69% rename from app/Console/Commands/Cron.php rename to app/Console/Commands/Tools/Cron.php index ac7dfb6ff4..51bfc46fe9 100644 --- a/app/Console/Commands/Cron.php +++ b/app/Console/Commands/Tools/Cron.php @@ -2,7 +2,7 @@ /** * Cron.php - * Copyright (c) 2018 thegrumpydictator@gmail.com + * Copyright (c) 2019 thegrumpydictator@gmail.com * * This file is part of Firefly III. * @@ -22,11 +22,14 @@ declare(strict_types=1); -namespace FireflyIII\Console\Commands; +namespace FireflyIII\Console\Commands\Tools; +use Carbon\Carbon; +use Exception; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Support\Cronjobs\RecurringCronjob; use Illuminate\Console\Command; +use InvalidArgumentException; /** * Class Cron @@ -46,16 +49,34 @@ class Cron extends Command * * @var string */ - protected $signature = 'firefly:cron'; + protected $signature = 'firefly-iii:cron + {--F|force : Force the cron job(s) to execute.} + {--date= : Set the date in YYYY-MM-DD to make Firefly III think that\'s the current date.} + '; /** - * Execute the console command. - * - * @return mixed + * @return int + * @throws Exception */ public function handle(): int { + $date = null; + try { + $date = new Carbon($this->option('date')); + } catch (InvalidArgumentException $e) { + $this->error(sprintf('"%s" is not a valid date', $this->option('date'))); + $e->getMessage(); + } + + $recurring = new RecurringCronjob; + $recurring->setForce($this->option('force')); + + // set date in cron job: + if (null !== $date) { + $recurring->setDate($date); + } + try { $result = $recurring->fire(); } catch (FireflyException $e) { diff --git a/app/Console/Commands/Upgrade/AccountCurrencies.php b/app/Console/Commands/Upgrade/AccountCurrencies.php new file mode 100644 index 0000000000..3a01760f14 --- /dev/null +++ b/app/Console/Commands/Upgrade/AccountCurrencies.php @@ -0,0 +1,234 @@ +. + */ + +namespace FireflyIII\Console\Commands\Upgrade; + +use FireflyIII\Models\Account; +use FireflyIII\Models\AccountMeta; +use FireflyIII\Models\AccountType; +use FireflyIII\Models\Transaction; +use FireflyIII\Models\TransactionCurrency; +use FireflyIII\Repositories\Account\AccountRepositoryInterface; +use FireflyIII\Repositories\User\UserRepositoryInterface; +use FireflyIII\User; +use Illuminate\Console\Command; +use Log; + +/** + * Class AccountCurrencies + */ +class AccountCurrencies extends Command +{ + public const CONFIG_NAME = '4780_account_currencies'; + /** + * The console command description. + * + * @var string + */ + protected $description = 'Give all accounts proper currency info.'; + /** + * The name and signature of the console command. + * + * @var string + */ + protected $signature = 'firefly-iii:account-currencies {--F|force : Force the execution of this command.}'; + /** @var AccountRepositoryInterface */ + private $accountRepos; + /** @var UserRepositoryInterface */ + private $userRepos; + /** @var int */ + private $count; + + /** + * Each (asset) account must have a reference to a preferred currency. If the account does not have one, it's forced upon the account. + * + * @return int + */ + public function handle(): int + { + Log::debug('Now in handle()'); + $this->stupidLaravel(); + $start = microtime(true); + if ($this->isExecuted() && true !== $this->option('force')) { + $this->warn('This command has already been executed.'); + + return 0; + } + $this->updateAccountCurrencies(); + + if (0 === $this->count) { + $this->line('All accounts are OK.'); + } + if (0 !== $this->count) { + $this->line(sprintf('Corrected %d account(s).', $this->count)); + } + + $end = round(microtime(true) - $start, 2); + $this->info(sprintf('Verified and fixed account currencies in %s seconds.', $end)); + $this->markAsExecuted(); + + return 0; + } + + /** + * Laravel will execute ALL __construct() methods for ALL commands whenever a SINGLE command is + * executed. This leads to noticeable slow-downs and class calls. To prevent this, this method should + * be called from the handle method instead of using the constructor to initialize the command. + * + * @codeCoverageIgnore + */ + private function stupidLaravel(): void + { + $this->accountRepos = app(AccountRepositoryInterface::class); + $this->userRepos = app(UserRepositoryInterface::class); + $this->count = 0; + } + + /** + * @return bool + */ + private function isExecuted(): bool + { + $configVar = app('fireflyconfig')->get(self::CONFIG_NAME, false); + if (null !== $configVar) { + return (bool)$configVar->data; + } + + return false; // @codeCoverageIgnore + } + + + /** + * + */ + private function markAsExecuted(): void + { + app('fireflyconfig')->set(self::CONFIG_NAME, true); + } + + /** + * @param Account $account + * @param TransactionCurrency $currency + */ + private function updateAccount(Account $account, TransactionCurrency $currency): void + { + Log::debug(sprintf('Now in updateAccount(%d, %s)', $account->id, $currency->code)); + $this->accountRepos->setUser($account->user); + + $accountCurrency = (int)$this->accountRepos->getMetaValue($account, 'currency_id'); + Log::debug(sprintf('Account currency is #%d', $accountCurrency)); + + $openingBalance = $this->accountRepos->getOpeningBalance($account); + $obCurrency = 0; + if (null !== $openingBalance) { + $obCurrency = (int)$openingBalance->transaction_currency_id; + Log::debug('Account has opening balance.'); + } + Log::debug(sprintf('Account OB currency is #%d.', $obCurrency)); + + // both 0? set to default currency: + if (0 === $accountCurrency && 0 === $obCurrency) { + Log::debug(sprintf('Both currencies are 0, so reset to #%d (%s)', $currency->id, $currency->code)); + AccountMeta::where('account_id', $account->id)->where('name', 'currency_id')->forceDelete(); + AccountMeta::create(['account_id' => $account->id, 'name' => 'currency_id', 'data' => $currency->id]); + $this->line(sprintf('Account #%d ("%s") now has a currency setting (%s).', $account->id, $account->name, $currency->code)); + $this->count++; + + return; + } + + // account is set to 0, opening balance is not? + if (0 === $accountCurrency && $obCurrency > 0) { + Log::debug(sprintf('Account is #0, OB is #%d, so set account to OB as well', $obCurrency)); + AccountMeta::create(['account_id' => $account->id, 'name' => 'currency_id', 'data' => $obCurrency]); + $this->line(sprintf('Account #%d ("%s") now has a currency setting (#%d).', $account->id, $account->name, $obCurrency)); + $this->count++; + + return; + } + + + // do not match and opening balance id is not null. + if ($accountCurrency !== $obCurrency && null !== $openingBalance) { + Log::debug(sprintf('Account (#%d) and OB currency (#%d) are different. Overrule OB, set to account currency.', $accountCurrency, $obCurrency)); + // update opening balance: + $openingBalance->transaction_currency_id = $accountCurrency; + $openingBalance->save(); + $openingBalance->transactions->each( + static function (Transaction $transaction) use ($accountCurrency) { + $transaction->transaction_currency_id = $accountCurrency; + $transaction->save(); + }); + $this->line(sprintf('Account #%d ("%s") now has a correct currency for opening balance.', $account->id, $account->name)); + $this->count++; + + return; + } + Log::debug('No changes necessary for this account.'); + } + + /** + * + */ + private function updateAccountCurrencies(): void + { + Log::debug('Now in updateAccountCurrencies()'); + $users = $this->userRepos->all(); + $defaultCurrencyCode = (string)config('firefly.default_currency', 'EUR'); + Log::debug(sprintf('Default currency is %s', $defaultCurrencyCode)); + foreach ($users as $user) { + $this->updateCurrenciesForUser($user, $defaultCurrencyCode); + } + } + + /** + * @param User $user + * @param string $systemCurrencyCode + */ + private function updateCurrenciesForUser(User $user, string $systemCurrencyCode): void + { + Log::debug(sprintf('Now in updateCurrenciesForUser(%s, %s)', $user->email, $systemCurrencyCode)); + $this->accountRepos->setUser($user); + $accounts = $this->accountRepos->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]); + + // get user's currency preference: + $defaultCurrencyCode = app('preferences')->getForUser($user, 'currencyPreference', $systemCurrencyCode)->data; + if (!is_string($defaultCurrencyCode)) { + $defaultCurrencyCode = $systemCurrencyCode; + } + Log::debug(sprintf('Users currency pref is %s', $defaultCurrencyCode)); + + /** @var TransactionCurrency $defaultCurrency */ + $defaultCurrency = TransactionCurrency::where('code', $defaultCurrencyCode)->first(); + + if (null === $defaultCurrency) { + Log::error(sprintf('Users currency pref "%s" does not exist!', $defaultCurrencyCode)); + $this->error(sprintf('User has a preference for "%s", but this currency does not exist.', $defaultCurrencyCode)); + + return; + } + + /** @var Account $account */ + foreach ($accounts as $account) { + $this->updateAccount($account, $defaultCurrency); + } + } +} diff --git a/app/Console/Commands/Upgrade/BackToJournals.php b/app/Console/Commands/Upgrade/BackToJournals.php new file mode 100644 index 0000000000..99fb2fd685 --- /dev/null +++ b/app/Console/Commands/Upgrade/BackToJournals.php @@ -0,0 +1,247 @@ +. + */ + +namespace FireflyIII\Console\Commands\Upgrade; + +use DB; +use FireflyIII\Models\Budget; +use FireflyIII\Models\Category; +use FireflyIII\Models\Transaction; +use FireflyIII\Models\TransactionJournal; +use Illuminate\Console\Command; + +/** + * Class BackToJournals + */ +class BackToJournals extends Command +{ + public const CONFIG_NAME = '4780_back_to_journals'; + /** + * The console command description. + * + * @var string + */ + protected $description = 'Move meta data back to journals, not individual transactions.'; + /** + * The name and signature of the console command. + * + * @var string + */ + protected $signature = 'firefly-iii:back-to-journals {--F|force : Force the execution of this command.}'; + + /** + * Execute the console command. + * + * @return int + */ + public function handle(): int + { + // @codeCoverageIgnoreStart + $start = microtime(true); + if (!$this->isMigrated()) { + $this->error('Please run firefly-iii:migrate-to-groups first.'); + } + if ($this->isExecuted() && true !== $this->option('force')) { + $this->info('This command has already been executed.'); + + return 0; + } + if (true === $this->option('force')) { + $this->warn('Forcing the command.'); + } + // @codeCoverageIgnoreEnd + + $this->migrateAll(); + $end = round(microtime(true) - $start, 2); + $this->info(sprintf('Updated category and budget info for all transaction journals in %s seconds.', $end)); + $this->markAsExecuted(); + + return 0; + } + + /** + * @return array + */ + private function getIdsForBudgets(): array + { + $transactions = DB::table('budget_transaction')->distinct()->get(['transaction_id'])->pluck('transaction_id')->toArray(); + + return DB::table('transactions') + ->whereIn('transactions.id', $transactions) + ->get(['transaction_journal_id'])->pluck('transaction_journal_id')->toArray(); + } + + /** + * @return array + */ + private function getIdsForCategories(): array + { + $transactions = DB::table('category_transaction')->distinct()->get(['transaction_id'])->pluck('transaction_id')->toArray(); + + return DB::table('transactions') + ->whereIn('transactions.id', $transactions) + ->get(['transaction_journal_id'])->pluck('transaction_journal_id')->toArray(); + } + + /** + * @return bool + */ + private function isExecuted(): bool + { + $configVar = app('fireflyconfig')->get(self::CONFIG_NAME, false); + if (null !== $configVar) { + return (bool)$configVar->data; + } + + return false; // @codeCoverageIgnore + } + + /** + * @return bool + */ + private function isMigrated(): bool + { + $configVar = app('fireflyconfig')->get(MigrateToGroups::CONFIG_NAME, false); + if (null !== $configVar) { + return (bool)$configVar->data; + } + + return false; // @codeCoverageIgnore + } + + /** + * + */ + private function markAsExecuted(): void + { + app('fireflyconfig')->set(self::CONFIG_NAME, true); + } + + /** + * + */ + private function migrateAll(): void + { + $this->migrateBudgets(); + $this->migrateCategories(); + + // empty tables + DB::table('budget_transaction')->delete(); + DB::table('category_transaction')->delete(); + } + + /** + * + */ + private function migrateBudgets(): void + { + + $journalIds = $this->getIdsForBudgets(); + $journals = TransactionJournal::whereIn('id', $journalIds)->with(['transactions', 'budgets', 'transactions.budgets'])->get(); + $this->line(sprintf('Check %d transaction journal(s) for budget info.', count($journals))); + /** @var TransactionJournal $journal */ + foreach ($journals as $journal) { + $this->migrateBudgetsForJournal($journal); + } + } + + /** + * @param TransactionJournal $journal + */ + private function migrateBudgetsForJournal(TransactionJournal $journal): void + { + + // grab category from first transaction + /** @var Transaction $transaction */ + $transaction = $journal->transactions->first(); + if (null === $transaction) { + // @codeCoverageIgnoreStart + $this->info(sprintf('Transaction journal #%d has no transactions. Will be fixed later.', $journal->id)); + + return; + // @codeCoverageIgnoreEnd + } + /** @var Budget $budget */ + $budget = $transaction->budgets->first(); + /** @var Budget $journalBudget */ + $journalBudget = $journal->budgets->first(); + + // both have a budget, but they don't match. + if (null !== $budget && null !== $journalBudget && $budget->id !== $journalBudget->id) { + // sync to journal: + $journal->budgets()->sync([(int)$budget->id]); + + return; + } + + // transaction has a budget, but the journal doesn't. + if (null !== $budget && null === $journalBudget) { + // sync to journal: + $journal->budgets()->sync([(int)$budget->id]); + } + } + + /** + * + */ + private function migrateCategories(): void + { + $journalIds = $this->getIdsForCategories(); + $journals = TransactionJournal::whereIn('id', $journalIds)->with(['transactions', 'categories', 'transactions.categories'])->get(); + $this->line(sprintf('Check %d transaction journal(s) for category info.', count($journals))); + /** @var TransactionJournal $journal */ + foreach ($journals as $journal) { + $this->migrateCategoriesForJournal($journal); + } + } + + /** + * @param TransactionJournal $journal + */ + private function migrateCategoriesForJournal(TransactionJournal $journal): void + { + // grab category from first transaction + /** @var Transaction $transaction */ + $transaction = $journal->transactions->first(); + if (null === $transaction) { + // @codeCoverageIgnoreStart + $this->info(sprintf('Transaction journal #%d has no transactions. Will be fixed later.', $journal->id)); + + return; + // @codeCoverageIgnoreEnd + } + /** @var Category $category */ + $category = $transaction->categories->first(); + /** @var Category $journalCategory */ + $journalCategory = $journal->categories->first(); + + // both have a category, but they don't match. + if (null !== $category && null !== $journalCategory && $category->id !== $journalCategory->id) { + // sync to journal: + $journal->categories()->sync([(int)$category->id]); + } + + // transaction has a category, but the journal doesn't. + if (null !== $category && null === $journalCategory) { + $journal->categories()->sync([(int)$category->id]); + } + } +} diff --git a/app/Console/Commands/Upgrade/BudgetLimitCurrency.php b/app/Console/Commands/Upgrade/BudgetLimitCurrency.php new file mode 100644 index 0000000000..dd37b5fc59 --- /dev/null +++ b/app/Console/Commands/Upgrade/BudgetLimitCurrency.php @@ -0,0 +1,119 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Console\Commands\Upgrade; + + +use FireflyIII\Models\Budget; +use FireflyIII\Models\BudgetLimit; +use Illuminate\Console\Command; + +/** + * Class BudgetLimitCurrency + */ +class BudgetLimitCurrency extends Command +{ + public const CONFIG_NAME = '4780_bl_currency'; + /** + * The console command description. + * + * @var string + */ + protected $description = 'Give budget limits a currency'; + /** + * The name and signature of the console command. + * + * @var string + */ + protected $signature = 'firefly-iii:bl-currency {--F|force : Force the execution of this command.}'; + + /** + * Execute the console command. + * + * @return int + */ + public function handle(): int + { + $start = microtime(true); + // @codeCoverageIgnoreStart + if ($this->isExecuted() && true !== $this->option('force')) { + $this->warn('This command has already been executed.'); + + return 0; + } + // @codeCoverageIgnoreEnd + + $count = 0; + $budgetLimits = BudgetLimit::get(); + /** @var BudgetLimit $budgetLimit */ + foreach ($budgetLimits as $budgetLimit) { + if (null === $budgetLimit->transaction_currency_id) { + /** @var Budget $budget */ + $budget = $budgetLimit->budget; + if (null !== $budget) { + $user = $budget->user; + if (null !== $user) { + $currency = app('amount')->getDefaultCurrencyByUser($user); + $budgetLimit->transaction_currency_id = $currency->id; + $budgetLimit->save(); + $this->line( + sprintf('Budget limit #%d (part of budget "%s") now has a currency setting (%s).', $budgetLimit->id, $budget->name, $currency->name) + ); + $count++; + } + } + } + } + if (0 === $count) { + $this->info('All budget limits are correct.'); + } + $end = round(microtime(true) - $start, 2); + $this->info(sprintf('Verified budget limits in %s seconds.', $end)); + + $this->markAsExecuted(); + + return 0; + } + + /** + * @return bool + */ + private function isExecuted(): bool + { + $configVar = app('fireflyconfig')->get(self::CONFIG_NAME, false); + if (null !== $configVar) { + return (bool)$configVar->data; + } + + return false; // @codeCoverageIgnore + } + + + /** + * + */ + private function markAsExecuted(): void + { + app('fireflyconfig')->set(self::CONFIG_NAME, true); + } +} \ No newline at end of file diff --git a/app/Console/Commands/Upgrade/CCLiabilities.php b/app/Console/Commands/Upgrade/CCLiabilities.php new file mode 100644 index 0000000000..e53bfafaa9 --- /dev/null +++ b/app/Console/Commands/Upgrade/CCLiabilities.php @@ -0,0 +1,118 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Console\Commands\Upgrade; + + +use FireflyIII\Models\Account; +use FireflyIII\Models\AccountType; +use Illuminate\Console\Command; +use Illuminate\Support\Collection; + +/** + * Class CCLiabilities + */ +class CCLiabilities extends Command +{ + + + public const CONFIG_NAME = '4780_cc_liabilities'; + /** + * The console command description. + * + * @var string + */ + protected $description = 'Convert old credit card liabilities.'; + /** + * The name and signature of the console command. + * + * @var string + */ + protected $signature = 'firefly-iii:cc-liabilities {--F|force : Force the execution of this command.}'; + + /** + * Execute the console command. + * + * @return int + */ + public function handle(): int + { + $start = microtime(true); + + // @codeCoverageIgnoreStart + if ($this->isExecuted() && true !== $this->option('force')) { + $this->warn('This command has already been executed.'); + + return 0; + } + // @codeCoverageIgnoreEnd + + $ccType = AccountType::where('type', AccountType::CREDITCARD)->first(); + $debtType = AccountType::where('type', AccountType::DEBT)->first(); + if (null === $ccType || null === $debtType) { + $this->info('No incorrectly stored credit card liabilities.'); + + return 0; + } + /** @var Collection $accounts */ + $accounts = Account::where('account_type_id', $ccType->id)->get(); + foreach ($accounts as $account) { + $account->account_type_id = $debtType->id; + $account->save(); + $this->line(sprintf('Converted credit card liability account "%s" (#%d) to generic debt liability.', $account->name, $account->id)); + } + if ($accounts->count() > 0) { + $this->info('Credit card liability types are no longer supported and have been converted to generic debts. See: http://bit.ly/FF3-credit-cards'); + } + if (0 === $accounts->count()) { + $this->info('No incorrectly stored credit card liabilities.'); + } + $end = round(microtime(true) - $start, 2); + $this->info(sprintf('Verified credit card liabilities in %s seconds', $end)); + $this->markAsExecuted(); + + return 0; + } + + /** + * @return bool + */ + private function isExecuted(): bool + { + $configVar = app('fireflyconfig')->get(self::CONFIG_NAME, false); + if (null !== $configVar) { + return (bool)$configVar->data; + } + + return false; // @codeCoverageIgnore + } + + + /** + * + */ + private function markAsExecuted(): void + { + app('fireflyconfig')->set(self::CONFIG_NAME, true); + } +} \ No newline at end of file diff --git a/app/Console/Commands/Upgrade/MigrateAttachments.php b/app/Console/Commands/Upgrade/MigrateAttachments.php new file mode 100644 index 0000000000..4e4df1caa8 --- /dev/null +++ b/app/Console/Commands/Upgrade/MigrateAttachments.php @@ -0,0 +1,128 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Console\Commands\Upgrade; + + +use FireflyIII\Models\Attachment; +use FireflyIII\Models\Note; +use Illuminate\Console\Command; +use Log; + +/** + * Class MigrateAttachments + */ +class MigrateAttachments extends Command +{ + public const CONFIG_NAME = '4780_migrate_attachments'; + /** + * The console command description. + * + * @var string + */ + protected $description = 'Migrates attachment meta-data.'; + /** + * The name and signature of the console command. + * + * @var string + */ + protected $signature = 'firefly-iii:migrate-attachments {--F|force : Force the execution of this command.}'; + + /** + * Execute the console command. + * + * @return int + */ + public function handle(): int + { + // @codeCoverageIgnoreStart + $start = microtime(true); + if ($this->isExecuted() && true !== $this->option('force')) { + $this->warn('This command has already been executed.'); + + return 0; + } + // @codeCoverageIgnoreEnd + + $attachments = Attachment::get(); + $count = 0; + + /** @var Attachment $att */ + foreach ($attachments as $att) { + + // move description: + $description = (string)$att->description; + if ('' !== $description) { + + // find or create note: + $note = $att->notes()->first(); + if (null === $note) { + $note = new Note; + $note->noteable()->associate($att); + } + $note->text = $description; + $note->save(); + + // clear description: + $att->description = ''; + $att->save(); + + Log::debug(sprintf('Migrated attachment #%s description to note #%d.', $att->id, $note->id)); + $count++; + } + } + if (0 === $count) { + $this->line('All attachments are OK.'); + } + if (0 !== $count) { + $this->line(sprintf('Updated %d attachment(s).', $count)); + } + $end = round(microtime(true) - $start, 2); + $this->info(sprintf('Migrated attachment notes in %s seconds.', $end)); + $this->markAsExecuted(); + + return 0; + } + + /** + * @return bool + */ + private function isExecuted(): bool + { + $configVar = app('fireflyconfig')->get(self::CONFIG_NAME, false); + if (null !== $configVar) { + return (bool)$configVar->data; + } + + return false; // @codeCoverageIgnore + } + + + /** + * + */ + private function markAsExecuted(): void + { + app('fireflyconfig')->set(self::CONFIG_NAME, true); + } +} \ No newline at end of file diff --git a/app/Console/Commands/Upgrade/MigrateJournalNotes.php b/app/Console/Commands/Upgrade/MigrateJournalNotes.php new file mode 100644 index 0000000000..5b29cf5593 --- /dev/null +++ b/app/Console/Commands/Upgrade/MigrateJournalNotes.php @@ -0,0 +1,127 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Console\Commands\Upgrade; + + +use Exception; +use FireflyIII\Models\Note; +use FireflyIII\Models\TransactionJournalMeta; +use Illuminate\Console\Command; +use Log; + +/** + * Class MigrateJournalNotes + */ +class MigrateJournalNotes extends Command +{ + public const CONFIG_NAME = '4780_migrate_notes'; + /** + * The console command description. + * + * @var string + */ + protected $description = 'Migrate notes for transaction journals.'; + /** + * The name and signature of the console command. + * + * @var string + */ + protected $signature = 'firefly-iii:migrate-notes {--F|force : Force the execution of this command.}'; + + /** + * Execute the console command. + * + * @return int + */ + public function handle(): int + { + $start = microtime(true); + // @codeCoverageIgnoreStart + if ($this->isExecuted() && true !== $this->option('force')) { + $this->warn('This command has already been executed.'); + + return 0; + } + // @codeCoverageIgnoreEnd + $count = 0; + /** @noinspection PhpUndefinedMethodInspection */ + $set = TransactionJournalMeta::whereName('notes')->get(); + /** @var TransactionJournalMeta $meta */ + foreach ($set as $meta) { + $journal = $meta->transactionJournal; + $note = $journal->notes()->first(); + if (null === $note) { + $note = new Note(); + $note->noteable()->associate($journal); + } + + $note->text = $meta->data; + $note->save(); + Log::debug(sprintf('Migrated meta note #%d to Note #%d', $meta->id, $note->id)); + try { + $meta->delete(); + // @codeCoverageIgnoreStart + } catch (Exception $e) { + Log::error(sprintf('Could not delete old meta entry #%d: %s', $meta->id, $e->getMessage())); + } + // @codeCoverageIgnoreEnd + $count++; + } + + if (0 === $count) { + $this->line('No notes to migrate.'); + } + if (0 !== $count) { + $this->line(sprintf('Migrated %d note(s).', $count)); + } + + $end = round(microtime(true) - $start, 2); + $this->info(sprintf('Migrated notes in %s seconds.', $end)); + $this->markAsExecuted(); + + return 0; + } + + /** + * @return bool + */ + private function isExecuted(): bool + { + $configVar = app('fireflyconfig')->get(self::CONFIG_NAME, false); + if (null !== $configVar) { + return (bool)$configVar->data; + } + + return false; // @codeCoverageIgnore + } + + + /** + * + */ + private function markAsExecuted(): void + { + app('fireflyconfig')->set(self::CONFIG_NAME, true); + } +} \ No newline at end of file diff --git a/app/Console/Commands/Upgrade/MigrateToGroups.php b/app/Console/Commands/Upgrade/MigrateToGroups.php new file mode 100644 index 0000000000..35c8053305 --- /dev/null +++ b/app/Console/Commands/Upgrade/MigrateToGroups.php @@ -0,0 +1,373 @@ +. + */ + +namespace FireflyIII\Console\Commands\Upgrade; + +use DB; +use Exception; +use FireflyIII\Factory\TransactionGroupFactory; +use FireflyIII\Models\Transaction; +use FireflyIII\Models\TransactionJournal; +use FireflyIII\Repositories\Journal\JournalRepositoryInterface; +use FireflyIII\Services\Internal\Destroy\JournalDestroyService; +use Illuminate\Console\Command; +use Illuminate\Support\Collection; +use Log; + +/** + * This command will take split transactions and migrate them to "transaction groups". + * + * It will only run once, but can be forced to run again. + * + * Class MigrateToGroups + */ +class MigrateToGroups extends Command +{ + public const CONFIG_NAME = '4780_migrated_to_groups'; + /** + * The console command description. + * + * @var string + */ + protected $description = 'Migrates a pre-4.7.8 transaction structure to the 4.7.8+ transaction structure.'; + /** + * The name and signature of the console command. + * + * @var string + */ + protected $signature = 'firefly-iii:migrate-to-groups {--F|force : Force the migration, even if it fired before.}'; + /** @var TransactionGroupFactory */ + private $groupFactory; + /** @var JournalRepositoryInterface */ + private $journalRepository; + /** @var JournalDestroyService */ + private $service; + private $count; + + /** + * Execute the console command. + * + * @return int + * @throws Exception + */ + public function handle(): int + { + $this->stupidLaravel(); + $start = microtime(true); + // @codeCoverageIgnoreStart + if ($this->isMigrated() && true !== $this->option('force')) { + $this->info('Database already seems to be migrated.'); + + return 0; + } + + if (true === $this->option('force')) { + $this->warn('Forcing the migration.'); + } + // @codeCoverageIgnoreEnd + + Log::debug('---- start group migration ----'); + $this->makeGroupsFromSplitJournals(); + $end = round(microtime(true) - $start, 2); + $this->info(sprintf('Migrate split journals to groups in %s seconds.', $end)); + + $start = microtime(true); + $this->makeGroupsFromAll(); + Log::debug('---- end group migration ----'); + $end = round(microtime(true) - $start, 2); + $this->info(sprintf('Migrate all journals to groups in %s seconds.', $end)); + + if (0 !== $this->count) { + $this->line(sprintf('Migrated %d transaction journal(s).', $this->count)); + } + if (0 === $this->count) { + $this->line('No journals to migrate to groups.'); + } + + + $this->markAsMigrated(); + + + return 0; + } + + /** + * Laravel will execute ALL __construct() methods for ALL commands whenever a SINGLE command is + * executed. This leads to noticeable slow-downs and class calls. To prevent this, this method should + * be called from the handle method instead of using the constructor to initialize the command. + * + * @codeCoverageIgnore + */ + private function stupidLaravel(): void + { + $this->count = 0; + $this->journalRepository = app(JournalRepositoryInterface::class); + $this->service = app(JournalDestroyService::class); + $this->groupFactory = app(TransactionGroupFactory::class); + } + + /** + * @param TransactionJournal $journal + * @param Transaction $transaction + * + * @return Transaction|null + */ + private function findOpposingTransaction(TransactionJournal $journal, Transaction $transaction): ?Transaction + { + $set = $journal->transactions->filter( + static function (Transaction $subject) use ($transaction) { + $amount = (float)$transaction->amount * -1 === (float)$subject->amount; + $identifier = $transaction->identifier === $subject->identifier; + Log::debug(sprintf('Amount the same? %s', var_export($amount, true))); + Log::debug(sprintf('ID the same? %s', var_export($identifier, true))); + + return $amount && $identifier; + } + ); + + return $set->first(); + } + + /** + * @param TransactionJournal $journal + * + * @return Collection + */ + private function getDestinationTransactions(TransactionJournal $journal): Collection + { + return $journal->transactions->filter( + static function (Transaction $transaction) { + return $transaction->amount > 0; + } + ); + } + + /** + * @param array $array + */ + private function giveGroup(array $array): void + { + $groupId = DB::table('transaction_groups')->insertGetId( + [ + 'created_at' => date('Y-m-d H:i:s'), + 'updated_at' => date('Y-m-d H:i:s'), + 'title' => null, + 'user_id' => $array['user_id'], + ] + ); + DB::table('transaction_journals')->where('id', $array['id'])->update(['transaction_group_id' => $groupId]); + $this->count++; + } + + /** + * @return bool + */ + private function isMigrated(): bool + { + $configVar = app('fireflyconfig')->get(self::CONFIG_NAME, false); + if (null !== $configVar) { + return (bool)$configVar->data; + } + + return false; // @codeCoverageIgnore + } + + /** + * Gives all journals without a group a group. + */ + private function makeGroupsFromAll(): void + { + $orphanedJournals = $this->journalRepository->getJournalsWithoutGroup(); + $count = count($orphanedJournals); + if ($count > 0) { + Log::debug(sprintf('Going to convert %d transaction journals. Please hold..', $count)); + $this->line(sprintf('Going to convert %d transaction journals. Please hold..', $count)); + /** @var array $journal */ + foreach ($orphanedJournals as $array) { + $this->giveGroup($array); + } + } + if (0 === $count) { + $this->info('No need to convert transaction journals.'); + } + } + + /** + * @throws Exception + */ + private function makeGroupsFromSplitJournals(): void + { + $splitJournals = $this->journalRepository->getSplitJournals(); + if ($splitJournals->count() > 0) { + $this->info(sprintf('Going to convert %d split transaction(s). Please hold..', $splitJournals->count())); + /** @var TransactionJournal $journal */ + foreach ($splitJournals as $journal) { + $this->makeMultiGroup($journal); + } + } + if (0 === $splitJournals->count()) { + $this->info('Found no split transaction journals. Nothing to do.'); + } + } + + /** + * @param TransactionJournal $journal + * + * @throws Exception + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + private function makeMultiGroup(TransactionJournal $journal): void + { + // double check transaction count. + if ($journal->transactions->count() <= 2) { + // @codeCoverageIgnoreStart + Log::debug(sprintf('Will not try to convert journal #%d because it has 2 or less transactions.', $journal->id)); + + return; + // @codeCoverageIgnoreEnd + } + Log::debug(sprintf('Will now try to convert journal #%d', $journal->id)); + + $this->journalRepository->setUser($journal->user); + $this->groupFactory->setUser($journal->user); + + $data = [ + // mandatory fields. + 'group_title' => $journal->description, + 'transactions' => [], + ]; + $destTransactions = $this->getDestinationTransactions($journal); + $budgetId = $this->journalRepository->getJournalBudgetId($journal); + $categoryId = $this->journalRepository->getJournalCategoryId($journal); + $notes = $this->journalRepository->getNoteText($journal); + $tags = $this->journalRepository->getTags($journal); + $internalRef = $this->journalRepository->getMetaField($journal, 'internal-reference'); + $sepaCC = $this->journalRepository->getMetaField($journal, 'sepa_cc'); + $sepaCtOp = $this->journalRepository->getMetaField($journal, 'sepa_ct_op'); + $sepaCtId = $this->journalRepository->getMetaField($journal, 'sepa_ct_id'); + $sepaDb = $this->journalRepository->getMetaField($journal, 'sepa_db'); + $sepaCountry = $this->journalRepository->getMetaField($journal, 'sepa_country'); + $sepaEp = $this->journalRepository->getMetaField($journal, 'sepa_ep'); + $sepaCi = $this->journalRepository->getMetaField($journal, 'sepa_ci'); + $sepaBatchId = $this->journalRepository->getMetaField($journal, 'sepa_batch_id'); + $externalId = $this->journalRepository->getMetaField($journal, 'external-id'); + $originalSource = $this->journalRepository->getMetaField($journal, 'original-source'); + $recurrenceId = $this->journalRepository->getMetaField($journal, 'recurrence_id'); + $bunq = $this->journalRepository->getMetaField($journal, 'bunq_payment_id'); + $hash = $this->journalRepository->getMetaField($journal, 'import_hash'); + $hashTwo = $this->journalRepository->getMetaField($journal, 'import_hash_v2'); + $interestDate = $this->journalRepository->getMetaDate($journal, 'interest_date'); + $bookDate = $this->journalRepository->getMetaDate($journal, 'book_date'); + $processDate = $this->journalRepository->getMetaDate($journal, 'process_date'); + $dueDate = $this->journalRepository->getMetaDate($journal, 'due_date'); + $paymentDate = $this->journalRepository->getMetaDate($journal, 'payment_date'); + $invoiceDate = $this->journalRepository->getMetaDate($journal, 'invoice_date'); + + + Log::debug(sprintf('Will use %d positive transactions to create a new group.', $destTransactions->count())); + + /** @var Transaction $transaction */ + foreach ($destTransactions as $transaction) { + Log::debug(sprintf('Now going to add transaction #%d to the array.', $transaction->id)); + $opposingTr = $this->findOpposingTransaction($journal, $transaction); + + if (null === $opposingTr) { + // @codeCoverageIgnoreStart + $this->error( + sprintf( + 'Journal #%d has no opposing transaction for transaction #%d. Cannot upgrade this entry.', + $journal->id, $transaction->id + ) + ); + continue; + // @codeCoverageIgnoreEnd + } + + $tArray = [ + 'type' => strtolower($journal->transactionType->type), + 'date' => $journal->date, + 'user' => $journal->user_id, + 'currency_id' => $transaction->transaction_currency_id, + 'foreign_currency_id' => $transaction->foreign_currency_id, + 'amount' => $transaction->amount, + 'foreign_amount' => $transaction->foreign_amount, + 'description' => $transaction->description ?? $journal->description, + 'source_id' => $opposingTr->account_id, + 'destination_id' => $transaction->account_id, + 'budget_id' => $budgetId, + 'category_id' => $categoryId, + 'bill_id' => $journal->bill_id, + 'notes' => $notes, + 'tags' => $tags, + 'internal_reference' => $internalRef, + 'sepa_cc' => $sepaCC, + 'sepa_ct_op' => $sepaCtOp, + 'sepa_ct_id' => $sepaCtId, + 'sepa_db' => $sepaDb, + 'sepa_country' => $sepaCountry, + 'sepa_ep' => $sepaEp, + 'sepa_ci' => $sepaCi, + 'sepa_batch_id' => $sepaBatchId, + 'external_id' => $externalId, + 'original-source' => $originalSource, + 'recurrence_id' => $recurrenceId, + 'bunq_payment_id' => $bunq, + 'import_hash' => $hash, + 'import_hash_v2' => $hashTwo, + 'interest_date' => $interestDate, + 'book_date' => $bookDate, + 'process_date' => $processDate, + 'due_date' => $dueDate, + 'payment_date' => $paymentDate, + 'invoice_date' => $invoiceDate, + ]; + + $data['transactions'][] = $tArray; + } + Log::debug(sprintf('Now calling transaction journal factory (%d transactions in array)', count($data['transactions']))); + $group = $this->groupFactory->create($data); + Log::debug('Done calling transaction journal factory'); + + // delete the old transaction journal. + $this->service->destroy($journal); + + $this->count++; + + // report on result: + Log::debug( + sprintf('Migrated journal #%d into group #%d with these journals: #%s', + $journal->id, $group->id, implode(', #', $group->transactionJournals->pluck('id')->toArray())) + ); + $this->line( + sprintf('Migrated journal #%d into group #%d with these journals: #%s', + $journal->id, $group->id, implode(', #', $group->transactionJournals->pluck('id')->toArray())) + ); + } + + /** + * + */ + private function markAsMigrated(): void + { + app('fireflyconfig')->set(self::CONFIG_NAME, true); + } + +} diff --git a/app/Console/Commands/Upgrade/MigrateToRules.php b/app/Console/Commands/Upgrade/MigrateToRules.php new file mode 100644 index 0000000000..880dcaab3e --- /dev/null +++ b/app/Console/Commands/Upgrade/MigrateToRules.php @@ -0,0 +1,249 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Console\Commands\Upgrade; + + +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Models\Bill; +use FireflyIII\Models\Preference; +use FireflyIII\Models\RuleGroup; +use FireflyIII\Repositories\Bill\BillRepositoryInterface; +use FireflyIII\Repositories\Rule\RuleRepositoryInterface; +use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface; +use FireflyIII\Repositories\User\UserRepositoryInterface; +use FireflyIII\User; +use Illuminate\Console\Command; + +/** + * Class MigrateToRules + */ +class MigrateToRules extends Command +{ + public const CONFIG_NAME = '4780_bills_to_rules'; + /** + * The console command description. + * + * @var string + */ + protected $description = 'Migrate bills to rules.'; + /** + * The name and signature of the console command. + * + * @var string + */ + protected $signature = 'firefly-iii:bills-to-rules {--F|force : Force the execution of this command.}'; + + /** @var UserRepositoryInterface */ + private $userRepository; + /** @var RuleGroupRepositoryInterface */ + private $ruleGroupRepository; + /** @var BillRepositoryInterface */ + private $billRepository; + /** @var RuleRepositoryInterface */ + private $ruleRepository; + private $count; + + /** + * Execute the console command. + * + * @return int + * @throws FireflyException + */ + public function handle(): int + { + $this->stupidLaravel(); + $start = microtime(true); + + // @codeCoverageIgnoreStart + if ($this->isExecuted() && true !== $this->option('force')) { + $this->warn('This command has already been executed.'); + + return 0; + } + // @codeCoverageIgnoreEnd + + $users = $this->userRepository->all(); + /** @var User $user */ + foreach ($users as $user) { + $this->migrateUser($user); + } + + if (0 === $this->count) { + $this->line('All bills are OK.'); + } + if (0 !== $this->count) { + $this->line(sprintf('Verified and fixed %d bill(s).', $this->count)); + } + + $end = round(microtime(true) - $start, 2); + $this->info(sprintf('Verified and fixed bills in %s seconds.', $end)); + $this->markAsExecuted(); + + return 0; + } + + /** + * Laravel will execute ALL __construct() methods for ALL commands whenever a SINGLE command is + * executed. This leads to noticeable slow-downs and class calls. To prevent this, this method should + * be called from the handle method instead of using the constructor to initialize the command. + * + * @codeCoverageIgnore + */ + private function stupidLaravel(): void + { + $this->count = 0; + $this->userRepository = app(UserRepositoryInterface::class); + $this->ruleGroupRepository = app(RuleGroupRepositoryInterface::class); + $this->billRepository = app(BillRepositoryInterface::class); + $this->ruleRepository = app(RuleRepositoryInterface::class); + } + + /** + * @return bool + */ + private function isExecuted(): bool + { + $configVar = app('fireflyconfig')->get(self::CONFIG_NAME, false); + if (null !== $configVar) { + return (bool)$configVar->data; + } + + return false; // @codeCoverageIgnore + } + + /** + * + */ + private function markAsExecuted(): void + { + app('fireflyconfig')->set(self::CONFIG_NAME, true); + } + + /** + * Migrate bills to new rule structure for a specific user. + * + * @param User $user + * @throws FireflyException + */ + private function migrateUser(User $user): void + { + $this->ruleGroupRepository->setUser($user); + $this->billRepository->setUser($user); + $this->ruleRepository->setUser($user); + + /** @var Preference $lang */ + $lang = app('preferences')->getForUser($user, 'language', 'en_US'); + $groupTitle = (string)trans('firefly.rulegroup_for_bills_title', [], $lang->data); + $ruleGroup = $this->ruleGroupRepository->findByTitle($groupTitle); + //$currency = $this->getCurrency($user); + + if (null === $ruleGroup) { + $ruleGroup = $this->ruleGroupRepository->store( + [ + 'title' => (string)trans('firefly.rulegroup_for_bills_title', [], $lang->data), + 'description' => (string)trans('firefly.rulegroup_for_bills_description', [], $lang->data), + 'active' => true, + ] + ); + } + $bills = $this->billRepository->getBills(); + + /** @var Bill $bill */ + foreach ($bills as $bill) { + $this->migrateBill($ruleGroup, $bill, $lang); + } + + } + + /** + * @param RuleGroup $ruleGroup + * @param Bill $bill + * @throws FireflyException + */ + private function migrateBill(RuleGroup $ruleGroup, Bill $bill, Preference $language): void + { + if ('MIGRATED_TO_RULES' === $bill->match) { + return; + } + + // get match thing: + $match = implode(' ', explode(',', $bill->match)); + $newRule = [ + 'rule_group_id' => $ruleGroup->id, + 'active' => true, + 'strict' => false, + 'stop_processing' => false, // field is no longer used. + 'title' => (string)trans('firefly.rule_for_bill_title', ['name' => $bill->name], $language->data), + 'description' => (string)trans('firefly.rule_for_bill_description', ['name' => $bill->name], $language->data), + 'trigger' => 'store-journal', + 'triggers' => [ + [ + 'type' => 'description_contains', + 'value' => $match, + ], + ], + 'actions' => [ + [ + 'type' => 'link_to_bill', + 'value' => $bill->name, + ], + ], + ]; + + // two triggers or one, depends on bill content: + if ($bill->amount_max === $bill->amount_min) { + $newRule['triggers'][] = [ + 'type' => 'amount_exactly', + 'value' => $bill->amount_min, + ]; + } + if ($bill->amount_max !== $bill->amount_min) { + $newRule['triggers'][] = [ + 'type' => 'amount_less', + 'value' => $bill->amount_max, + ]; + $newRule['triggers'][] = [ + 'type' => 'amount_more', + 'value' => $bill->amount_min, + ]; + } + + $this->ruleRepository->store($newRule); + + // update bill: + $newBillData = [ + 'currency_id' => $bill->transaction_currency_id, + 'name' => $bill->name, + 'match' => 'MIGRATED_TO_RULES', + 'amount_min' => $bill->amount_min, + 'amount_max' => $bill->amount_max, + 'date' => $bill->date, + 'repeat_freq' => $bill->repeat_freq, + 'skip' => $bill->skip, + 'active' => $bill->active, + ]; + $this->billRepository->update($bill, $newBillData); + $this->count++; + } +} \ No newline at end of file diff --git a/app/Console/Commands/Upgrade/OtherCurrenciesCorrections.php b/app/Console/Commands/Upgrade/OtherCurrenciesCorrections.php new file mode 100644 index 0000000000..0a6c6412d5 --- /dev/null +++ b/app/Console/Commands/Upgrade/OtherCurrenciesCorrections.php @@ -0,0 +1,280 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Console\Commands\Upgrade; + +use FireflyIII\Models\Account; +use FireflyIII\Models\AccountType; +use FireflyIII\Models\Transaction; +use FireflyIII\Models\TransactionCurrency; +use FireflyIII\Models\TransactionJournal; +use FireflyIII\Models\TransactionType; +use FireflyIII\Repositories\Account\AccountRepositoryInterface; +use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; +use FireflyIII\Repositories\Journal\JournalRepositoryInterface; +use Illuminate\Console\Command; + +/** + * Class OtherCurrenciesCorrections + */ +class OtherCurrenciesCorrections extends Command +{ + + public const CONFIG_NAME = '4780_other_currencies'; + /** + * The console command description. + * + * @var string + */ + protected $description = 'Update all journal currency information.'; + /** + * The name and signature of the console command. + * + * @var string + */ + protected $signature = 'firefly-iii:other-currencies {--F|force : Force the execution of this command.}'; + /** @var array */ + private $accountCurrencies; + /** @var AccountRepositoryInterface */ + private $accountRepos; + /** @var CurrencyRepositoryInterface */ + private $currencyRepos; + /** @var JournalRepositoryInterface */ + private $journalRepos; + /** @var int */ + private $count; + + /** + * Execute the console command. + * + * @return int + */ + public function handle(): int + { + $this->stupidLaravel(); + $start = microtime(true); + // @codeCoverageIgnoreStart + if ($this->isExecuted() && true !== $this->option('force')) { + $this->warn('This command has already been executed.'); + + return 0; + } + // @codeCoverageIgnoreEnd + + $this->updateOtherJournalsCurrencies(); + $this->markAsExecuted(); + + $this->line(sprintf('Verified %d transaction(s) and journal(s).', $this->count)); + $end = round(microtime(true) - $start, 2); + $this->info(sprintf('Verified and fixed transaction currencies in %s seconds.', $end)); + + return 0; + } + + /** + * Laravel will execute ALL __construct() methods for ALL commands whenever a SINGLE command is + * executed. This leads to noticeable slow-downs and class calls. To prevent this, this method should + * be called from the handle method instead of using the constructor to initialize the command. + * + * @codeCoverageIgnore + */ + private function stupidLaravel(): void + { + $this->count = 0; + $this->accountCurrencies = []; + $this->accountRepos = app(AccountRepositoryInterface::class); + $this->currencyRepos = app(CurrencyRepositoryInterface::class); + $this->journalRepos = app(JournalRepositoryInterface::class); + } + + /** + * @param Account $account + * + * @return TransactionCurrency|null + */ + private function getCurrency(Account $account): ?TransactionCurrency + { + $accountId = $account->id; + if (isset($this->accountCurrencies[$accountId]) && 0 === $this->accountCurrencies[$accountId]) { + return null; // @codeCoverageIgnore + } + if (isset($this->accountCurrencies[$accountId]) && $this->accountCurrencies[$accountId] instanceof TransactionCurrency) { + return $this->accountCurrencies[$accountId]; // @codeCoverageIgnore + } + $currencyId = (int)$this->accountRepos->getMetaValue($account, 'currency_id'); + $result = $this->currencyRepos->findNull($currencyId); + if (null === $result) { + // @codeCoverageIgnoreStart + $this->accountCurrencies[$accountId] = 0; + + return null; + // @codeCoverageIgnoreEnd + } + $this->accountCurrencies[$accountId] = $result; + + return $result; + + + } + + /** + * @return bool + */ + private function isExecuted(): bool + { + $configVar = app('fireflyconfig')->get(self::CONFIG_NAME, false); + if (null !== $configVar) { + return (bool)$configVar->data; + } + + return false; // @codeCoverageIgnore + } + + /** + * + */ + private function markAsExecuted(): void + { + app('fireflyconfig')->set(self::CONFIG_NAME, true); + } + + /** + * @param TransactionJournal $journal + */ + private function updateJournalCurrency(TransactionJournal $journal): void + { + $this->accountRepos->setUser($journal->user); + $this->journalRepos->setUser($journal->user); + $this->currencyRepos->setUser($journal->user); + + $leadTransaction = $this->getLeadTransaction($journal); + + if (null === $leadTransaction) { + // @codeCoverageIgnoreStart + $this->error(sprintf('Could not reliably determine which transaction is in the lead for transaction journal #%d.', $journal->id)); + + return; + // @codeCoverageIgnoreEnd + } + + /** @var Account $account */ + $account = $leadTransaction->account; + $currency = $this->getCurrency($account); + if (null === $currency) { + // @codeCoverageIgnoreStart + $this->error(sprintf('Account #%d ("%s") has no currency preference, so transaction journal #%d can\'t be corrected', + $account->id, $account->name, $journal->id)); + $this->count++; + + return; + // @codeCoverageIgnoreEnd + } + // fix each transaction: + $journal->transactions->each( + static function (Transaction $transaction) use ($currency) { + if (null === $transaction->transaction_currency_id) { + $transaction->transaction_currency_id = $currency->id; + $transaction->save(); + } + + // when mismatch in transaction: + if (!((int)$transaction->transaction_currency_id === (int)$currency->id)) { + $transaction->foreign_currency_id = (int)$transaction->transaction_currency_id; + $transaction->foreign_amount = $transaction->amount; + $transaction->transaction_currency_id = $currency->id; + $transaction->save(); + } + } + ); + // also update the journal, of course: + $journal->transaction_currency_id = $currency->id; + $this->count++; + $journal->save(); + } + + /** + * This routine verifies that withdrawals, deposits and opening balances have the correct currency settings for + * the accounts they are linked to. + * + * Both source and destination must match the respective currency preference of the related asset account. + * So FF3 must verify all transactions. + * + */ + private function updateOtherJournalsCurrencies(): void + { + $set = + $this->journalRepos->getAllJournals( + [ + TransactionType::WITHDRAWAL, + TransactionType::DEPOSIT, + TransactionType::OPENING_BALANCE, + TransactionType::RECONCILIATION, + ] + ); + + /** @var TransactionJournal $journal */ + foreach ($set as $journal) { + $this->updateJournalCurrency($journal); + } + } + + /** + * Gets the transaction that determines the transaction that "leads" and will determine + * the currency to be used by all transactions, and the journal itself. + * + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @param TransactionJournal $journal + * @return Transaction|null + */ + private function getLeadTransaction(TransactionJournal $journal): ?Transaction + { + /** @var Transaction $lead */ + $lead = null; + switch ($journal->transactionType->type) { + case TransactionType::WITHDRAWAL: + $lead = $journal->transactions()->where('amount', '<', 0)->first(); + break; + case TransactionType::DEPOSIT: + $lead = $journal->transactions()->where('amount', '>', 0)->first(); + break; + case TransactionType::OPENING_BALANCE: + // whichever isn't an initial balance account: + $lead = $journal->transactions() + ->leftJoin('accounts', 'transactions.account_id', '=', 'accounts.id') + ->leftJoin('account_types', 'accounts.account_type_id', '=', 'account_types.id') + ->where('account_types.type', '!=', AccountType::INITIAL_BALANCE) + ->first(['transactions.*']); + break; + case TransactionType::RECONCILIATION: + // whichever isn't the reconciliation account: + $lead = $journal->transactions() + ->leftJoin('accounts', 'transactions.account_id', '=', 'accounts.id') + ->leftJoin('account_types', 'accounts.account_type_id', '=', 'account_types.id') + ->where('account_types.type', '!=', AccountType::RECONCILIATION) + ->first(['transactions.*']); + break; + } + + return $lead; + } +} \ No newline at end of file diff --git a/app/Console/Commands/Upgrade/RenameAccountMeta.php b/app/Console/Commands/Upgrade/RenameAccountMeta.php new file mode 100644 index 0000000000..8d99cd552c --- /dev/null +++ b/app/Console/Commands/Upgrade/RenameAccountMeta.php @@ -0,0 +1,116 @@ +. + */ + +namespace FireflyIII\Console\Commands\Upgrade; + +use FireflyIII\Models\AccountMeta; +use Illuminate\Console\Command; + +/** + * Class RenameAccountMeta + */ +class RenameAccountMeta extends Command +{ + public const CONFIG_NAME = '4780_rename_account_meta'; + /** + * The console command description. + * + * @var string + */ + protected $description = 'Rename account meta-data to new format.'; + /** + * The name and signature of the console command. + * + * @var string + */ + protected $signature = 'firefly-iii:rename-account-meta {--F|force : Force the execution of this command.}'; + + /** + * Execute the console command. + * + * @return int + */ + public function handle(): int + { + $start = microtime(true); + // @codeCoverageIgnoreStart + if ($this->isExecuted() && true !== $this->option('force')) { + $this->warn('This command has already been executed.'); + + return 0; + } + // @codeCoverageIgnoreEnd + $array = [ + 'accountRole' => 'account_role', + 'ccType' => 'cc_type', + 'accountNumber' => 'account_number', + 'ccMonthlyPaymentDate' => 'cc_monthly_payment_date', + ]; + $count = 0; + + /** + * @var string $old + * @var string $new + */ + foreach ($array as $old => $new) { + $count += AccountMeta::where('name', $old)->update(['name' => $new]); + + // delete empty entries while we're at it. + AccountMeta::where('name', $new)->where('data','""')->delete(); + } + + $this->markAsExecuted(); + + if (0 === $count) { + $this->line('All account meta is OK.'); + } + if (0 !== $count) { + $this->line(sprintf('Renamed %d account meta entries (entry).', $count)); + } + + $end = round(microtime(true) - $start, 2); + $this->info(sprintf('Fixed account meta data in %s seconds.', $end)); + + return 0; + } + + /** + * @return bool + */ + private function isExecuted(): bool + { + $configVar = app('fireflyconfig')->get(self::CONFIG_NAME, false); + if (null !== $configVar) { + return (bool)$configVar->data; + } + + return false; // @codeCoverageIgnore + } + + + /** + * + */ + private function markAsExecuted(): void + { + app('fireflyconfig')->set(self::CONFIG_NAME, true); + } +} diff --git a/app/Console/Commands/Upgrade/TransactionIdentifier.php b/app/Console/Commands/Upgrade/TransactionIdentifier.php new file mode 100644 index 0000000000..fcbde7e17a --- /dev/null +++ b/app/Console/Commands/Upgrade/TransactionIdentifier.php @@ -0,0 +1,199 @@ +. + */ + +namespace FireflyIII\Console\Commands\Upgrade; + +use FireflyIII\Models\Transaction; +use FireflyIII\Models\TransactionJournal; +use FireflyIII\Repositories\Journal\JournalRepositoryInterface; +use Illuminate\Console\Command; +use Illuminate\Database\QueryException; +use Log; +use Schema; + +/** + * Class TransactionIdentifier + */ +class TransactionIdentifier extends Command +{ + public const CONFIG_NAME = '4780_transaction_identifier'; + /** + * The console command description. + * + * @var string + */ + protected $description = 'Fixes transaction identifiers.'; + /** + * The name and signature of the console command. + * + * @var string + */ + protected $signature = 'firefly-iii:transaction-identifiers {--F|force : Force the execution of this command.}'; + + /** @var JournalRepositoryInterface */ + private $journalRepository; + + /** @var int */ + private $count; + + /** + * This method gives all transactions which are part of a split journal (so more than 2) a sort of "order" so they are easier + * to easier to match to their counterpart. When a journal is split, it has two or three transactions: -3, -4 and -5 for example. + * + * In the database this is reflected as 6 transactions: -3/+3, -4/+4, -5/+5. + * + * When either of these are the same amount, FF3 can't keep them apart: +3/-3, +3/-3, +3/-3. This happens more often than you would + * think. So each set gets a number (1,2,3) to keep them apart. + * + * @return int + */ + public function handle(): int + { + $this->stupidLaravel(); + $start = microtime(true); + // @codeCoverageIgnoreStart + if ($this->isExecuted() && true !== $this->option('force')) { + $this->warn('This command has already been executed.'); + + return 0; + } + + // if table does not exist, return false + if (!Schema::hasTable('transaction_journals')) { + return 0; + } + // @codeCoverageIgnoreEnd + $journals = $this->journalRepository->getSplitJournals(); + /** @var TransactionJournal $journal */ + foreach ($journals as $journal) { + $this->updateJournalIdentifiers($journal); + } + + if (0 === $this->count) { + $this->line('All split journal transaction identifiers are correct.'); + } + if (0 !== $this->count) { + $this->line(sprintf('Fixed %d split journal transaction identifier(s).', $this->count)); + } + + $end = round(microtime(true) - $start, 2); + $this->info(sprintf('Verified and fixed transaction identifiers in %s seconds.', $end)); + $this->markAsExecuted(); + + return 0; + } + + /** + * Laravel will execute ALL __construct() methods for ALL commands whenever a SINGLE command is + * executed. This leads to noticeable slow-downs and class calls. To prevent this, this method should + * be called from the handle method instead of using the constructor to initialize the command. + * + * @codeCoverageIgnore + */ + private function stupidLaravel(): void + { + $this->journalRepository = app(JournalRepositoryInterface::class); + $this->count = 0; + } + + /** + * @return bool + */ + private function isExecuted(): bool + { + $configVar = app('fireflyconfig')->get(self::CONFIG_NAME, false); + if (null !== $configVar) { + return (bool)$configVar->data; + } + + return false; // @codeCoverageIgnore + } + + /** + * + */ + private function markAsExecuted(): void + { + app('fireflyconfig')->set(self::CONFIG_NAME, true); + } + + /** + * Grab all positive transactions from this journal that are not deleted. for each one, grab the negative opposing one + * which has 0 as an identifier and give it the same identifier. + * + * @param TransactionJournal $transactionJournal + */ + private function updateJournalIdentifiers(TransactionJournal $transactionJournal): void + { + $identifier = 0; + $exclude = []; // transactions already processed. + $transactions = $transactionJournal->transactions()->where('amount', '>', 0)->get(); + + /** @var Transaction $transaction */ + foreach ($transactions as $transaction) { + $opposing = $this->findOpposing($transaction, $exclude); + if (null !== $opposing) { + // give both a new identifier: + $transaction->identifier = $identifier; + $opposing->identifier = $identifier; + $transaction->save(); + $opposing->save(); + $exclude[] = $transaction->id; + $exclude[] = $opposing->id; + $this->count++; + } + ++$identifier; + } + + } + + /** + * @param Transaction $transaction + * @param array $exclude + * @return Transaction|null + */ + private function findOpposing(Transaction $transaction, array $exclude): ?Transaction + { + // find opposing: + $amount = bcmul((string)$transaction->amount, '-1'); + + try { + /** @var Transaction $opposing */ + $opposing = Transaction::where('transaction_journal_id', $transaction->transaction_journal_id) + ->where('amount', $amount)->where('identifier', '=', 0) + ->whereNotIn('id', $exclude) + ->first(); + // @codeCoverageIgnoreStart + } catch (QueryException $e) { + Log::error($e->getMessage()); + $this->error('Firefly III could not find the "identifier" field in the "transactions" table.'); + $this->error(sprintf('This field is required for Firefly III version %s to run.', config('firefly.version'))); + $this->error('Please run "php artisan migrate" to add this field to the table.'); + $this->info('Then, run "php artisan firefly:upgrade-database" to try again.'); + + return null; + } + + // @codeCoverageIgnoreEnd + + return $opposing; + } +} diff --git a/app/Console/Commands/Upgrade/TransferCurrenciesCorrections.php b/app/Console/Commands/Upgrade/TransferCurrenciesCorrections.php new file mode 100644 index 0000000000..bcb5582f26 --- /dev/null +++ b/app/Console/Commands/Upgrade/TransferCurrenciesCorrections.php @@ -0,0 +1,574 @@ +. + */ + +namespace FireflyIII\Console\Commands\Upgrade; + + +use FireflyIII\Models\Account; +use FireflyIII\Models\Transaction; +use FireflyIII\Models\TransactionCurrency; +use FireflyIII\Models\TransactionJournal; +use FireflyIII\Models\TransactionType; +use FireflyIII\Repositories\Account\AccountRepositoryInterface; +use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; +use FireflyIII\Repositories\Journal\JournalRepositoryInterface; +use Illuminate\Console\Command; +use Log; + +/** + * Class TransferCurrenciesCorrections + */ +class TransferCurrenciesCorrections extends Command +{ + + public const CONFIG_NAME = '4780_transfer_currencies'; + /** + * The console command description. + * + * @var string + */ + protected $description = 'Updates transfer currency information.'; + /** + * The name and signature of the console command. + * + * @var string + */ + protected $signature = 'firefly-iii:transfer-currencies {--F|force : Force the execution of this command.}'; + /** @var array */ + private $accountCurrencies; + /** @var AccountRepositoryInterface */ + private $accountRepos; + /** @var CurrencyRepositoryInterface */ + private $currencyRepos; + /** @var JournalRepositoryInterface */ + private $journalRepos; + /** @var int */ + private $count; + + /** @var Transaction The source transaction of the current journal. */ + private $sourceTransaction; + /** @var Account The source account of the current journal. */ + private $sourceAccount; + /** @var TransactionCurrency The currency preference of the source account of the current journal. */ + private $sourceCurrency; + /** @var Transaction The destination transaction of the current journal. */ + private $destinationTransaction; + /** @var Account The destination account of the current journal. */ + private $destinationAccount; + /** @var TransactionCurrency The currency preference of the destination account of the current journal. */ + private $destinationCurrency; + + /** + * Execute the console command. + * + * @return int + */ + public function handle(): int + { + $this->stupidLaravel(); + $start = microtime(true); + // @codeCoverageIgnoreStart + if ($this->isExecuted() && true !== $this->option('force')) { + $this->warn('This command has already been executed.'); + + return 0; + } + // @codeCoverageIgnoreEnd + + $this->startUpdateRoutine(); + $this->markAsExecuted(); + + if (0 === $this->count) { + $message = 'All transfers have correct currency information.'; + $this->line($message); + Log::debug($message); + } + if (0 !== $this->count) { + $message = sprintf('Verified currency information of %d transfer(s).', $this->count); + $this->line($message); + Log::debug($message); + } + $end = round(microtime(true) - $start, 2); + $this->info(sprintf('Verified and fixed currency information for transfers in %s seconds.', $end)); + + return 0; + } + + /** + * Laravel will execute ALL __construct() methods for ALL commands whenever a SINGLE command is + * executed. This leads to noticeable slow-downs and class calls. To prevent this, this method should + * be called from the handle method instead of using the constructor to initialize the command. + * + * @codeCoverageIgnore + */ + private function stupidLaravel(): void + { + $this->count = 0; + $this->accountRepos = app(AccountRepositoryInterface::class); + $this->currencyRepos = app(CurrencyRepositoryInterface::class); + $this->journalRepos = app(JournalRepositoryInterface::class); + $this->accountCurrencies = []; + $this->resetInformation(); + } + + /** + * @param Account $account + * + * @return TransactionCurrency|null + */ + private function getCurrency(Account $account): ?TransactionCurrency + { + $accountId = $account->id; + if (isset($this->accountCurrencies[$accountId]) && 0 === $this->accountCurrencies[$accountId]) { + return null; // @codeCoverageIgnore + } + if (isset($this->accountCurrencies[$accountId]) && $this->accountCurrencies[$accountId] instanceof TransactionCurrency) { + return $this->accountCurrencies[$accountId]; // @codeCoverageIgnore + } + $currencyId = (int)$this->accountRepos->getMetaValue($account, 'currency_id'); + $result = $this->currencyRepos->findNull($currencyId); + if (null === $result) { + // @codeCoverageIgnoreStart + $this->accountCurrencies[$accountId] = 0; + + return null; + // @codeCoverageIgnoreEnd + } + $this->accountCurrencies[$accountId] = $result; + + return $result; + + + } + + /** + * @param TransactionJournal $transfer + * + * @return Transaction|null + * @codeCoverageIgnore + */ + private function getDestinationTransaction(TransactionJournal $transfer): ?Transaction + { + return $transfer->transactions()->where('amount', '>', 0)->first(); + } + + /** + * @param TransactionJournal $transfer + * + * @return Transaction|null + * @codeCoverageIgnore + */ + private function getSourceTransaction(TransactionJournal $transfer): ?Transaction + { + return $transfer->transactions()->where('amount', '<', 0)->first(); + } + + /** + * @return bool + */ + private function isExecuted(): bool + { + $configVar = app('fireflyconfig')->get(self::CONFIG_NAME, false); + if (null !== $configVar) { + return (bool)$configVar->data; + } + + return false; // @codeCoverageIgnore + } + + /** + * + */ + private function markAsExecuted(): void + { + app('fireflyconfig')->set(self::CONFIG_NAME, true); + } + + /** + * This method makes sure that the transaction journal uses the currency given in the source transaction. + * + * @param TransactionJournal $journal + */ + private function fixTransactionJournalCurrency(TransactionJournal $journal): void + { + if ((int)$journal->transaction_currency_id !== (int)$this->sourceCurrency->id) { + $oldCurrencyCode = $journal->transactionCurrency->code ?? '(nothing)'; + $journal->transaction_currency_id = $this->sourceCurrency->id; + $message = sprintf( + 'Transfer #%d ("%s") has been updated to use %s instead of %s.', + $journal->id, + $journal->description, + $this->sourceCurrency->code, + $oldCurrencyCode + ); + $this->count++; + $this->line($message); + Log::debug($message); + $journal->save(); + } + } + + /** + * This routine verifies that transfers have the correct currency settings for the accounts they are linked to. + * For transfers, this is can be a destructive routine since we FORCE them into a currency setting whether they + * like it or not. Previous routines MUST have set the currency setting for both accounts for this to work. + * + * A transfer always has the + * + * Both source and destination must match the respective currency preference. So FF3 must verify ALL + * transactions. + */ + private function startUpdateRoutine(): void + { + $set = $this->journalRepos->getAllJournals([TransactionType::TRANSFER]); + /** @var TransactionJournal $journal */ + foreach ($set as $journal) { + $this->updateTransferCurrency($journal); + } + } + + /** + * Reset all the class fields for the current transfer. + * @codeCoverageIgnore + */ + private function resetInformation(): void + { + $this->sourceTransaction = null; + $this->sourceAccount = null; + $this->sourceCurrency = null; + $this->destinationTransaction = null; + $this->destinationAccount = null; + $this->destinationCurrency = null; + } + + /** + * Extract source transaction, source account + source account currency from the journal. + * + * @param TransactionJournal $journal + * @codeCoverageIgnore + */ + private function getSourceInformation(TransactionJournal $journal): void + { + $this->sourceTransaction = $this->getSourceTransaction($journal); + $this->sourceAccount = null === $this->sourceTransaction ? null : $this->sourceTransaction->account; + $this->sourceCurrency = null === $this->sourceAccount ? null : $this->getCurrency($this->sourceAccount); + } + + /** + * Extract destination transaction, destination account + destination account currency from the journal. + * + * @param TransactionJournal $journal + * @codeCoverageIgnore + */ + private function getDestinationInformation(TransactionJournal $journal): void + { + $this->destinationTransaction = $this->getDestinationTransaction($journal); + $this->destinationAccount = null === $this->destinationTransaction ? null : $this->destinationTransaction->account; + $this->destinationCurrency = null === $this->destinationAccount ? null : $this->getCurrency($this->destinationAccount); + } + + /** + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + * + * @param TransactionJournal $transfer + */ + private function updateTransferCurrency(TransactionJournal $transfer): void + { + + $this->resetInformation(); + + // @codeCoverageIgnoreStart + if ($this->isSplitJournal($transfer)) { + $this->line(sprintf(sprintf('Transaction journal #%d is a split journal. Cannot continue.', $transfer->id))); + + return; + } + // @codeCoverageIgnoreEnd + + $this->getSourceInformation($transfer); + $this->getDestinationInformation($transfer); + + // unexpectedly, either one is null: + // @codeCoverageIgnoreStart + if ($this->isEmptyTransactions()) { + $this->error(sprintf('Source or destination information for transaction journal #%d is null. Cannot fix this one.', $transfer->id)); + + return; + } + // @codeCoverageIgnoreEnd + + + // both accounts must have currency preference: + // @codeCoverageIgnoreStart + if ($this->isNoCurrencyPresent()) { + $this->error( + sprintf('Source or destination accounts for transaction journal #%d have no currency information. Cannot fix this one.', $transfer->id)); + + return; + } + // @codeCoverageIgnoreEnd + + // fix source transaction having no currency. + $this->fixSourceNoCurrency(); + + // fix source transaction having bad currency. + $this->fixSourceUnmatchedCurrency(); + + // fix destination transaction having no currency. + $this->fixDestNoCurrency(); + + // fix destination transaction having bad currency. + $this->fixDestinationUnmatchedCurrency(); + + // remove foreign currency information if not necessary. + $this->fixInvalidForeignCurrency(); + // correct foreign currency info if necessary. + $this->fixMismatchedForeignCurrency(); + + // restore missing foreign currency amount. + $this->fixSourceNullForeignAmount(); + + $this->fixDestNullForeignAmount(); + + // fix journal itself: + $this->fixTransactionJournalCurrency($transfer); + + } + + /** + * The source transaction must have a currency. If not, it will be added by + * taking it from the source account's preference. + */ + private function fixSourceNoCurrency(): void + { + if (null === $this->sourceTransaction->transaction_currency_id && null !== $this->sourceCurrency) { + $this->sourceTransaction + ->transaction_currency_id = (int)$this->sourceCurrency->id; + $message = sprintf('Transaction #%d has no currency setting, now set to %s.', + $this->sourceTransaction->id, $this->sourceCurrency->code); + Log::debug($message); + $this->line($message); + $this->count++; + $this->sourceTransaction->save(); + } + } + + /** + * The destination transaction must have a currency. If not, it will be added by + * taking it from the destination account's preference. + */ + private function fixDestNoCurrency(): void + { + if (null === $this->destinationTransaction->transaction_currency_id && null !== $this->destinationCurrency) { + $this->destinationTransaction + ->transaction_currency_id = (int)$this->destinationCurrency->id; + $message = sprintf('Transaction #%d has no currency setting, now set to %s.', + $this->destinationTransaction->id, $this->destinationCurrency->code); + Log::debug($message); + $this->line($message); + $this->count++; + $this->destinationTransaction->save(); + } + } + + /** + * The source transaction must have the correct currency. If not, it will be set by + * taking it from the source account's preference. + */ + private function fixSourceUnmatchedCurrency(): void + { + if (null !== $this->sourceCurrency && + null === $this->sourceTransaction->foreign_amount && + (int)$this->sourceTransaction->transaction_currency_id !== (int)$this->sourceCurrency->id + ) { + $message = sprintf( + 'Transaction #%d has a currency setting #%d that should be #%d. Amount remains %s, currency is changed.', + $this->sourceTransaction->id, + $this->sourceTransaction->transaction_currency_id, + $this->sourceAccount->id, + $this->sourceTransaction->amount + ); + Log::debug($message); + $this->line($message); + $this->count++; + $this->sourceTransaction->transaction_currency_id = (int)$this->sourceCurrency->id; + $this->sourceTransaction->save(); + } + } + + /** + * The destination transaction must have the correct currency. If not, it will be set by + * taking it from the destination account's preference. + */ + private function fixDestinationUnmatchedCurrency(): void + { + if (null !== $this->destinationCurrency && + null === $this->destinationTransaction->foreign_amount && + (int)$this->destinationTransaction->transaction_currency_id !== (int)$this->destinationCurrency->id + ) { + $message = sprintf( + 'Transaction #%d has a currency setting #%d that should be #%d. Amount remains %s, currency is changed.', + $this->destinationTransaction->id, + $this->destinationTransaction->transaction_currency_id, + $this->destinationAccount->id, + $this->destinationTransaction->amount + ); + Log::debug($message); + $this->line($message); + $this->count++; + $this->destinationTransaction->transaction_currency_id = (int)$this->destinationCurrency->id; + $this->destinationTransaction->save(); + } + } + + /** + * Is this a split transaction journal? + * + * @param TransactionJournal $transfer + * @return bool + * @codeCoverageIgnore + */ + private function isSplitJournal(TransactionJournal $transfer): bool + { + return $transfer->transactions->count() > 2; + } + + /** + * Is either the source or destination transaction NULL? + * @return bool + * @codeCoverageIgnore + */ + private function isEmptyTransactions(): bool + { + return null === $this->sourceTransaction || null === $this->destinationTransaction || + null === $this->sourceAccount || null === $this->destinationAccount; + } + + /** + * If the destination account currency is the same as the source currency, + * both foreign_amount and foreign_currency_id fields must be NULL + * for both transactions (because foreign currency info would not make sense) + * + */ + private function fixInvalidForeignCurrency(): void + { + if ((int)$this->destinationCurrency->id === (int)$this->sourceCurrency->id) { + // update both transactions to match: + $this->sourceTransaction->foreign_amount = null; + $this->sourceTransaction->foreign_currency_id = null; + + $this->destinationTransaction->foreign_amount = null; + $this->destinationTransaction->foreign_currency_id = null; + + $this->sourceTransaction->save(); + $this->destinationTransaction->save(); + + Log::debug( + sprintf( + 'Currency for account "%s" is %s, and currency for account "%s" is also + %s, so transactions #%d and #%d has been verified to be to %s exclusively.', + $this->destinationAccount->name, $this->destinationCurrency->code, + $this->sourceAccount->name, $this->sourceCurrency->code, + $this->sourceTransaction->id, $this->destinationTransaction->id, $this->sourceCurrency->code + ) + ); + } + } + + /** + * If destination account currency is different from source account currency, + * then both transactions must get the source account's currency as normal currency + * and the opposing account's currency as foreign currency. + */ + private function fixMismatchedForeignCurrency(): void + { + if ((int)$this->sourceCurrency->id !== (int)$this->destinationCurrency->id) { + $this->sourceTransaction->transaction_currency_id = $this->sourceCurrency->id; + $this->sourceTransaction->foreign_currency_id = $this->destinationCurrency->id; + $this->destinationTransaction->transaction_currency_id = $this->sourceCurrency->id; + $this->destinationTransaction->foreign_currency_id = $this->destinationCurrency->id; + + $this->sourceTransaction->save(); + $this->destinationTransaction->save(); + $this->count++; + Log::debug(sprintf('Verified foreign currency ID of transaction #%d and #%d', $this->sourceTransaction->id, $this->destinationTransaction->id)); + } + } + + /** + * If the foreign amount of the source transaction is null, but that of the other isn't, use this piece of code + * to restore it. + */ + private function fixSourceNullForeignAmount(): void + { + if (null === $this->sourceTransaction->foreign_amount && null !== $this->destinationTransaction->foreign_amount) { + $this->sourceTransaction->foreign_amount = bcmul((string)$this->destinationTransaction->foreign_amount, '-1'); + $this->sourceTransaction->save(); + $this->count++; + Log::debug(sprintf('Restored foreign amount of source transaction #%d to %s', + $this->sourceTransaction->id, $this->sourceTransaction->foreign_amount)); + } + } + + /** + * If the foreign amount of the destination transaction is null, but that of the other isn't, use this piece of code + * to restore it. + */ + private function fixDestNullForeignAmount(): void + { + if (null === $this->destinationTransaction->foreign_amount && null !== $this->sourceTransaction->foreign_amount) { + $this->destinationTransaction->foreign_amount = bcmul((string)$this->sourceTransaction->foreign_amount, '-1'); + $this->destinationTransaction->save(); + $this->count++; + Log::debug(sprintf('Restored foreign amount of destination transaction #%d to %s', + $this->destinationTransaction->id, $this->destinationTransaction->foreign_amount)); + } + } + + /** + * @return bool + * @codeCoverageIgnore + */ + private function isNoCurrencyPresent(): bool + { + // source account must have a currency preference. + if (null === $this->sourceCurrency) { + $message = sprintf('Account #%d ("%s") must have currency preference but has none.', $this->sourceAccount->id, $this->sourceAccount->name); + Log::error($message); + $this->error($message); + + return true; + } + + // destination account must have a currency preference. + if (null === $this->destinationCurrency) { + $message = sprintf('Account #%d ("%s") must have currency preference but has none.', + $this->destinationAccount->id, $this->destinationAccount->name); + Log::error($message); + $this->error($message); + + return true; + } + + return false; + } + +} \ No newline at end of file diff --git a/app/Console/Commands/Upgrade/UpgradeDatabase.php b/app/Console/Commands/Upgrade/UpgradeDatabase.php new file mode 100644 index 0000000000..0faa9e4e31 --- /dev/null +++ b/app/Console/Commands/Upgrade/UpgradeDatabase.php @@ -0,0 +1,119 @@ +. + */ + +namespace FireflyIII\Console\Commands\Upgrade; + +set_time_limit(0); + +use Artisan; +use Illuminate\Console\Command; + +/** + * Class UpgradeDatabase + * @codeCoverageIgnore + */ +class UpgradeDatabase extends Command +{ + /** + * The console command description. + * + * @var string + */ + protected $description = 'Upgrades the database to the latest version.'; + /** + * The name and signature of the console command. + * + * @var string + */ + protected $signature = 'firefly-iii:upgrade-database {--F|force : Force all upgrades.}'; + + /** + * Execute the console command. + * + * @return int + */ + public function handle(): int + { + $this->callInitialCommands(); + + + $commands = [ + // there are 12 upgrade commands. + 'firefly-iii:transaction-identifiers', + 'firefly-iii:migrate-to-groups', + 'firefly-iii:account-currencies', + 'firefly-iii:transfer-currencies', + 'firefly-iii:other-currencies', + 'firefly-iii:migrate-notes', + 'firefly-iii:migrate-attachments', + 'firefly-iii:bills-to-rules', + 'firefly-iii:bl-currency', + 'firefly-iii:cc-liabilities', + 'firefly-iii:back-to-journals', + 'firefly-iii:rename-account-meta', + + // there are 13 verify commands. + 'firefly-iii:fix-piggies', + 'firefly-iii:create-link-types', + 'firefly-iii:create-access-tokens', + 'firefly-iii:remove-bills', + 'firefly-iii:enable-currencies', + 'firefly-iii:fix-transfer-budgets', + 'firefly-iii:fix-uneven-amount', + 'firefly-iii:delete-zero-amount', + 'firefly-iii:delete-orphaned-transactions', + 'firefly-iii:delete-empty-journals', + 'firefly-iii:delete-empty-groups', + 'firefly-iii:fix-account-types', + 'firefly-iii:rename-meta-fields', + + // two report commands + 'firefly-iii:report-empty-objects', + 'firefly-iii:report-sum', + + // instructions + 'firefly:instructions update', + ]; + $args = []; + if ($this->option('force')) { + $args = ['--force' => true]; + } + foreach ($commands as $command) { + $this->line(sprintf('Now executing %s', $command)); + Artisan::call($command, $args); + $result = Artisan::output(); + echo $result; + } + + // set new DB version. + // index will set FF3 version. + app('fireflyconfig')->set('ff3_version', (string)config('firefly.version')); + + return 0; + } + + private function callInitialCommands(): void + { + Artisan::call('migrate', ['--seed' => true]); + Artisan::call('firefly-iii:decrypt-all'); + Artisan::call('generate-keys'); + } +} diff --git a/app/Console/Commands/Upgrade/UpgradeSkeleton.php.stub b/app/Console/Commands/Upgrade/UpgradeSkeleton.php.stub new file mode 100644 index 0000000000..f45211806e --- /dev/null +++ b/app/Console/Commands/Upgrade/UpgradeSkeleton.php.stub @@ -0,0 +1,89 @@ +. + */ + +namespace FireflyIII\Console\Commands\Upgrade; + +use Illuminate\Console\Command; + +/** + * Class UpgradeSkeleton + */ +class UpgradeSkeleton extends Command +{ + public const CONFIG_NAME = '4780_some_name'; + /** + * The console command description. + * + * @var string + */ + protected $description = 'SOME DESCRIPTION'; + /** + * The name and signature of the console command. + * + * @var string + */ + protected $signature = 'firefly-iii:UPGRSKELETON {--F|force : Force the execution of this command.}'; + + /** + * Execute the console command. + * + * @return int + */ + public function handle(): int + { + $start = microtime(true); + if ($this->isExecuted() && true !== $this->option('force')) { + $this->warn('This command has already been executed.'); + + return 0; + } + $this->warn('Congrats, you found the skeleton command. Boo!'); + + //$this->markAsExecuted(); + + $end = round(microtime(true) - $start, 2); + $this->info(sprintf('in %s seconds.', $end)); + + return 0; + } + + /** + * @return bool + */ + private function isExecuted(): bool + { + $configVar = app('fireflyconfig')->get(self::CONFIG_NAME, false); + if (null !== $configVar) { + return (bool)$configVar->data; + } + + return false; // @codeCoverageIgnore + } + + + /** + * + */ + private function markAsExecuted(): void + { + app('fireflyconfig')->set(self::CONFIG_NAME, true); + } +} diff --git a/app/Console/Commands/UpgradeDatabase.php b/app/Console/Commands/UpgradeDatabase.php deleted file mode 100644 index 8b5ef9267e..0000000000 --- a/app/Console/Commands/UpgradeDatabase.php +++ /dev/null @@ -1,829 +0,0 @@ -. - */ - -/** @noinspection MultipleReturnStatementsInspection */ -/** @noinspection PhpStaticAsDynamicMethodCallInspection */ -/** @noinspection PhpDynamicAsStaticMethodCallInspection */ - -declare(strict_types=1); - -namespace FireflyIII\Console\Commands; - -use Crypt; -use DB; -use Exception; -use FireflyIII\Models\Account; -use FireflyIII\Models\AccountMeta; -use FireflyIII\Models\AccountType; -use FireflyIII\Models\Attachment; -use FireflyIII\Models\Bill; -use FireflyIII\Models\Budget; -use FireflyIII\Models\BudgetLimit; -use FireflyIII\Models\Note; -use FireflyIII\Models\Preference; -use FireflyIII\Models\Rule; -use FireflyIII\Models\RuleAction; -use FireflyIII\Models\RuleGroup; -use FireflyIII\Models\RuleTrigger; -use FireflyIII\Models\Transaction; -use FireflyIII\Models\TransactionCurrency; -use FireflyIII\Models\TransactionJournal; -use FireflyIII\Models\TransactionJournalMeta; -use FireflyIII\Models\TransactionType; -use FireflyIII\Repositories\Account\AccountRepositoryInterface; -use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; -use FireflyIII\Repositories\Journal\JournalRepositoryInterface; -use FireflyIII\User; -use Illuminate\Console\Command; -use Illuminate\Contracts\Encryption\DecryptException; -use Illuminate\Database\QueryException; -use Illuminate\Support\Collection; -use Log; -use Schema; -use UnexpectedValueException; - -/** - * Class UpgradeDatabase. - * - * Upgrade user database. - * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) - * - * @codeCoverageIgnore - */ -class UpgradeDatabase extends Command -{ - /** - * The console command description. - * - * @var string - */ - protected $description = 'Will run various commands to update database records.'; - /** - * The name and signature of the console command. - * - * @var string - */ - protected $signature = 'firefly:upgrade-database'; - - /** - * Execute the console command. - */ - public function handle(): int - { - $this->setTransactionIdentifier(); - $this->updateAccountCurrencies(); - $this->createNewTypes(); - $this->line('Updating currency information..'); - $this->updateTransferCurrencies(); - $this->updateOtherCurrencies(); - $this->line('Done updating currency information..'); - $this->migrateNotes(); - $this->migrateAttachmentData(); - $this->migrateBillsToRules(); - $this->budgetLimitCurrency(); - $this->removeCCLiabilities(); - - $this->info('Firefly III database is up to date.'); - - return 0; - } - - /** - * @param string $value - * - * @return string - */ - private function tryDecrypt(string $value): string - { - try { - $value = Crypt::decrypt($value); - } catch (DecryptException $e) { - Log::debug(sprintf('Could not decrypt. %s', $e->getMessage())); - } - - return $value; - } - - /** - * Since it is one routine these warnings make sense and should be supressed. - * - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - * @SuppressWarnings(PHPMD.NPathComplexity) - */ - public function migrateBillsToRules(): void - { - foreach (User::get() as $user) { - /** @var Preference $lang */ - $lang = app('preferences')->getForUser($user, 'language', 'en_US'); - $groupName = (string)trans('firefly.rulegroup_for_bills_title', [], $lang->data); - $ruleGroup = $user->ruleGroups()->where('title', $groupName)->first(); - $currencyPreference = app('preferences')->getForUser($user, 'currencyPreference', config('firefly.default_currency', 'EUR')); - - if (null === $currencyPreference) { - $this->error('User has no currency preference. Impossible.'); - - return; - } - $currencyCode = $this->tryDecrypt($currencyPreference->data); - - // try json decrypt just in case. - if (\strlen($currencyCode) > 3) { - $currencyCode = json_decode($currencyCode) ?? 'EUR'; - } - - $currency = TransactionCurrency::where('code', $currencyCode)->first(); - if (null === $currency) { - $this->line('Fall back to default currency in migrateBillsToRules().'); - $currency = app('amount')->getDefaultCurrencyByUser($user); - } - - if (null === $ruleGroup) { - $array = RuleGroup::get(['order'])->pluck('order')->toArray(); - $order = \count($array) > 0 ? max($array) + 1 : 1; - $ruleGroup = RuleGroup::create( - [ - 'user_id' => $user->id, - 'title' => (string)trans('firefly.rulegroup_for_bills_title', [], $lang->data), - 'description' => (string)trans('firefly.rulegroup_for_bills_description', [], $lang->data), - 'order' => $order, - 'active' => 1, - ] - ); - } - - // loop bills. - $order = 1; - /** @var Collection $collection */ - $collection = $user->bills()->get(); - /** @var Bill $bill */ - foreach ($collection as $bill) { - if ('MIGRATED_TO_RULES' !== $bill->match) { - $rule = Rule::create( - [ - 'user_id' => $user->id, - 'rule_group_id' => $ruleGroup->id, - 'title' => (string)trans('firefly.rule_for_bill_title', ['name' => $bill->name], $lang->data), - 'description' => (string)trans('firefly.rule_for_bill_description', ['name' => $bill->name], $lang->data), - 'order' => $order, - 'active' => $bill->active, - 'stop_processing' => 1, - ] - ); - // add default trigger - RuleTrigger::create( - [ - 'rule_id' => $rule->id, - 'trigger_type' => 'user_action', - 'trigger_value' => 'store-journal', - 'active' => 1, - 'stop_processing' => 0, - 'order' => 1, - ] - ); - // add trigger for description - $match = implode(' ', explode(',', $bill->match)); - RuleTrigger::create( - [ - 'rule_id' => $rule->id, - 'trigger_type' => 'description_contains', - 'trigger_value' => $match, - 'active' => 1, - 'stop_processing' => 0, - 'order' => 2, - ] - ); - if ($bill->amount_max !== $bill->amount_min) { - // add triggers for amounts: - RuleTrigger::create( - [ - 'rule_id' => $rule->id, - 'trigger_type' => 'amount_less', - 'trigger_value' => round($bill->amount_max, $currency->decimal_places), - 'active' => 1, - 'stop_processing' => 0, - 'order' => 3, - ] - ); - RuleTrigger::create( - [ - 'rule_id' => $rule->id, - 'trigger_type' => 'amount_more', - 'trigger_value' => round((float)$bill->amount_min, $currency->decimal_places), - 'active' => 1, - 'stop_processing' => 0, - 'order' => 4, - ] - ); - } - if ($bill->amount_max === $bill->amount_min) { - RuleTrigger::create( - [ - 'rule_id' => $rule->id, - 'trigger_type' => 'amount_exactly', - 'trigger_value' => round((float)$bill->amount_min, $currency->decimal_places), - 'active' => 1, - 'stop_processing' => 0, - 'order' => 3, - ] - ); - } - - // create action - RuleAction::create( - [ - 'rule_id' => $rule->id, - 'action_type' => 'link_to_bill', - 'action_value' => $bill->name, - 'order' => 1, - 'active' => 1, - 'stop_processing' => 0, - ] - ); - - $order++; - $bill->match = 'MIGRATED_TO_RULES'; - $bill->save(); - $this->line(sprintf('Updated bill #%d ("%s") so it will use rules.', $bill->id, $bill->name)); - } - - // give bills a currency when they dont have one. - if (null === $bill->transaction_currency_id) { - $this->line(sprintf('Gave bill #%d ("%s") a currency (%s).', $bill->id, $bill->name, $currency->name)); - $bill->transactionCurrency()->associate($currency); - $bill->save(); - } - } - } - } - - /** - * This method gives all transactions which are part of a split journal (so more than 2) a sort of "order" so they are easier - * to easier to match to their counterpart. When a journal is split, it has two or three transactions: -3, -4 and -5 for example. - * - * In the database this is reflected as 6 transactions: -3/+3, -4/+4, -5/+5. - * - * When either of these are the same amount, FF3 can't keep them apart: +3/-3, +3/-3, +3/-3. This happens more often than you would - * think. So each set gets a number (1,2,3) to keep them apart. - */ - public function setTransactionIdentifier(): void - { - // if table does not exist, return false - if (!Schema::hasTable('transaction_journals')) { - return; - } - $subQuery = TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') - ->whereNull('transaction_journals.deleted_at') - ->whereNull('transactions.deleted_at') - ->groupBy(['transaction_journals.id']) - ->select(['transaction_journals.id', DB::raw('COUNT(transactions.id) AS t_count')]); - /** @noinspection PhpStrictTypeCheckingInspection */ - $result = DB::table(DB::raw('(' . $subQuery->toSql() . ') AS derived')) - ->mergeBindings($subQuery->getQuery()) - ->where('t_count', '>', 2) - ->select(['id', 't_count']); - $journalIds = array_unique($result->pluck('id')->toArray()); - - foreach ($journalIds as $journalId) { - $this->updateJournalidentifiers((int)$journalId); - } - - } - - /** - * Each (asset) account must have a reference to a preferred currency. If the account does not have one, it's forced upon the account. - * - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - */ - public function updateAccountCurrencies(): void - { - Log::debug('Now in updateAccountCurrencies()'); - - $defaultConfig = (string)config('firefly.default_currency', 'EUR'); - Log::debug(sprintf('System default currency is "%s"', $defaultConfig)); - - $accounts = Account::leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id') - ->whereIn('account_types.type', [AccountType::DEFAULT, AccountType::ASSET])->get(['accounts.*']); - /** @var AccountRepositoryInterface $repository */ - $repository = app(AccountRepositoryInterface::class); - $accounts->each( - function (Account $account) use ($repository, $defaultConfig) { - $repository->setUser($account->user); - // get users preference, fall back to system pref. - - // expand and debug routine. - $defaultCurrencyCode = app('preferences')->getForUser($account->user, 'currencyPreference', $defaultConfig)->data; - Log::debug(sprintf('Default currency code is "%s"', var_export($defaultCurrencyCode, true))); - if (!is_string($defaultCurrencyCode)) { - $defaultCurrencyCode = $defaultConfig; - Log::debug(sprintf('Default currency code is not a string, now set to "%s"', $defaultCurrencyCode)); - } - $defaultCurrency = TransactionCurrency::where('code', $defaultCurrencyCode)->first(); - $accountCurrency = (int)$repository->getMetaValue($account, 'currency_id'); - $openingBalance = $account->getOpeningBalance(); - $obCurrency = (int)$openingBalance->transaction_currency_id; - - if (null === $defaultCurrency) { - throw new UnexpectedValueException(sprintf('User has a preference for "%s", but this currency does not exist.', $defaultCurrencyCode)); - } - Log::debug( - sprintf('Found default currency #%d (%s) while searching for "%s"', $defaultCurrency->id, $defaultCurrency->code, $defaultCurrencyCode) - ); - - // both 0? set to default currency: - if (0 === $accountCurrency && 0 === $obCurrency) { - AccountMeta::where('account_id', $account->id)->where('name', 'currency_id')->forceDelete(); - AccountMeta::create(['account_id' => $account->id, 'name' => 'currency_id', 'data' => $defaultCurrency->id]); - $this->line(sprintf('Account #%d ("%s") now has a currency setting (%s).', $account->id, $account->name, $defaultCurrencyCode)); - - return true; - } - - // account is set to 0, opening balance is not? - if (0 === $accountCurrency && $obCurrency > 0) { - AccountMeta::create(['account_id' => $account->id, 'name' => 'currency_id', 'data' => $obCurrency]); - $this->line(sprintf('Account #%d ("%s") now has a currency setting (%s).', $account->id, $account->name, $defaultCurrencyCode)); - - return true; - } - - // do not match and opening balance id is not null. - if ($accountCurrency !== $obCurrency && $openingBalance->id > 0) { - // update opening balance: - $openingBalance->transaction_currency_id = $accountCurrency; - $openingBalance->save(); - $this->line(sprintf('Account #%d ("%s") now has a correct currency for opening balance.', $account->id, $account->name)); - - return true; - } - - return true; - } - ); - - } - - /** - * This routine verifies that withdrawals, deposits and opening balances have the correct currency settings for - * the accounts they are linked to. - * - * Both source and destination must match the respective currency preference of the related asset account. - * So FF3 must verify all transactions. - * - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - */ - public function updateOtherCurrencies(): void - { - /** @var CurrencyRepositoryInterface $repository */ - $repository = app(CurrencyRepositoryInterface::class); - /** @var AccountRepositoryInterface $accountRepos */ - $accountRepos = app(AccountRepositoryInterface::class); - $set = TransactionJournal - ::leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id') - ->whereIn('transaction_types.type', [TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::OPENING_BALANCE]) - ->get(['transaction_journals.*']); - - $set->each( - function (TransactionJournal $journal) use ($repository, $accountRepos) { - // get the transaction with the asset account in it: - /** @var Transaction $transaction */ - $transaction = $journal->transactions() - ->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id') - ->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id') - ->whereIn('account_types.type', [AccountType::DEFAULT, AccountType::ASSET])->first(['transactions.*']); - if (null === $transaction) { - return; - } - $accountRepos->setUser($journal->user); - /** @var Account $account */ - $account = $transaction->account; - $currency = $repository->findNull((int)$accountRepos->getMetaValue($account, 'currency_id')); - if (null === $currency) { - return; - } - $transactions = $journal->transactions()->get(); - $transactions->each( - function (Transaction $transaction) use ($currency) { - if (null === $transaction->transaction_currency_id) { - $transaction->transaction_currency_id = $currency->id; - $transaction->save(); - } - - // when mismatch in transaction: - if (!((int)$transaction->transaction_currency_id === (int)$currency->id)) { - $transaction->foreign_currency_id = (int)$transaction->transaction_currency_id; - $transaction->foreign_amount = $transaction->amount; - $transaction->transaction_currency_id = $currency->id; - $transaction->save(); - } - } - ); - // also update the journal, of course: - $journal->transaction_currency_id = $currency->id; - $journal->save(); - } - ); - - } - - /** - * This routine verifies that transfers have the correct currency settings for the accounts they are linked to. - * For transfers, this is can be a destructive routine since we FORCE them into a currency setting whether they - * like it or not. Previous routines MUST have set the currency setting for both accounts for this to work. - * - * A transfer always has the - * - * Both source and destination must match the respective currency preference. So FF3 must verify ALL - * transactions. - */ - public function updateTransferCurrencies(): void - { - $set = TransactionJournal - ::leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id') - ->where('transaction_types.type', TransactionType::TRANSFER) - ->get(['transaction_journals.*']); - - $set->each( - function (TransactionJournal $transfer) { - // select all "source" transactions: - /** @var Collection $transactions */ - $transactions = $transfer->transactions()->where('amount', '<', 0)->get(); - $transactions->each( - function (Transaction $transaction) { - $this->updateTransactionCurrency($transaction); - $this->updateJournalCurrency($transaction); - } - ); - } - ); - } - - /** - * - */ - private function budgetLimitCurrency(): void - { - $budgetLimits = BudgetLimit::get(); - /** @var BudgetLimit $budgetLimit */ - foreach ($budgetLimits as $budgetLimit) { - if (null === $budgetLimit->transaction_currency_id) { - /** @var Budget $budget */ - $budget = $budgetLimit->budget; - if (null !== $budget) { - $user = $budget->user; - if (null !== $user) { - $currency = app('amount')->getDefaultCurrencyByUser($user); - $budgetLimit->transaction_currency_id = $currency->id; - $budgetLimit->save(); - $this->line( - sprintf('Budget limit #%d (part of budget "%s") now has a currency setting (%s).', $budgetLimit->id, $budget->name, $currency->name) - ); - } - } - } - } - } - - /** - * - */ - private function createNewTypes(): void - { - // create transaction type "Reconciliation". - $type = TransactionType::where('type', TransactionType::RECONCILIATION)->first(); - if (null === $type) { - TransactionType::create(['type' => TransactionType::RECONCILIATION]); - } - $account = AccountType::where('type', AccountType::RECONCILIATION)->first(); - if (null === $account) { - AccountType::create(['type' => AccountType::RECONCILIATION]); - } - } - - /** - * Move the description of each attachment (when not NULL) to the notes or to a new note object - * for all attachments. - */ - private function migrateAttachmentData(): void - { - $attachments = Attachment::get(); - - /** @var Attachment $att */ - foreach ($attachments as $att) { - - // move description: - $description = (string)$att->description; - if ('' !== $description) { - // find or create note: - $note = $att->notes()->first(); - if (null === $note) { - $note = new Note; - $note->noteable()->associate($att); - } - $note->text = $description; - $note->save(); - - // clear description: - $att->description = ''; - $att->save(); - - Log::debug(sprintf('Migrated attachment #%s description to note #%d', $att->id, $note->id)); - } - } - } - - /** - * Move all the journal_meta notes to their note object counter parts. - */ - private function migrateNotes(): void - { - /** @noinspection PhpUndefinedMethodInspection */ - $set = TransactionJournalMeta::whereName('notes')->get(); - /** @var TransactionJournalMeta $meta */ - foreach ($set as $meta) { - $journal = $meta->transactionJournal; - $note = $journal->notes()->first(); - if (null === $note) { - $note = new Note(); - $note->noteable()->associate($journal); - } - - $note->text = $meta->data; - $note->save(); - Log::debug(sprintf('Migrated meta note #%d to Note #%d', $meta->id, $note->id)); - try { - $meta->delete(); - } catch (Exception $e) { - Log::error(sprintf('Could not delete old meta entry #%d: %s', $meta->id, $e->getMessage())); - } - } - } - - /** - * - */ - private function removeCCLiabilities(): void - { - $ccType = AccountType::where('type', AccountType::CREDITCARD)->first(); - $debtType = AccountType::where('type', AccountType::DEBT)->first(); - if (null === $ccType || null === $debtType) { - return; - } - /** @var Collection $accounts */ - $accounts = Account::where('account_type_id', $ccType->id)->get(); - foreach ($accounts as $account) { - $account->account_type_id = $debtType->id; - $account->save(); - $this->line(sprintf('Converted credit card liability account "%s" (#%d) to generic debt liability.', $account->name, $account->id)); - } - if ($accounts->count() > 0) { - $this->info('Credit card liability types are no longer supported and have been converted to generic debts. See: http://bit.ly/FF3-credit-cards'); - } - } - - /** - * This method makes sure that the transaction journal uses the currency given in the transaction. - * - * @param Transaction $transaction - */ - private function updateJournalCurrency(Transaction $transaction): void - { - /** @var CurrencyRepositoryInterface $repository */ - $repository = app(CurrencyRepositoryInterface::class); - /** @var AccountRepositoryInterface $accountRepos */ - $accountRepos = app(AccountRepositoryInterface::class); - $accountRepos->setUser($transaction->account->user); - $currency = $repository->findNull((int)$accountRepos->getMetaValue($transaction->account, 'currency_id')); - $journal = $transaction->transactionJournal; - - if (null === $currency) { - return; - } - - if (!((int)$currency->id === (int)$journal->transaction_currency_id)) { - $this->line( - sprintf( - 'Transfer #%d ("%s") has been updated to use %s instead of %s.', - $journal->id, - $journal->description, - $currency->code, - $journal->transactionCurrency->code - ) - ); - $journal->transaction_currency_id = $currency->id; - $journal->save(); - } - - } - - /** - * grab all positive transactiosn from this journal that are not deleted. for each one, grab the negative opposing one - * which has 0 as an identifier and give it the same identifier. - * - * @param int $journalId - */ - private function updateJournalidentifiers(int $journalId): void - { - $identifier = 0; - $processed = []; - $transactions = Transaction::where('transaction_journal_id', $journalId)->where('amount', '>', 0)->get(); - /** @var Transaction $transaction */ - foreach ($transactions as $transaction) { - // find opposing: - $amount = bcmul((string)$transaction->amount, '-1'); - - try { - /** @var Transaction $opposing */ - $opposing = Transaction::where('transaction_journal_id', $journalId) - ->where('amount', $amount)->where('identifier', '=', 0) - ->whereNotIn('id', $processed) - ->first(); - } catch (QueryException $e) { - Log::error($e->getMessage()); - $this->error('Firefly III could not find the "identifier" field in the "transactions" table.'); - $this->error(sprintf('This field is required for Firefly III version %s to run.', config('firefly.version'))); - $this->error('Please run "php artisan migrate" to add this field to the table.'); - $this->info('Then, run "php artisan firefly:upgrade-database" to try again.'); - - return; - } - if (null !== $opposing) { - // give both a new identifier: - $transaction->identifier = $identifier; - $opposing->identifier = $identifier; - $transaction->save(); - $opposing->save(); - $processed[] = $transaction->id; - $processed[] = $opposing->id; - } - ++$identifier; - } - - } - - /** - * This method makes sure that the tranaction uses the same currency as the source account does. - * If not, the currency is updated to include a reference to its original currency as the "foreign" currency. - * - * The transaction that is sent to this function MUST be the source transaction (amount negative). - * - * Method is long and complex but I'll allow it. https://imgur.com/gallery/dVDJiez - * - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - * @SuppressWarnings(PHPMD.NPathComplexity) - * - * @param Transaction $transaction - */ - private function updateTransactionCurrency(Transaction $transaction): void - { - /** @var CurrencyRepositoryInterface $repository */ - $repository = app(CurrencyRepositoryInterface::class); - /** @var AccountRepositoryInterface $accountRepos */ - $accountRepos = app(AccountRepositoryInterface::class); - /** @var JournalRepositoryInterface $journalRepos */ - $journalRepos = app(JournalRepositoryInterface::class); - - $accountRepos->setUser($transaction->account->user); - $journalRepos->setUser($transaction->account->user); - $currency = $repository->findNull((int)$accountRepos->getMetaValue($transaction->account, 'currency_id')); - - if (null === $currency) { - Log::error(sprintf('Account #%d ("%s") must have currency preference but has none.', $transaction->account->id, $transaction->account->name)); - - return; - } - - // has no currency ID? Must have, so fill in using account preference: - if (null === $transaction->transaction_currency_id) { - $transaction->transaction_currency_id = (int)$currency->id; - Log::debug(sprintf('Transaction #%d has no currency setting, now set to %s', $transaction->id, $currency->code)); - $transaction->save(); - } - - // does not match the source account (see above)? Can be fixed - // when mismatch in transaction and NO foreign amount is set: - if (!((int)$transaction->transaction_currency_id === (int)$currency->id) && null === $transaction->foreign_amount) { - Log::debug( - sprintf( - 'Transaction #%d has a currency setting #%d that should be #%d. Amount remains %s, currency is changed.', - $transaction->id, - $transaction->transaction_currency_id, - $currency->id, - $transaction->amount - ) - ); - $transaction->transaction_currency_id = (int)$currency->id; - $transaction->save(); - } - - // grab opposing transaction: - /** @var TransactionJournal $journal */ - $journal = $transaction->transactionJournal; - /** @var Transaction $opposing */ - $opposing = $journal->transactions()->where('amount', '>', 0)->where('identifier', $transaction->identifier)->first(); - $opposingCurrency = $repository->findNull((int)$accountRepos->getMetaValue($opposing->account, 'currency_id')); - - if (null === $opposingCurrency) { - Log::error(sprintf('Account #%d ("%s") must have currency preference but has none.', $opposing->account->id, $opposing->account->name)); - - return; - } - - // if the destination account currency is the same, both foreign_amount and foreign_currency_id must be NULL for both transactions: - if ((int)$opposingCurrency->id === (int)$currency->id) { - // update both transactions to match: - $transaction->foreign_amount = null; - $transaction->foreign_currency_id = null; - $opposing->foreign_amount = null; - $opposing->foreign_currency_id = null; - $opposing->transaction_currency_id = $currency->id; - $transaction->save(); - $opposing->save(); - Log::debug( - sprintf( - 'Currency for account "%s" is %s, and currency for account "%s" is also - %s, so %s #%d (#%d and #%d) has been verified to be to %s exclusively.', - $opposing->account->name, $opposingCurrency->code, - $transaction->account->name, $transaction->transactionCurrency->code, - $journal->transactionType->type, $journal->id, - $transaction->id, $opposing->id, $currency->code - ) - ); - - return; - } - // if destination account currency is different, both transactions must have this currency as foreign currency id. - if (!((int)$opposingCurrency->id === (int)$currency->id)) { - $transaction->foreign_currency_id = $opposingCurrency->id; - $opposing->foreign_currency_id = $opposingCurrency->id; - $transaction->save(); - $opposing->save(); - Log::debug(sprintf('Verified foreign currency ID of transaction #%d and #%d', $transaction->id, $opposing->id)); - } - - // if foreign amount of one is null and the other is not, use this to restore: - if (null === $transaction->foreign_amount && null !== $opposing->foreign_amount) { - $transaction->foreign_amount = bcmul((string)$opposing->foreign_amount, '-1'); - $transaction->save(); - Log::debug(sprintf('Restored foreign amount of transaction (1) #%d to %s', $transaction->id, $transaction->foreign_amount)); - } - - // if foreign amount of one is null and the other is not, use this to restore (other way around) - if (null === $opposing->foreign_amount && null !== $transaction->foreign_amount) { - $opposing->foreign_amount = bcmul((string)$transaction->foreign_amount, '-1'); - $opposing->save(); - Log::debug(sprintf('Restored foreign amount of transaction (2) #%d to %s', $opposing->id, $opposing->foreign_amount)); - } - - // when both are zero, try to grab it from journal: - if (null === $opposing->foreign_amount && null === $transaction->foreign_amount) { - $foreignAmount = $journalRepos->getMetaField($journal, 'foreign_amount'); - if (null === $foreignAmount) { - Log::debug(sprintf('Journal #%d has missing foreign currency data, forced to do 1:1 conversion :(.', $transaction->transaction_journal_id)); - $transaction->foreign_amount = bcmul((string)$transaction->amount, '-1'); - $opposing->foreign_amount = bcmul((string)$opposing->amount, '-1'); - $transaction->save(); - $opposing->save(); - - return; - } - $foreignPositive = app('steam')->positive((string)$foreignAmount); - Log::debug( - sprintf( - 'Journal #%d has missing foreign currency info, try to restore from meta-data ("%s").', - $transaction->transaction_journal_id, - $foreignAmount - ) - ); - $transaction->foreign_amount = bcmul($foreignPositive, '-1'); - $opposing->foreign_amount = $foreignPositive; - $transaction->save(); - $opposing->save(); - } - - } - -} diff --git a/app/Console/Commands/VerifyDatabase.php b/app/Console/Commands/VerifyDatabase.php deleted file mode 100644 index 07458fe113..0000000000 --- a/app/Console/Commands/VerifyDatabase.php +++ /dev/null @@ -1,714 +0,0 @@ -. - */ - -/** @noinspection PhpDynamicAsStaticMethodCallInspection */ - -declare(strict_types=1); - -namespace FireflyIII\Console\Commands; - -use DB; -use Exception; -use FireflyIII\Models\Account; -use FireflyIII\Models\AccountMeta; -use FireflyIII\Models\AccountType; -use FireflyIII\Models\Budget; -use FireflyIII\Models\BudgetLimit; -use FireflyIII\Models\Category; -use FireflyIII\Models\LinkType; -use FireflyIII\Models\PiggyBankEvent; -use FireflyIII\Models\Transaction; -use FireflyIII\Models\TransactionCurrency; -use FireflyIII\Models\TransactionJournal; -use FireflyIII\Models\TransactionType; -use FireflyIII\Repositories\User\UserRepositoryInterface; -use FireflyIII\User; -use Illuminate\Console\Command; -use Illuminate\Database\Eloquent\Builder; -use Illuminate\Support\Collection; -use Log; -use Schema; -use stdClass; - -/** - * Class VerifyDatabase. - * - * @SuppressWarnings(PHPMD.ExcessiveClassComplexity) - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) - * @codeCoverageIgnore - */ -class VerifyDatabase extends Command -{ - /** - * The console command description. - * - * @var string - */ - protected $description = 'Will verify your database.'; - /** - * The name and signature of the console command. - * - * @var string - */ - protected $signature = 'firefly:verify'; - - /** - * Execute the console command. - */ - public function handle(): int - { - // if table does not exist, return false - if (!Schema::hasTable('users')) { - return 1; - } - - $this->reportEmptyBudgets(); - $this->reportEmptyCategories(); - $this->reportObject('tag'); - $this->reportAccounts(); - $this->reportBudgetLimits(); - $this->reportSum(); - $this->reportJournals(); - $this->reportTransactions(); - $this->reportDeletedAccounts(); - $this->reportNoTransactions(); - $this->reportTransfersBudgets(); - $this->reportIncorrectJournals(); - $this->repairPiggyBanks(); - $this->createLinkTypes(); - $this->createAccessTokens(); - $this->fixDoubleAmounts(); - $this->fixBadMeta(); - $this->removeBills(); - $this->enableCurrencies(); - $this->reportZeroAmount(); - - return 0; - } - - /** - * Create user access tokens, if not present already. - */ - private function createAccessTokens(): void - { - $count = 0; - $users = User::get(); - /** @var User $user */ - foreach ($users as $user) { - $pref = app('preferences')->getForUser($user, 'access_token', null); - if (null === $pref) { - $token = $user->generateAccessToken(); - app('preferences')->setForUser($user, 'access_token', $token); - $this->line(sprintf('Generated access token for user %s', $user->email)); - ++$count; - } - } - if (0 === $count) { - $this->info('All access tokens OK!'); - } - } - - /** - * Create default link types if necessary. - */ - private function createLinkTypes(): void - { - $count = 0; - $set = [ - 'Related' => ['relates to', 'relates to'], - 'Refund' => ['(partially) refunds', 'is (partially) refunded by'], - 'Paid' => ['(partially) pays for', 'is (partially) paid for by'], - 'Reimbursement' => ['(partially) reimburses', 'is (partially) reimbursed by'], - ]; - foreach ($set as $name => $values) { - $link = LinkType::where('name', $name)->where('outward', $values[0])->where('inward', $values[1])->first(); - if (null === $link) { - $link = new LinkType; - $link->name = $name; - $link->outward = $values[0]; - $link->inward = $values[1]; - ++$count; - } - $link->editable = false; - $link->save(); - } - if (0 === $count) { - $this->info('All link types OK!'); - } - } - - /** - * Will make sure that all currencies in use are actually enabled. - */ - private function enableCurrencies(): void - { - $found = []; - // get all meta entries - /** @var Collection $meta */ - $meta = AccountMeta::where('name', 'currency_id')->groupBy('data')->get(['data']); - foreach ($meta as $entry) { - $found[] = (int)$entry->data; - } - - // get all from journals: - /** @var Collection $journals */ - $journals = TransactionJournal::groupBy('transaction_currency_id')->get(['transaction_currency_id']); - foreach ($journals as $entry) { - $found[] = (int)$entry->transaction_currency_id; - } - - // get all from transactions - /** @var Collection $transactions */ - $transactions = Transaction::groupBy('transaction_currency_id')->get(['transaction_currency_id']); - foreach ($transactions as $entry) { - $found[] = (int)$entry->transaction_currency_id; - } - - // get all from budget limits - /** @var Collection $limits */ - $limits = BudgetLimit::groupBy('transaction_currency_id')->get(['transaction_currency_id']); - foreach ($limits as $entry) { - $found[] = (int)$entry->transaction_currency_id; - } - - $found = array_unique($found); - TransactionCurrency::whereIn('id', $found)->update(['enabled' => true]); - - } - - /** - * Fix the situation where the matching transactions of a journal somehow have non-matching categories or budgets. - * - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - */ - private function fixBadMeta(): void - { - // categories - $set = Transaction - ::leftJoin('category_transaction', 'category_transaction.transaction_id', '=', 'transactions.id') - ->whereNull('transactions.deleted_at') - ->get(['transactions.id', 'transaction_journal_id', 'identifier', 'category_transaction.category_id', 'category_transaction.id as ct_id']); - $results = []; - foreach ($set as $obj) { - $key = $obj->transaction_journal_id . '-' . $obj->identifier; - $category = (int)$obj->category_id; - - // value exists and is not category: - if (isset($results[$key]) && $results[$key] !== $category) { - $this->error( - sprintf( - 'Transaction #%d referred to the wrong category. Was category #%d but is fixed to be category #%d.', $obj->transaction_journal_id, - $category, $results[$key] - ) - ); - DB::table('category_transaction')->where('id', $obj->ct_id)->update(['category_id' => $results[$key]]); - - } - - // value does not exist: - if ($category > 0 && !isset($results[$key])) { - $results[$key] = $category; - } - } - - // budgets - $set = Transaction - ::leftJoin('budget_transaction', 'budget_transaction.transaction_id', '=', 'transactions.id') - ->whereNull('transactions.deleted_at') - ->get(['transactions.id', 'transaction_journal_id', 'identifier', 'budget_transaction.budget_id', 'budget_transaction.id as ct_id']); - $results = []; - foreach ($set as $obj) { - $key = $obj->transaction_journal_id . '-' . $obj->identifier; - $budget = (int)$obj->budget_id; - - // value exists and is not budget: - if (isset($results[$key]) && $results[$key] !== $budget) { - $this->error( - sprintf( - 'Transaction #%d referred to the wrong budget. Was budget #%d but is fixed to be budget #%d.', $obj->transaction_journal_id, $budget, - $results[$key] - ) - ); - DB::table('budget_transaction')->where('id', $obj->ct_id)->update(['budget_id' => $results[$key]]); - - } - - // value does not exist: - if ($budget > 0 && !isset($results[$key])) { - $results[$key] = $budget; - } - } - } - - /** - * Makes sure amounts are stored correctly. - * - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - */ - private function fixDoubleAmounts(): void - { - $count = 0; - // get invalid journals - $errored = []; - $journals = DB::table('transactions') - ->groupBy('transaction_journal_id') - ->get(['transaction_journal_id', DB::raw('SUM(amount) AS the_sum')]); - /** @var stdClass $entry */ - foreach ($journals as $entry) { - if (0 !== bccomp((string)$entry->the_sum, '0')) { - $errored[] = $entry->transaction_journal_id; - } - } - foreach ($errored as $journalId) { - // select and update: - $res = Transaction::whereNull('deleted_at')->where('transaction_journal_id', $journalId)->groupBy('amount')->get([DB::raw('MIN(id) as first_id')]); - $ids = $res->pluck('first_id')->toArray(); - DB::table('transactions')->whereIn('id', $ids)->update(['amount' => DB::raw('amount * -1')]); - ++$count; - // report about it - /** @var TransactionJournal $journal */ - $journal = TransactionJournal::find($journalId); - if (null === $journal) { - continue; - } - if (TransactionType::OPENING_BALANCE === $journal->transactionType->type) { - $this->error( - sprintf( - 'Transaction #%d was stored incorrectly. One of your asset accounts may show the wrong balance. Please visit /transactions/show/%d to verify the opening balance.', - $journalId, $journalId - ) - ); - } - if (TransactionType::OPENING_BALANCE !== $journal->transactionType->type) { - $this->error( - sprintf( - 'Transaction #%d was stored incorrectly. Could be that the transaction shows the wrong amount. Please visit /transactions/show/%d to verify the opening balance.', - $journalId, $journalId - ) - ); - } - } - if (0 === $count) { - $this->info('Amount integrity OK!'); - } - } - - /** - * Removes bills from journals that should not have bills. - */ - private function removeBills(): void - { - /** @var TransactionType $withdrawal */ - $withdrawal = TransactionType::where('type', TransactionType::WITHDRAWAL)->first(); - $journals = TransactionJournal::whereNotNull('bill_id') - ->where('transaction_type_id', '!=', $withdrawal->id)->get(); - /** @var TransactionJournal $journal */ - foreach ($journals as $journal) { - $this->line(sprintf('Transaction journal #%d should not be linked to bill #%d.', $journal->id, $journal->bill_id)); - $journal->bill_id = null; - $journal->save(); - } - } - - /** - * Eeport (and fix) piggy banks. Make sure there are only transfers linked to piggy bank events. - */ - private function repairPiggyBanks(): void - { - $set = PiggyBankEvent::with(['PiggyBank', 'TransactionJournal', 'TransactionJournal.TransactionType'])->get(); - $set->each( - function (PiggyBankEvent $event) { - if (null === $event->transaction_journal_id) { - return true; - } - /** @var TransactionJournal $journal */ - $journal = $event->transactionJournal()->first(); - if (null === $journal) { - return true; - } - - $type = $journal->transactionType->type; - if (TransactionType::TRANSFER !== $type) { - $event->transaction_journal_id = null; - $event->save(); - $this->line(sprintf('Piggy bank #%d was referenced by an invalid event. This has been fixed.', $event->piggy_bank_id)); - } - - return true; - } - ); - } - - /** - * Reports on accounts with no transactions. - */ - private function reportAccounts(): void - { - $set = Account::leftJoin('transactions', 'transactions.account_id', '=', 'accounts.id') - ->leftJoin('users', 'accounts.user_id', '=', 'users.id') - ->groupBy(['accounts.id', 'accounts.encrypted', 'accounts.name', 'accounts.user_id', 'users.email']) - ->whereNull('transactions.account_id') - ->get( - ['accounts.id', 'accounts.encrypted', 'accounts.name', 'accounts.user_id', 'users.email'] - ); - - /** @var stdClass $entry */ - foreach ($set as $entry) { - $name = $entry->name; - $line = 'User #%d (%s) has account #%d ("%s") which has no transactions.'; - $line = sprintf($line, $entry->user_id, $entry->email, $entry->id, $name); - $this->line($line); - } - } - - /** - * Reports on budgets with no budget limits (which makes them pointless). - */ - private function reportBudgetLimits(): void - { - $set = Budget::leftJoin('budget_limits', 'budget_limits.budget_id', '=', 'budgets.id') - ->leftJoin('users', 'budgets.user_id', '=', 'users.id') - ->groupBy(['budgets.id', 'budgets.name', 'budgets.encrypted', 'budgets.user_id', 'users.email']) - ->whereNull('budget_limits.id') - ->get(['budgets.id', 'budgets.name', 'budgets.user_id', 'budgets.encrypted', 'users.email']); - - /** @var Budget $entry */ - foreach ($set as $entry) { - $line = sprintf( - 'User #%d (%s) has budget #%d ("%s") which has no budget limits.', - $entry->user_id, - $entry->email, - $entry->id, - $entry->name - ); - $this->line($line); - } - } - - /** - * Reports on deleted accounts that still have not deleted transactions or journals attached to them. - */ - private function reportDeletedAccounts(): void - { - $set = Account::leftJoin('transactions', 'transactions.account_id', '=', 'accounts.id') - ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') - ->whereNotNull('accounts.deleted_at') - ->whereNotNull('transactions.id') - ->where( - function (Builder $q) { - $q->whereNull('transactions.deleted_at'); - $q->orWhereNull('transaction_journals.deleted_at'); - } - ) - ->get( - ['accounts.id as account_id', 'accounts.deleted_at as account_deleted_at', 'transactions.id as transaction_id', - 'transactions.deleted_at as transaction_deleted_at', 'transaction_journals.id as journal_id', - 'transaction_journals.deleted_at as journal_deleted_at',] - ); - /** @var stdClass $entry */ - foreach ($set as $entry) { - $date = $entry->transaction_deleted_at ?? $entry->journal_deleted_at; - $this->error( - 'Error: Account #' . $entry->account_id . ' should have been deleted, but has not.' . - ' Find it in the table called "accounts" and change the "deleted_at" field to: "' . $date . '"' - ); - } - } - - /** - * Report on budgets with no transactions or journals. - */ - private function reportEmptyBudgets(): void - { - $set = Budget::leftJoin('budget_transaction_journal', 'budgets.id', '=', 'budget_transaction_journal.budget_id') - ->leftJoin('users', 'budgets.user_id', '=', 'users.id') - ->distinct() - ->whereNull('budget_transaction_journal.budget_id') - ->whereNull('budgets.deleted_at') - ->get(['budgets.id', 'budgets.name', 'budgets.user_id', 'users.email']); - - /** @var stdClass $entry */ - foreach ($set as $entry) { - $objName = $entry->name; - - // also count the transactions: - $countTransactions = DB::table('budget_transaction')->where('budget_id', $entry->id)->count(); - - if (0 === $countTransactions) { - $line = sprintf( - 'User #%d (%s) has budget #%d ("%s") which has no transactions.', - $entry->user_id, - $entry->email, - $entry->id, - $objName - ); - $this->line($line); - } - } - } - - /** - * Report on categories with no transactions or journals. - */ - private function reportEmptyCategories(): void - { - $set = Category::leftJoin('category_transaction_journal', 'categories.id', '=', 'category_transaction_journal.category_id') - ->leftJoin('users', 'categories.user_id', '=', 'users.id') - ->distinct() - ->whereNull('category_transaction_journal.category_id') - ->whereNull('categories.deleted_at') - ->get(['categories.id', 'categories.name', 'categories.user_id', 'users.email']); - - /** @var stdClass $entry */ - foreach ($set as $entry) { - $objName = $entry->name; - - // also count the transactions: - $countTransactions = DB::table('category_transaction')->where('category_id', $entry->id)->count(); - - if (0 === $countTransactions) { - $line = sprintf( - 'User #%d (%s) has category #%d ("%s") which has no transactions.', - $entry->user_id, - $entry->email, - $entry->id, - $objName - ); - $this->line($line); - } - } - } - - /** - * Report on journals with bad account types linked to them. - */ - private function reportIncorrectJournals(): void - { - $configuration = [ - // a withdrawal can not have revenue account: - TransactionType::WITHDRAWAL => [AccountType::REVENUE], - // deposit cannot have an expense account: - TransactionType::DEPOSIT => [AccountType::EXPENSE], - // transfer cannot have either: - TransactionType::TRANSFER => [AccountType::EXPENSE, AccountType::REVENUE], - ]; - foreach ($configuration as $transactionType => $accountTypes) { - $set = TransactionJournal::leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id') - ->leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') - ->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id') - ->leftJoin('account_types', 'account_types.id', 'accounts.account_type_id') - ->leftJoin('users', 'users.id', '=', 'transaction_journals.user_id') - ->where('transaction_types.type', $transactionType) - ->whereIn('account_types.type', $accountTypes) - ->whereNull('transaction_journals.deleted_at') - ->get( - ['transaction_journals.id', 'transaction_journals.user_id', 'users.email', 'account_types.type as a_type', - 'transaction_types.type',] - ); - foreach ($set as $entry) { - $this->error( - sprintf( - 'Transaction journal #%d (user #%d, %s) is of type "%s" but ' . - 'is linked to a "%s". The transaction journal should be recreated.', - $entry->id, - $entry->user_id, - $entry->email, - $entry->type, - $entry->a_type - ) - ); - } - } - } - - /** - * Any deleted transaction journals that have transactions that are NOT deleted:. - */ - private function reportJournals(): void - { - $count = 0; - $set = TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') - ->whereNotNull('transaction_journals.deleted_at')// USE THIS - ->whereNull('transactions.deleted_at') - ->whereNotNull('transactions.id') - ->get( - [ - 'transaction_journals.id as journal_id', - 'transaction_journals.description', - 'transaction_journals.deleted_at as journal_deleted', - 'transactions.id as transaction_id', - 'transactions.deleted_at as transaction_deleted_at',] - ); - /** @var stdClass $entry */ - foreach ($set as $entry) { - $this->error( - 'Error: Transaction #' . $entry->transaction_id . ' should have been deleted, but has not.' . - ' Find it in the table called "transactions" and change the "deleted_at" field to: "' . $entry->journal_deleted . '"' - ); - ++$count; - } - if (0 === $count) { - $this->info('No orphaned transactions!'); - } - } - - /** - * Report on journals without transactions. - */ - private function reportNoTransactions(): void - { - $count = 0; - $set = TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') - ->groupBy('transaction_journals.id') - ->whereNull('transactions.transaction_journal_id') - ->get(['transaction_journals.id']); - - foreach ($set as $entry) { - $this->error( - 'Error: Journal #' . $entry->id . ' has zero transactions. Open table "transaction_journals" and delete the entry with id #' . $entry->id - ); - ++$count; - } - if (0 === $count) { - $this->info('No orphaned journals!'); - } - } - - /** - * Report on things with no linked journals. - * - * @param string $name - */ - private function reportObject(string $name): void - { - $plural = str_plural($name); - $class = sprintf('FireflyIII\Models\%s', ucfirst($name)); - $field = 'tag' === $name ? 'tag' : 'name'; - /** @noinspection PhpUndefinedMethodInspection */ - $set = $class::leftJoin($name . '_transaction_journal', $plural . '.id', '=', $name . '_transaction_journal.' . $name . '_id') - ->leftJoin('users', $plural . '.user_id', '=', 'users.id') - ->distinct() - ->whereNull($name . '_transaction_journal.' . $name . '_id') - ->whereNull($plural . '.deleted_at') - ->get([$plural . '.id', $plural . '.' . $field . ' as name', $plural . '.user_id', 'users.email']); - - /** @var stdClass $entry */ - foreach ($set as $entry) { - $objName = $entry->name; - - $line = sprintf( - 'User #%d (%s) has %s #%d ("%s") which has no transactions.', - $entry->user_id, - $entry->email, - $name, - $entry->id, - $objName - ); - $this->line($line); - } - } - - /** - * Reports for each user when the sum of their transactions is not zero. - */ - private function reportSum(): void - { - /** @var UserRepositoryInterface $userRepository */ - $userRepository = app(UserRepositoryInterface::class); - - /** @var User $user */ - foreach ($userRepository->all() as $user) { - $sum = (string)$user->transactions()->sum('amount'); - if (0 !== bccomp($sum, '0')) { - $this->error('Error: Transactions for user #' . $user->id . ' (' . $user->email . ') are off by ' . $sum . '!'); - } - if (0 === bccomp($sum, '0')) { - $this->info(sprintf('Amount integrity OK for user #%d', $user->id)); - } - } - } - - /** - * Reports on deleted transactions that are connected to a not deleted journal. - */ - private function reportTransactions(): void - { - $set = Transaction::leftJoin('transaction_journals', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') - ->whereNotNull('transactions.deleted_at') - ->whereNull('transaction_journals.deleted_at') - ->get( - ['transactions.id as transaction_id', 'transactions.deleted_at as transaction_deleted', 'transaction_journals.id as journal_id', - 'transaction_journals.deleted_at',] - ); - /** @var stdClass $entry */ - foreach ($set as $entry) { - $this->error( - 'Error: Transaction journal #' . $entry->journal_id . ' should have been deleted, but has not.' . - ' Find it in the table called "transaction_journals" and change the "deleted_at" field to: "' . $entry->transaction_deleted . '"' - ); - } - } - - /** - * Report on transfers that have budgets. - */ - private function reportTransfersBudgets(): void - { - $set = TransactionJournal::distinct() - ->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id') - ->leftJoin('budget_transaction_journal', 'transaction_journals.id', '=', 'budget_transaction_journal.transaction_journal_id') - ->whereIn('transaction_types.type', [TransactionType::TRANSFER, TransactionType::DEPOSIT]) - ->whereNotNull('budget_transaction_journal.budget_id')->get(['transaction_journals.*']); - - /** @var TransactionJournal $entry */ - foreach ($set as $entry) { - $this->error( - sprintf( - 'Error: Transaction journal #%d is a %s, but has a budget. Edit it without changing anything, so the budget will be removed.', - $entry->id, - $entry->transactionType->type - ) - ); - } - } - - /** - * Collect all journals with empty amount. - */ - private function reportZeroAmount(): void - { - $set = Transaction::where('amount', 0)->get(['transaction_journal_id'])->pluck('transaction_journal_id')->toArray(); - $set = array_unique($set); - /** @var Collection $journals */ - $journals = TransactionJournal::whereIn('id', $set)->get(); - /** @var TransactionJournal $journal */ - foreach ($journals as $journal) { - $message = sprintf( - 'Transaction "%s" (#%d), owned by user %s, has amount zero (0.00). It should be deleted.', $journal->description, - $journal->id, $journal->user->email - ); - $this->error($message); - } - } - -} diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 9ede16c879..161cd3b48f 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -49,7 +49,7 @@ class Kernel extends ConsoleKernel /** * Define the application's command schedule. * - * @param \Illuminate\Console\Scheduling\Schedule $schedule + * @param Schedule $schedule */ protected function schedule(Schedule $schedule): void { diff --git a/app/Events/RequestedReportOnJournals.php b/app/Events/RequestedReportOnJournals.php index 4b869ee173..23e5e7c26c 100644 --- a/app/Events/RequestedReportOnJournals.php +++ b/app/Events/RequestedReportOnJournals.php @@ -43,6 +43,7 @@ declare(strict_types=1); namespace FireflyIII\Events; +use Illuminate\Broadcasting\Channel; use Illuminate\Broadcasting\InteractsWithSockets; use Illuminate\Broadcasting\PrivateChannel; use Illuminate\Foundation\Events\Dispatchable; @@ -80,7 +81,7 @@ class RequestedReportOnJournals /** * Get the channels the event should broadcast on. * - * @return \Illuminate\Broadcasting\Channel|array + * @return Channel|array */ public function broadcastOn() { diff --git a/app/Events/UpdatedTransactionJournal.php b/app/Events/StoredTransactionGroup.php similarity index 61% rename from app/Events/UpdatedTransactionJournal.php rename to app/Events/StoredTransactionGroup.php index 3d3ef2fff6..e558dd4d36 100644 --- a/app/Events/UpdatedTransactionJournal.php +++ b/app/Events/StoredTransactionGroup.php @@ -1,8 +1,8 @@ journal = $journal; + $this->transactionGroup = $transactionGroup; + $this->applyRules = $applyRules; } } diff --git a/app/Events/StoredTransactionJournal.php b/app/Events/UpdatedTransactionGroup.php similarity index 67% rename from app/Events/StoredTransactionJournal.php rename to app/Events/UpdatedTransactionGroup.php index c631e7b148..af12194b17 100644 --- a/app/Events/StoredTransactionJournal.php +++ b/app/Events/UpdatedTransactionGroup.php @@ -1,8 +1,8 @@ journal = $journal; + $this->transactionGroup = $transactionGroup; } } diff --git a/app/Exceptions/FireflyException.php b/app/Exceptions/FireflyException.php index 16c24f25aa..79afaf860a 100644 --- a/app/Exceptions/FireflyException.php +++ b/app/Exceptions/FireflyException.php @@ -28,6 +28,7 @@ use Exception; /** * Class FireflyException. + * @codeCoverageIgnore */ class FireflyException extends Exception { diff --git a/app/Exceptions/GracefulNotFoundHandler.php b/app/Exceptions/GracefulNotFoundHandler.php new file mode 100644 index 0000000000..2866feaf1a --- /dev/null +++ b/app/Exceptions/GracefulNotFoundHandler.php @@ -0,0 +1,228 @@ +. + */ + +namespace FireflyIII\Exceptions; + + +use Exception; +use FireflyIII\Models\Account; +use FireflyIII\Models\Attachment; +use FireflyIII\Models\Bill; +use FireflyIII\Models\TransactionGroup; +use FireflyIII\Models\TransactionJournal; +use FireflyIII\User; +use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; +use Illuminate\Http\Request; +use Log; + +/** + * Class GracefulNotFoundHandler + */ +class GracefulNotFoundHandler extends ExceptionHandler +{ + /** + * Render an exception into an HTTP response. + * + * @param Request $request + * @param Exception $exception + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.NPathComplexity) + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + * + * @return mixed + */ + public function render($request, Exception $exception) + { + $route = $request->route(); + if(null === $route) { + return parent::render($request, $exception); + } + $name = $route->getName(); + if (!auth()->check()) { + return parent::render($request, $exception); + } + + switch ($name) { + default: + Log::error(sprintf('GracefulNotFoundHandler cannot handle route with name "%s"', $name)); + + return parent::render($request, $exception); + case 'accounts.show': + return $this->handleAccount($request, $exception); + case 'transactions.show': + return $this->handleGroup($request, $exception); + break; + case 'attachments.show': + case 'attachments.edit': + // redirect to original attachment holder. + return $this->handleAttachment($request, $exception); + break; + case 'bills.show': + $request->session()->reflash(); + + return redirect(route('bills.index')); + break; + case 'currencies.show': + $request->session()->reflash(); + + return redirect(route('currencies.index')); + break; + case 'budgets.show': + $request->session()->reflash(); + + return redirect(route('budgets.index')); + break; + case 'piggy-banks.show': + $request->session()->reflash(); + + return redirect(route('piggy-banks.index')); + break; + case 'recurring.show': + $request->session()->reflash(); + + return redirect(route('recurring.index')); + break; + case 'tags.show': + $request->session()->reflash(); + + return redirect(route('tags.index')); + break; + case 'categories.show': + $request->session()->reflash(); + + return redirect(route('categories.index')); + break; + case 'rules.edit': + $request->session()->reflash(); + + return redirect(route('rules.index')); + break; + case 'transactions.mass.edit': + case 'transactions.mass.delete': + case 'transactions.bulk.edit': + $request->session()->reflash(); + + return redirect(route('index')); + break; + } + } + + /** + * @param Request $request + * @param Exception $exception + * + * @return \Illuminate\Http\Response|\Symfony\Component\HttpFoundation\Response + */ + private function handleAccount($request, Exception $exception) + { + Log::debug('404 page is probably a deleted account. Redirect to overview of account types.'); + /** @var User $user */ + $user = auth()->user(); + $route = $request->route(); + $accountId = (int)$route->parameter('account'); + /** @var Account $account */ + $account = $user->accounts()->with(['accountType'])->withTrashed()->find($accountId); + if (null === $account) { + Log::error(sprintf('Could not find account %d, so give big fat error.', $accountId)); + + return parent::render($request, $exception); + } + $type = $account->accountType; + $shortType = config(sprintf('firefly.shortNamesByFullName.%s', $type->type)); + $request->session()->reflash(); + + return redirect(route('accounts.index', [$shortType])); + } + + private function handleAttachment(Request $request, Exception $exception) + { + Log::debug('404 page is probably a deleted attachment. Redirect to parent object.'); + /** @var User $user */ + $user = auth()->user(); + $route = $request->route(); + $attachmentId = (int)$route->parameter('attachment'); + /** @var Attachment $attachment */ + $attachment = $user->attachments()->withTrashed()->find($attachmentId); + if (null === $attachment) { + Log::error(sprintf('Could not find attachment %d, so give big fat error.', $attachmentId)); + + return parent::render($request, $exception); + } + // get bindable. + if (TransactionJournal::class === $attachment->attachable_type) { + // is linked to journal, get group of journal (if not also deleted) + /** @var TransactionJournal $journal */ + $journal = $user->transactionJournals()->withTrashed()->find($attachment->attachable_id); + if (null !== $journal) { + return redirect(route('transactions.show', [$journal->transaction_group_id])); + } + + } + if (Bill::class === $attachment->attachable_type) { + // is linked to bill. + /** @var Bill $bill */ + $bill = $user->bills()->withTrashed()->find($attachment->attachable_id); + if (null !== $bill) { + return redirect(route('bills.show', [$bill->id])); + } + } + + Log::error(sprintf('Could not redirect attachment %d, its linked to a %s.', $attachmentId, $attachment->attachable_type)); + + return parent::render($request, $exception); + } + + /** + * @param $request + * @param Exception $exception + * + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\Response|\Illuminate\Routing\Redirector|\Symfony\Component\HttpFoundation\Response + */ + private function handleGroup($request, Exception $exception) + { + Log::debug('404 page is probably a deleted group. Redirect to overview of group types.'); + /** @var User $user */ + $user = auth()->user(); + $route = $request->route(); + $groupId = (int)$route->parameter('transactionGroup'); + + /** @var TransactionGroup $group */ + $group = $user->transactionGroups()->withTrashed()->find($groupId); + if (null === $group) { + Log::error(sprintf('Could not find group %d, so give big fat error.', $groupId)); + + return parent::render($request, $exception); + } + /** @var TransactionJournal $journal */ + $journal = $group->transactionJournals()->withTrashed()->first(); + if (null === $journal) { + Log::error(sprintf('Could not find journal for group %d, so give big fat error.', $groupId)); + + return parent::render($request, $exception); + } + $type = $journal->transactionType->type; + $request->session()->reflash(); + + return redirect(route('transactions.index', [strtolower($type)])); + + } + +} \ No newline at end of file diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 5d2cdb2426..5e6ca46764 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -81,7 +81,7 @@ class Handler extends ExceptionHandler return response()->json( [ 'message' => $exception->getMessage(), - 'exception' => \get_class($exception), + 'exception' => get_class($exception), 'line' => $exception->getLine(), 'file' => $exception->getFile(), 'trace' => $exception->getTrace(), @@ -89,9 +89,15 @@ class Handler extends ExceptionHandler ); } - return response()->json(['message' => 'Internal Firefly III Exception. See log files.', 'exception' => \get_class($exception)], 500); + return response()->json(['message' => 'Internal Firefly III Exception. See log files.', 'exception' => get_class($exception)], 500); } + if($exception instanceof NotFoundHttpException) { + $handler = app(GracefulNotFoundHandler::class); + return $handler->render($request, $exception); + } + + if ($exception instanceof FireflyException || $exception instanceof ErrorException || $exception instanceof OAuthServerException) { $isDebug = config('app.debug'); @@ -104,11 +110,11 @@ class Handler extends ExceptionHandler /** * Report or log an exception. * - * This is a great spot to send exceptions to Sentry, Bugsnag, etc. + * This is a great spot to send exceptions to Sentry etc. * * @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's five its fine. * - * @param \Exception $exception + * @param Exception $exception * * @return mixed|void * @@ -131,7 +137,7 @@ class Handler extends ExceptionHandler $userData['email'] = auth()->user()->email; } $data = [ - 'class' => \get_class($exception), + 'class' => get_class($exception), 'errorMessage' => $exception->getMessage(), 'time' => date('r'), 'stackTrace' => $exception->getTraceAsString(), diff --git a/app/Exceptions/NotImplementedException.php b/app/Exceptions/NotImplementedException.php index 7254d56d34..060cd32b67 100644 --- a/app/Exceptions/NotImplementedException.php +++ b/app/Exceptions/NotImplementedException.php @@ -24,9 +24,12 @@ declare(strict_types=1); namespace FireflyIII\Exceptions; +use Exception; + /** * Class NotImplementedException. + * @codeCoverageIgnore */ -class NotImplementedException extends \Exception +class NotImplementedException extends Exception { } diff --git a/app/Exceptions/ValidationException.php b/app/Exceptions/ValidationException.php index 602b4df9b0..65c4037fd6 100644 --- a/app/Exceptions/ValidationException.php +++ b/app/Exceptions/ValidationException.php @@ -24,9 +24,12 @@ declare(strict_types=1); namespace FireflyIII\Exceptions; +use Exception; + /** * Class ValidationExceptions. + * @codeCoverageIgnore */ -class ValidationException extends \Exception +class ValidationException extends Exception { } diff --git a/app/Export/Collector/AttachmentCollector.php b/app/Export/Collector/AttachmentCollector.php deleted file mode 100644 index 271ffd595a..0000000000 --- a/app/Export/Collector/AttachmentCollector.php +++ /dev/null @@ -1,154 +0,0 @@ -. - */ - -declare(strict_types=1); - -namespace FireflyIII\Export\Collector; - -use Carbon\Carbon; -use Crypt; -use FireflyIII\Models\Attachment; -use FireflyIII\Repositories\Attachment\AttachmentRepositoryInterface; -use Illuminate\Contracts\Encryption\DecryptException; -use Illuminate\Contracts\Filesystem\FileNotFoundException; -use Illuminate\Support\Collection; -use Log; -use Storage; - -/** - * Class AttachmentCollector. - * - * @deprecated - * @codeCoverageIgnore - */ -class AttachmentCollector extends BasicCollector implements CollectorInterface -{ - /** @var Carbon The end date of the range. */ - private $end; - /** @var \Illuminate\Contracts\Filesystem\Filesystem File system */ - private $exportDisk; - /** @var AttachmentRepositoryInterface Attachment repository */ - private $repository; - /** @var Carbon Start date of range */ - private $start; - /** @var \Illuminate\Contracts\Filesystem\Filesystem Disk with uploads on it */ - private $uploadDisk; - - /** - * AttachmentCollector constructor. - */ - public function __construct() - { - /** @var AttachmentRepositoryInterface repository */ - $this->repository = app(AttachmentRepositoryInterface::class); - // make storage: - $this->uploadDisk = Storage::disk('upload'); - $this->exportDisk = Storage::disk('export'); - - parent::__construct(); - } - - /** - * Run the routine. - * - * @return bool - */ - public function run(): bool - { - // grab all the users attachments: - $attachments = $this->getAttachments(); - - /** @var Attachment $attachment */ - foreach ($attachments as $attachment) { - $this->exportAttachment($attachment); - } - - return true; - } - - /** - * Set the start and end date. - * - * @param Carbon $start - * @param Carbon $end - */ - public function setDates(Carbon $start, Carbon $end) - { - $this->start = $start; - $this->end = $end; - } - - /** @noinspection MultipleReturnStatementsInspection */ - /** - * Export attachments. - * - * @param Attachment $attachment - * - * @return bool - * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException - */ - private function exportAttachment(Attachment $attachment): bool - { - $file = $attachment->fileName(); - $decrypted = false; - if ($this->uploadDisk->exists($file)) { - try { - $decrypted = Crypt::decrypt($this->uploadDisk->get($file)); - } catch (DecryptException|FileNotFoundException $e) { - Log::error('Catchable error: could not decrypt attachment #' . $attachment->id . ' because: ' . $e->getMessage()); - - return false; - } - } - if (false === $decrypted) { - return false; - } - $exportFile = $this->exportFileName($attachment); - $this->exportDisk->put($exportFile, $decrypted); - $this->getEntries()->push($exportFile); - - return true; - } - - /** - * Returns the new file name for the export file. - * - * @param $attachment - * - * @return string - */ - private function exportFileName($attachment): string - { - return sprintf('%s-Attachment nr. %s - %s', $this->job->key, (string)$attachment->id, $attachment->filename); - } - - /** - * Get the attachments. - * - * @return Collection - */ - private function getAttachments(): Collection - { - $this->repository->setUser($this->user); - - return $this->repository->getBetween($this->start, $this->end); - } -} diff --git a/app/Export/Collector/BasicCollector.php b/app/Export/Collector/BasicCollector.php deleted file mode 100644 index c1db2a82a3..0000000000 --- a/app/Export/Collector/BasicCollector.php +++ /dev/null @@ -1,94 +0,0 @@ -. - */ - -declare(strict_types=1); - -namespace FireflyIII\Export\Collector; - -use FireflyIII\Models\ExportJob; -use FireflyIII\User; -use Illuminate\Support\Collection; - -/** - * Class BasicCollector. - * - * @codeCoverageIgnore - * @deprecated - */ -class BasicCollector -{ - /** @var ExportJob The job to export. */ - protected $job; - /** @var User The user */ - protected $user; - /** @var Collection All the entries. */ - private $entries; - - /** - * BasicCollector constructor. - */ - public function __construct() - { - $this->entries = new Collection; - } - - /** - * Get all entries. - * - * @return Collection - */ - public function getEntries(): Collection - { - return $this->entries; - } - - /** - * Set entries. - * - * @param Collection $entries - */ - public function setEntries(Collection $entries): void - { - $this->entries = $entries; - } - - /** - * Set export job. - * - * @param ExportJob $job - */ - public function setJob(ExportJob $job): void - { - $this->job = $job; - $this->user = $job->user; - } - - /** - * Set user. - * - * @param User $user - */ - public function setUser(User $user): void - { - $this->user = $user; - } -} diff --git a/app/Export/Collector/UploadCollector.php b/app/Export/Collector/UploadCollector.php deleted file mode 100644 index 85852944c5..0000000000 --- a/app/Export/Collector/UploadCollector.php +++ /dev/null @@ -1,123 +0,0 @@ -. - */ - -declare(strict_types=1); - -namespace FireflyIII\Export\Collector; - -use Crypt; -use Exception; -use Illuminate\Contracts\Encryption\DecryptException; -use Log; -use Storage; - -/** - * Class UploadCollector. - * - * @codeCoverageIgnore - * @deprecated - */ -class UploadCollector extends BasicCollector implements CollectorInterface -{ - /** @var \Illuminate\Contracts\Filesystem\Filesystem */ - private $exportDisk; - /** @var \Illuminate\Contracts\Filesystem\Filesystem */ - private $uploadDisk; - - /** - * AttachmentCollector constructor. - */ - public function __construct() - { - parent::__construct(); - $this->uploadDisk = Storage::disk('upload'); - $this->exportDisk = Storage::disk('export'); - } - - /** - * Is called from the outside to actually start the export. - * - * @return bool - */ - public function run(): bool - { - Log::debug('Going to collect attachments', ['key' => $this->job->key]); - $this->collectModernUploads(); - - return true; - } - - /** - * This method collects all the uploads that are uploaded using the new importer. So after the summer of 2016. - * - * @return bool - */ - private function collectModernUploads(): bool - { - $set = $this->job->user->importJobs()->whereIn('status', ['import_complete', 'finished'])->get(['import_jobs.*']); - Log::debug(sprintf('Found %d import jobs', $set->count())); - $keys = []; - if ($set->count() > 0) { - $keys = $set->pluck('key')->toArray(); - } - - foreach ($keys as $key) { - $this->processModernUpload($key); - } - - return true; - } - - /** @noinspection MultipleReturnStatementsInspection */ - /** - * Process new file uploads. - * - * @param string $key - * - * @return bool - */ - private function processModernUpload(string $key): bool - { - // find job associated with import file: - $job = $this->job->user->importJobs()->where('key', $key)->first(); - if (null === $job) { - return false; - } - - // find the file for this import: - $content = ''; - try { - $content = Crypt::decrypt($this->uploadDisk->get(sprintf('%s.upload', $key))); - } catch (Exception | DecryptException $e) { - Log::error(sprintf('Could not decrypt old import file "%s". Skipped because: %s', $key, $e->getMessage())); - } - - if ('' !== $content) { - // add to export disk. - $date = $job->created_at->format('Y-m-d'); - $file = sprintf('%s-Old %s import dated %s.%s', $this->job->key, strtoupper($job->file_type), $date, $job->file_type); - $this->exportDisk->put($file, $content); - $this->getEntries()->push($file); - } - - return true; - } -} diff --git a/app/Export/Entry/Entry.php b/app/Export/Entry/Entry.php deleted file mode 100644 index 01386cbfcc..0000000000 --- a/app/Export/Entry/Entry.php +++ /dev/null @@ -1,189 +0,0 @@ -. - */ - -declare(strict_types=1); - -namespace FireflyIII\Export\Entry; - -use FireflyIII\Models\Transaction; - -/** - * To extend the exported object, in case of new features in Firefly III for example, - * do the following:. - * - * - Add the field(s) to this class. If you add more than one related field, add a new object. - * - Make sure the "fromJournal"-routine fills these fields. - * - Add them to the static function that returns its type (key=value. Remember that the only - * valid types can be found in config/csv.php (under "roles"). - * - * These new entries should be should be strings and numbers as much as possible. - * - * - * - * Class Entry - * - * @SuppressWarnings(PHPMD.LongVariable) - * @SuppressWarnings(PHPMD.TooManyFields) - * - * @codeCoverageIgnore - * @deprecated - */ -final class Entry -{ - /** @var string The amount. */ - public $amount; - /** @var string Asset account BIC */ - public $asset_account_bic; - /** @var string Asset account IBAN */ - public $asset_account_iban; - /** @var string Asset account ID */ - public $asset_account_id; - /** @var string Asset account name */ - public $asset_account_name; - /** @var string Asset account number */ - public $asset_account_number; - /** @var string Asset account currency code */ - public $asset_currency_code; - /** @var string Bill ID */ - public $bill_id; - /** @var string Bill name */ - public $bill_name; - /** @var string Budget ID */ - public $budget_id; - /** @var string Budget name */ - public $budget_name; - /** @var string Category ID */ - public $category_id; - /** @var string Category name */ - public $category_name; - /** @var string The currency code. */ - public $currency_code; - /** @var string The date. */ - public $date; - /** @var string The description */ - public $description; - /** @var string Foreign amount */ - public $foreign_amount = '0'; - /** @var string The foreign currency code */ - public $foreign_currency_code = ''; - /** @var int ID of the journal */ - public $journal_id; - /** @var string Notes */ - public $notes; - /** @var string Opposing account BIC */ - public $opposing_account_bic; - /** @var string Opposing account IBAN */ - public $opposing_account_iban; - /** @var string Opposing account ID */ - public $opposing_account_id; - /** @var string Opposing account name */ - public $opposing_account_name; - /** @var string Opposing account number */ - public $opposing_account_number; - /** @var string Opposing account code */ - public $opposing_currency_code; - /** @var string Tags */ - public $tags; - /** @var int ID of the transaction */ - public $transaction_id = 0; - /** @var string Transaction type */ - public $transaction_type; - - /** - * Entry constructor. - */ - private function __construct() - { - } - - /** - * Converts a given transaction (as collected by the collector) into an export entry. - * - * @SuppressWarnings(PHPMD.CyclomaticComplexity) // complex but little choice. - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) // cannot be helped - * - * @param Transaction $transaction - * - * @return Entry - */ - public static function fromTransaction(Transaction $transaction): Entry - { - $entry = new self(); - $entry->journal_id = $transaction->journal_id; - $entry->transaction_id = $transaction->id; - $entry->date = $transaction->date->format('Ymd'); - $entry->description = $transaction->description; - if ('' !== (string)$transaction->transaction_description) { - $entry->description = $transaction->transaction_description . '(' . $transaction->description . ')'; - } - $entry->currency_code = $transaction->transactionCurrency->code; - $entry->amount = (string)round($transaction->transaction_amount, $transaction->transactionCurrency->decimal_places); - - $entry->foreign_currency_code = null === $transaction->foreign_currency_id ? null : $transaction->foreignCurrency->code; - $entry->foreign_amount = null === $transaction->foreign_currency_id - ? null - : (string)round( - $transaction->transaction_foreign_amount, - $transaction->foreignCurrency->decimal_places - ); - - $entry->transaction_type = $transaction->transaction_type_type; - $entry->asset_account_id = (string)$transaction->account_id; - $entry->asset_account_name = $transaction->account_name; - $entry->asset_account_iban = $transaction->account_iban; - $entry->asset_account_number = $transaction->account_number; - $entry->asset_account_bic = $transaction->account_bic; - $entry->asset_currency_code = $transaction->account_currency_code; - - $entry->opposing_account_id = (string)$transaction->opposing_account_id; - $entry->opposing_account_name = $transaction->opposing_account_name; - $entry->opposing_account_iban = $transaction->opposing_account_iban; - $entry->opposing_account_number = $transaction->opposing_account_number; - $entry->opposing_account_bic = $transaction->opposing_account_bic; - $entry->opposing_currency_code = $transaction->opposing_currency_code; - - // budget - $entry->budget_id = (string)$transaction->transaction_budget_id; - $entry->budget_name = $transaction->transaction_budget_name; - if (null === $transaction->transaction_budget_id) { - $entry->budget_id = $transaction->transaction_journal_budget_id; - $entry->budget_name = $transaction->transaction_journal_budget_name; - } - - // category - $entry->category_id = (string)$transaction->transaction_category_id; - $entry->category_name = $transaction->transaction_category_name; - if (null === $transaction->transaction_category_id) { - $entry->category_id = $transaction->transaction_journal_category_id; - $entry->category_name = $transaction->transaction_journal_category_name; - } - - // budget - $entry->bill_id = (string)$transaction->bill_id; - $entry->bill_name = $transaction->bill_name; - - $entry->tags = $transaction->tags; - $entry->notes = $transaction->notes; - - return $entry; - } -} diff --git a/app/Export/ExpandedProcessor.php b/app/Export/ExpandedProcessor.php deleted file mode 100644 index e021133ab3..0000000000 --- a/app/Export/ExpandedProcessor.php +++ /dev/null @@ -1,375 +0,0 @@ -. - */ - -/** @noinspection PhpDynamicAsStaticMethodCallInspection */ - -declare(strict_types=1); - -namespace FireflyIII\Export; - -use DB; -use FireflyIII\Exceptions\FireflyException; -use FireflyIII\Export\Collector\AttachmentCollector; -use FireflyIII\Export\Collector\UploadCollector; -use FireflyIII\Export\Entry\Entry; -use FireflyIII\Export\Exporter\ExporterInterface; -use FireflyIII\Helpers\Collector\TransactionCollectorInterface; -use FireflyIII\Helpers\Filter\InternalTransferFilter; -use FireflyIII\Models\AccountMeta; -use FireflyIII\Models\ExportJob; -use FireflyIII\Models\Note; -use FireflyIII\Models\Transaction; -use FireflyIII\Models\TransactionJournal; -use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; -use Illuminate\Support\Collection; -use Illuminate\Support\Facades\Storage; -use Log; -use ZipArchive; - -/** - * Class ExpandedProcessor. - * - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) // its doing a lot. - * - * @codeCoverageIgnore - * @deprecated - */ -class ExpandedProcessor implements ProcessorInterface -{ - /** @var Collection All accounts */ - public $accounts; - /** @var string The export format */ - public $exportFormat; - /** @var bool Should include attachments */ - public $includeAttachments; - /** @var bool Should include old uploads */ - public $includeOldUploads; - /** @var ExportJob The export job itself */ - public $job; - /** @var array The settings */ - public $settings; - /** @var Collection The entries to export. */ - private $exportEntries; - /** @var Collection The files to export */ - private $files; - /** @var Collection The journals. */ - private $journals; - - /** - * Processor constructor. - */ - public function __construct() - { - $this->journals = new Collection; - $this->exportEntries = new Collection; - $this->files = new Collection; - } - - /** - * Collect all attachments - * - * @return bool - */ - public function collectAttachments(): bool - { - /** @var AttachmentCollector $attachmentCollector */ - $attachmentCollector = app(AttachmentCollector::class); - $attachmentCollector->setJob($this->job); - $attachmentCollector->setDates($this->settings['startDate'], $this->settings['endDate']); - $attachmentCollector->run(); - $this->files = $this->files->merge($attachmentCollector->getEntries()); - - return true; - } - - /** - * Collects all transaction journals. - * - * @return bool - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - */ - public function collectJournals(): bool - { - // use journal collector thing. - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setUser($this->job->user); - $collector->setAccounts($this->accounts)->setRange($this->settings['startDate'], $this->settings['endDate']) - ->withOpposingAccount()->withBudgetInformation()->withCategoryInformation() - ->removeFilter(InternalTransferFilter::class); - $transactions = $collector->getTransactions(); - // get some more meta data for each entry: - $ids = $transactions->pluck('journal_id')->toArray(); - $assetIds = $transactions->pluck('account_id')->toArray(); - $opposingIds = $transactions->pluck('opposing_account_id')->toArray(); - $notes = $this->getNotes($ids); - $tags = $this->getTags($ids); - /** @var array $ibans */ - $ibans = array_merge($this->getIbans($assetIds), $this->getIbans($opposingIds)); - $currencies = $this->getAccountCurrencies($ibans); - $transactions->each( - function (Transaction $transaction) use ($notes, $tags, $ibans, $currencies) { - $journalId = (int)$transaction->journal_id; - $accountId = (int)$transaction->account_id; - $opposingId = (int)$transaction->opposing_account_id; - $currencyId = (int)($ibans[$accountId]['currency_id'] ?? 0.0); - $opposingCurrencyId = (int)($ibans[$opposingId]['currency_id'] ?? 0.0); - $transaction->notes = $notes[$journalId] ?? ''; - $transaction->tags = implode(',', $tags[$journalId] ?? []); - $transaction->account_number = $ibans[$accountId]['accountNumber'] ?? ''; - $transaction->account_bic = $ibans[$accountId]['BIC'] ?? ''; - $transaction->account_currency_code = $currencies[$currencyId] ?? ''; - $transaction->opposing_account_number = $ibans[$opposingId]['accountNumber'] ?? ''; - $transaction->opposing_account_bic = $ibans[$opposingId]['BIC'] ?? ''; - $transaction->opposing_currency_code = $currencies[$opposingCurrencyId] ?? ''; - } - ); - - $this->journals = $transactions; - - return true; - } - - /** - * Get old oploads. - * - * @return bool - */ - public function collectOldUploads(): bool - { - /** @var UploadCollector $uploadCollector */ - $uploadCollector = app(UploadCollector::class); - $uploadCollector->setJob($this->job); - $uploadCollector->run(); - - $this->files = $this->files->merge($uploadCollector->getEntries()); - - return true; - } - - /** - * Convert journals to export objects. - * - * @return bool - */ - public function convertJournals(): bool - { - $this->journals->each( - function (Transaction $transaction) { - $this->exportEntries->push(Entry::fromTransaction($transaction)); - } - ); - Log::debug(sprintf('Count %d entries in exportEntries (convertJournals)', $this->exportEntries->count())); - - return true; - } - - /** - * Create a ZIP file locally (!) in storage_path('export'). - * - * @return bool - * - * @throws FireflyException - * @throws \Illuminate\Contracts\Filesystem\FileNotFoundException - */ - public function createZipFile(): bool - { - $zip = new ZipArchive; - $file = $this->job->key . '.zip'; - $localPath = storage_path('export') . '/' . $file; - - if (true !== $zip->open($localPath, ZipArchive::CREATE)) { - throw new FireflyException('Cannot store zip file.'); - } - // for each file in the collection, add it to the zip file. - $disk = Storage::disk('export'); - foreach ($this->getFiles() as $entry) { - // is part of this job? - $zipFileName = str_replace($this->job->key . '-', '', $entry); - $zip->addFromString($zipFileName, $disk->get($entry)); - } - - $zip->close(); - - // delete the files: - $this->deleteFiles(); - - return true; - } - - /** - * Export the journals. - * - * @return bool - */ - public function exportJournals(): bool - { - $exporterClass = config('firefly.export_formats.' . $this->exportFormat); - /** @var ExporterInterface $exporter */ - $exporter = app($exporterClass); - $exporter->setJob($this->job); - $exporter->setEntries($this->exportEntries); - $exporter->run(); - $this->files->push($exporter->getFileName()); - - return true; - } - - /** - * Get files. - * - * @return Collection - */ - public function getFiles(): Collection - { - return $this->files; - } - - /** - * Save export job settings to class. - * - * @param array $settings - */ - public function setSettings(array $settings): void - { - // save settings - $this->settings = $settings; - $this->accounts = $settings['accounts']; - $this->exportFormat = $settings['exportFormat']; - $this->includeAttachments = $settings['includeAttachments']; - $this->includeOldUploads = $settings['includeOldUploads']; - $this->job = $settings['job']; - } - - /** - * Delete files. - */ - private function deleteFiles(): void - { - $disk = Storage::disk('export'); - foreach ($this->getFiles() as $file) { - $disk->delete($file); - } - } - - /** - * Get currencies. - * - * @param array $array - * - * @return array - */ - private function getAccountCurrencies(array $array): array - { - /** @var CurrencyRepositoryInterface $repository */ - $repository = app(CurrencyRepositoryInterface::class); - $return = []; - $ids = []; - $repository->setUser($this->job->user); - foreach ($array as $value) { - $ids[] = (int)($value['currency_id'] ?? 0.0); - } - $ids = array_unique($ids); - $result = $repository->getByIds($ids); - - foreach ($result as $currency) { - $return[$currency->id] = $currency->code; - } - - return $return; - } - - /** - * Get all IBAN / SWIFT / account numbers. - * - * @param array $array - * - * @return array - */ - private function getIbans(array $array): array - { - $array = array_unique($array); - $return = []; - $set = AccountMeta::whereIn('account_id', $array) - ->leftJoin('accounts', 'accounts.id', 'account_meta.account_id') - ->where('accounts.user_id', $this->job->user_id) - ->whereIn('account_meta.name', ['accountNumber', 'BIC', 'currency_id']) - ->get(['account_meta.id', 'account_meta.account_id', 'account_meta.name', 'account_meta.data']); - /** @var AccountMeta $meta */ - foreach ($set as $meta) { - $id = (int)$meta->account_id; - $return[$id][$meta->name] = $meta->data; - } - - return $return; - } - - /** - * Returns, if present, for the given journal ID's the notes. - * - * @param array $array - * - * @return array - */ - private function getNotes(array $array): array - { - $array = array_unique($array); - $notes = Note::where('notes.noteable_type', TransactionJournal::class) - ->whereIn('notes.noteable_id', $array) - ->get(['notes.*']); - $return = []; - /** @var Note $note */ - foreach ($notes as $note) { - if ('' !== trim((string)$note->text)) { - $id = (int)$note->noteable_id; - $return[$id] = $note->text; - } - } - - return $return; - } - - /** - * Returns a comma joined list of all the users tags linked to these journals. - * - * @param array $array - * - * @return array - * @throws \Illuminate\Contracts\Encryption\DecryptException - */ - private function getTags(array $array): array - { - $set = DB::table('tag_transaction_journal') - ->whereIn('tag_transaction_journal.transaction_journal_id', $array) - ->leftJoin('tags', 'tag_transaction_journal.tag_id', '=', 'tags.id') - ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'tag_transaction_journal.transaction_journal_id') - ->where('transaction_journals.user_id', $this->job->user_id) - ->get(['tag_transaction_journal.transaction_journal_id', 'tags.tag']); - $result = []; - foreach ($set as $entry) { - $id = (int)$entry->transaction_journal_id; - $result[$id] = $result[$id] ?? []; - $result[$id][] = $entry->tag; - } - - return $result; - } -} diff --git a/app/Export/Exporter/BasicExporter.php b/app/Export/Exporter/BasicExporter.php deleted file mode 100644 index de7eaed474..0000000000 --- a/app/Export/Exporter/BasicExporter.php +++ /dev/null @@ -1,80 +0,0 @@ -. - */ - -declare(strict_types=1); - -namespace FireflyIII\Export\Exporter; - -use FireflyIII\Models\ExportJob; -use Illuminate\Support\Collection; - -/** - * Class BasicExporter. - * - * @codeCoverageIgnore - * @deprecated - */ -class BasicExporter -{ - /** @var ExportJob The export job */ - protected $job; - /** @var Collection The entries */ - private $entries; - - /** - * BasicExporter constructor. - */ - public function __construct() - { - $this->entries = new Collection; - } - - /** - * Get all entries. - * - * @return Collection - */ - public function getEntries(): Collection - { - return $this->entries; - } - - /** - * Set all entries. - * - * @param Collection $entries - */ - public function setEntries(Collection $entries): void - { - $this->entries = $entries; - } - - /** - * Set the job. - * - * @param ExportJob $job - */ - public function setJob(ExportJob $job): void - { - $this->job = $job; - } -} diff --git a/app/Export/Exporter/CsvExporter.php b/app/Export/Exporter/CsvExporter.php deleted file mode 100644 index 00d909d762..0000000000 --- a/app/Export/Exporter/CsvExporter.php +++ /dev/null @@ -1,90 +0,0 @@ -. - */ - -declare(strict_types=1); - -namespace FireflyIII\Export\Exporter; - -use FireflyIII\Export\Entry\Entry; -use Illuminate\Support\Facades\Storage; -use League\Csv\Writer; - -/** - * Class CsvExporter. - * - * @codeCoverageIgnore - * @deprecated - */ -class CsvExporter extends BasicExporter implements ExporterInterface -{ - /** @var string Filename */ - private $fileName; - - /** - * Get file name. - * - * @return string - */ - public function getFileName(): string - { - return $this->fileName; - } - - /** - * Run collector. - * - * @return bool - * - */ - public function run(): bool - { - // choose file name: - $this->fileName = $this->job->key . '-records.csv'; - - //we create the CSV into memory - $writer = Writer::createFromString(''); - $rows = []; - - // get field names for header row: - $first = $this->getEntries()->first(); - $headers = []; - if (null !== $first) { - $headers = array_keys(get_object_vars($first)); - } - - $rows[] = $headers; - - /** @var Entry $entry */ - foreach ($this->getEntries() as $entry) { - $line = []; - foreach ($headers as $header) { - $line[] = $entry->$header; - } - $rows[] = $line; - } - $writer->insertAll($rows); - $disk = Storage::disk('export'); - $disk->put($this->fileName, $writer->getContent()); - - return true; - } -} diff --git a/app/Export/Exporter/ExporterInterface.php b/app/Export/Exporter/ExporterInterface.php deleted file mode 100644 index 998510f65b..0000000000 --- a/app/Export/Exporter/ExporterInterface.php +++ /dev/null @@ -1,72 +0,0 @@ -. - */ - -declare(strict_types=1); - -namespace FireflyIII\Export\Exporter; - -use FireflyIII\Models\ExportJob; -use Illuminate\Support\Collection; - -/** - * Interface ExporterInterface. - * - * @codeCoverageIgnore - * @deprecated - */ -interface ExporterInterface -{ - /** - * Get entries. - * - * @return Collection - */ - public function getEntries(): Collection; - - /** - * Get file name. - * - * @return string - */ - public function getFileName(): string; - - /** - * Run exporter. - * - * @return bool - */ - public function run(): bool; - - /** - * Set entries. - * - * @param Collection $entries - */ - public function setEntries(Collection $entries); - - /** - * Set job. - * - * @param ExportJob $job - */ - public function setJob(ExportJob $job); -} diff --git a/app/Export/ProcessorInterface.php b/app/Export/ProcessorInterface.php deleted file mode 100644 index 330e69c2d2..0000000000 --- a/app/Export/ProcessorInterface.php +++ /dev/null @@ -1,97 +0,0 @@ -. - */ - -declare(strict_types=1); - -namespace FireflyIII\Export; - -use Illuminate\Support\Collection; - -/** - * Interface ProcessorInterface. - * - * @codeCoverageIgnore - * @deprecated - */ -interface ProcessorInterface -{ - /** - * Processor constructor. - */ - public function __construct(); - - /** - * Collect all attachments. - * - * @return bool - */ - public function collectAttachments(): bool; - - /** - * Collect all journals. - * - * @return bool - */ - public function collectJournals(): bool; - - /** - * Collect old uploads. - * - * @return bool - */ - public function collectOldUploads(): bool; - - /** - * Convert all journals. - * - * @return bool - */ - public function convertJournals(): bool; - - /** - * Create a zip file. - * - * @return bool - */ - public function createZipFile(): bool; - - /** - * Export journals. - * - * @return bool - */ - public function exportJournals(): bool; - - /** - * Get all files. - * - * @return Collection - */ - public function getFiles(): Collection; - - /** - * Set the settings. - * - * @param array $settings - */ - public function setSettings(array $settings); -} diff --git a/app/Factory/AccountFactory.php b/app/Factory/AccountFactory.php index ccb86beda8..0c35036f02 100644 --- a/app/Factory/AccountFactory.php +++ b/app/Factory/AccountFactory.php @@ -20,9 +20,6 @@ * along with Firefly III. If not, see . */ -/** @noinspection PhpDynamicAsStaticMethodCallInspection */ -/** @noinspection PhpUndefinedMethodInspection */ - declare(strict_types=1); namespace FireflyIII\Factory; @@ -30,7 +27,7 @@ namespace FireflyIII\Factory; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\Account; use FireflyIII\Models\AccountType; -use FireflyIII\Models\TransactionCurrency; +use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Services\Internal\Support\AccountServiceTrait; use FireflyIII\User; use Log; @@ -42,10 +39,15 @@ use Log; */ class AccountFactory { + use AccountServiceTrait; + + /** @var AccountRepositoryInterface */ + protected $accountRepository; /** @var User */ private $user; - use AccountServiceTrait; + /** @var array */ + private $canHaveVirtual; /** * AccountFactory constructor. @@ -55,8 +57,10 @@ class AccountFactory public function __construct() { if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } + $this->canHaveVirtual = [AccountType::ASSET, AccountType::DEBT, AccountType::LOAN, AccountType::MORTGAGE, AccountType::CREDITCARD]; + $this->accountRepository = app(AccountRepositoryInterface::class); } /** @@ -64,22 +68,21 @@ class AccountFactory * * @return Account * @throws FireflyException - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ public function create(array $data): Account { - $type = $this->getAccountType($data['account_type_id'], $data['accountType']); + $type = $this->getAccountType($data['account_type_id'], $data['account_type']); if (null === $type) { throw new FireflyException( - sprintf('AccountFactory::create() was unable to find account type #%d ("%s").', $data['account_type_id'], $data['accountType']) + sprintf('AccountFactory::create() was unable to find account type #%d ("%s").', $data['account_type_id'], $data['account_type']) ); } - $data['iban'] = $this->filterIban($data['iban']); + $data['iban'] = $this->filterIban($data['iban'] ?? null); // account may exist already: + Log::debug('Data array is as follows', $data); $return = $this->find($data['name'], $type->type); if (null === $return) { @@ -89,29 +92,17 @@ class AccountFactory 'user_id' => $this->user->id, 'account_type_id' => $type->id, 'name' => $data['name'], - 'virtual_balance' => $data['virtualBalance'] ?? '0', + 'virtual_balance' => $data['virtual_balance'] ?? '0', 'active' => true === $data['active'], 'iban' => $data['iban'], ]; - // find currency, or use default currency instead. - /** @var TransactionCurrencyFactory $factory */ - $factory = app(TransactionCurrencyFactory::class); - /** @var TransactionCurrency $currency */ - $currency = $factory->find((int)($data['currency_id'] ?? null), (string)($data['currency_code'] ?? null)); - - if (null === $currency) { - // use default currency: - $currency = app('amount')->getDefaultCurrencyByUser($this->user); - } - $currency->enabled = true; - $currency->save(); - + $currency = $this->getCurrency((int)($data['currency_id'] ?? null), (string)($data['currency_code'] ?? null)); unset($data['currency_code']); $data['currency_id'] = $currency->id; + // remove virtual balance when not an asset account or a liability - $canHaveVirtual = [AccountType::ASSET, AccountType::DEBT, AccountType::LOAN, AccountType::MORTGAGE, AccountType::CREDITCARD]; - if (!\in_array($type->type, $canHaveVirtual, true)) { + if (!in_array($type->type, $this->canHaveVirtual, true)) { $databaseData['virtual_balance'] = '0'; } @@ -123,12 +114,13 @@ class AccountFactory $return = Account::create($databaseData); $this->updateMetaData($return, $data); - if (\in_array($type->type, $canHaveVirtual, true)) { - if ($this->validIBData($data)) { - $this->updateIB($return, $data); + // if it can have a virtual balance, it can also have an opening balance. + if (in_array($type->type, $this->canHaveVirtual, true)) { + if ($this->validOBData($data)) { + $this->updateOBGroup($return, $data); } - if (!$this->validIBData($data)) { - $this->deleteIB($return); + if (!$this->validOBData($data)) { + $this->deleteOBGroup($return); } } $this->updateNote($return, $data['notes'] ?? ''); @@ -145,18 +137,9 @@ class AccountFactory */ public function find(string $accountName, string $accountType): ?Account { - $type = AccountType::whereType($accountType)->first(); - $accounts = $this->user->accounts()->where('account_type_id', $type->id)->get(['accounts.*']); - $return = null; - /** @var Account $object */ - foreach ($accounts as $object) { - if ($object->name === $accountName) { - $return = $object; - break; - } - } + $type = AccountType::whereType($accountType)->first(); - return $return; + return $this->user->accounts()->where('account_type_id', $type->id)->where('name', $accountName)->first(); } /** @@ -170,20 +153,11 @@ class AccountFactory public function findOrCreate(string $accountName, string $accountType): Account { Log::debug(sprintf('Searching for "%s" of type "%s"', $accountName, $accountType)); - $type = AccountType::whereType($accountType)->first(); - $accounts = $this->user->accounts()->where('account_type_id', $type->id)->get(['accounts.*']); - $return = null; + /** @var AccountType $type */ + $type = AccountType::whereType($accountType)->first(); + $return = $this->user->accounts->where('account_type_id', $type->id) + ->where('name', $accountName)->first(); - Log::debug(sprintf('Account type is #%d', $type->id)); - - /** @var Account $object */ - foreach ($accounts as $object) { - if ($object->name === $accountName) { - Log::debug(sprintf('Found account #%d "%s".', $object->id, $object->name)); - $return = $object; - break; - } - } if (null === $return) { Log::debug('Found nothing. Will create a new one.'); $return = $this->create( @@ -191,8 +165,8 @@ class AccountFactory 'user_id' => $this->user->id, 'name' => $accountName, 'account_type_id' => $type->id, - 'accountType' => null, - 'virtualBalance' => '0', + 'account_type' => null, + 'virtual_balance' => '0', 'iban' => null, 'active' => true, ] @@ -211,7 +185,7 @@ class AccountFactory } /** - * @param int|null $accountTypeId + * @param int|null $accountTypeId * @param null|string $accountType * * @return AccountType|null @@ -228,8 +202,8 @@ class AccountFactory Log::debug(sprintf('No account type found by ID, continue search for "%s".', $accountType)); /** @var array $types */ $types = config('firefly.accountTypeByIdentifier.' . $accountType) ?? []; - if (\count($types) > 0) { - Log::debug(sprintf('%d accounts in list from config', \count($types)), $types); + if (count($types) > 0) { + Log::debug(sprintf('%d accounts in list from config', count($types)), $types); $result = AccountType::whereIn('type', $types)->first(); } if (null === $result && null !== $accountType) { @@ -237,9 +211,17 @@ class AccountFactory $result = AccountType::whereType($accountType)->first(); } } + if (null === $result) { + Log::warning(sprintf('Found NO account type based on %d and "%s"', $accountTypeId, $accountType)); + } + if (null !== $result) { + Log::debug(sprintf('Found account type based on %d and "%s": "%s"', $accountTypeId, $accountType, $result->type)); + } + return $result; } + } diff --git a/app/Factory/AccountMetaFactory.php b/app/Factory/AccountMetaFactory.php index a6c156e299..d3afff2397 100644 --- a/app/Factory/AccountMetaFactory.php +++ b/app/Factory/AccountMetaFactory.php @@ -36,11 +36,12 @@ class AccountMetaFactory { /** * Constructor. + * @codeCoverageIgnore */ public function __construct() { if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } diff --git a/app/Factory/AttachmentFactory.php b/app/Factory/AttachmentFactory.php index 4ebefc9f94..42ab6d394e 100644 --- a/app/Factory/AttachmentFactory.php +++ b/app/Factory/AttachmentFactory.php @@ -41,11 +41,12 @@ class AttachmentFactory /** * Constructor. + * @codeCoverageIgnore */ public function __construct() { if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } @@ -58,13 +59,14 @@ class AttachmentFactory public function create(array $data): ?Attachment { // append if necessary. - $model = false === strpos('FireflyIII', $data['model']) ? 'FireflyIII\\Models\\' . $data['model'] : $data['model']; + $model = false === strpos($data['model'], 'FireflyIII') ? sprintf('FireflyIII\\Models\\%s', $data['model']) : $data['model']; + // get journal instead of transaction. if (Transaction::class === $model) { /** @var Transaction $transaction */ $transaction = $this->user->transactions()->find((int)$data['model_id']); if (null === $transaction) { - throw new FireflyException('Unexpectedly could not find transaction'); + throw new FireflyException('Unexpectedly could not find transaction'); // @codeCoverageIgnore } $data['model_id'] = $transaction->transaction_journal_id; $model = TransactionJournal::class; diff --git a/app/Factory/BillFactory.php b/app/Factory/BillFactory.php index e9a7363fd8..88d9ca269e 100644 --- a/app/Factory/BillFactory.php +++ b/app/Factory/BillFactory.php @@ -28,7 +28,6 @@ use FireflyIII\Models\Bill; use FireflyIII\Models\TransactionCurrency; use FireflyIII\Services\Internal\Support\BillServiceTrait; use FireflyIII\User; -use Illuminate\Support\Collection; use Log; /** @@ -43,11 +42,12 @@ class BillFactory /** * Constructor. + * @codeCoverageIgnore */ public function __construct() { if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } @@ -94,7 +94,7 @@ class BillFactory } /** - * @param int|null $billId + * @param int|null $billId * @param null|string $billName * * @return Bill|null @@ -126,20 +126,11 @@ class BillFactory */ public function findByName(string $name): ?Bill { - /** @var Collection $collection */ - $collection = $this->user->bills()->get(); - $return = null; - /** @var Bill $bill */ - foreach ($collection as $bill) { - Log::debug(sprintf('"%s" vs. "%s"', $bill->name, $name)); - if ($bill->name === $name) { - $return = $bill; - break; - } - } - Log::debug(sprintf('Bill::find("%s") by name returns null? %s', $name, var_export($return, true))); + $query = sprintf('%%%s%%', $name); + /** @var Bill $first */ + $first = $this->user->bills()->where('name', 'LIKE', $query)->first(); - return $return; + return $first; } /** diff --git a/app/Factory/BudgetFactory.php b/app/Factory/BudgetFactory.php index a4474f5877..3df3134889 100644 --- a/app/Factory/BudgetFactory.php +++ b/app/Factory/BudgetFactory.php @@ -39,11 +39,12 @@ class BudgetFactory /** * Constructor. + * @codeCoverageIgnore */ public function __construct() { if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } @@ -91,6 +92,7 @@ class BudgetFactory { /** @var Collection $collection */ $collection = $this->user->budgets()->get(); + // TODO no longer need to loop like this /** @var Budget $budget */ foreach ($collection as $budget) { if ($budget->name === $name) { diff --git a/app/Factory/CategoryFactory.php b/app/Factory/CategoryFactory.php index 5d9ce23274..8379c5a147 100644 --- a/app/Factory/CategoryFactory.php +++ b/app/Factory/CategoryFactory.php @@ -39,11 +39,12 @@ class CategoryFactory /** * Constructor. + * @codeCoverageIgnore */ public function __construct() { if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } @@ -57,6 +58,9 @@ class CategoryFactory $result = null; /** @var Collection $collection */ $collection = $this->user->categories()->get(); + + // TODO no longer need to loop like this + /** @var Category $category */ foreach ($collection as $category) { if ($category->name === $name) { diff --git a/app/Factory/PiggyBankEventFactory.php b/app/Factory/PiggyBankEventFactory.php index 0c6d19e86f..3dcb2a1e16 100644 --- a/app/Factory/PiggyBankEventFactory.php +++ b/app/Factory/PiggyBankEventFactory.php @@ -39,11 +39,12 @@ class PiggyBankEventFactory { /** * Constructor. + * @codeCoverageIgnore */ public function __construct() { if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } @@ -58,10 +59,11 @@ class PiggyBankEventFactory { Log::debug(sprintf('Now in PiggyBankEventCreate for a %s', $journal->transactionType->type)); if (null === $piggyBank) { + Log::debug('Piggy bank is null'); return null; } - if (!(TransactionType::TRANSFER === $journal->transactionType->type)) { + if (TransactionType::TRANSFER !== $journal->transactionType->type) { Log::info(sprintf('Will not connect %s #%d to a piggy bank.', $journal->transactionType->type, $journal->id)); return null; @@ -77,7 +79,7 @@ class PiggyBankEventFactory return null; } - + Log::debug('Found repetition'); $amount = $piggyRepos->getExactAmount($piggyBank, $repetition, $journal); if (0 === bccomp($amount, '0')) { Log::debug('Amount is zero, will not create event.'); diff --git a/app/Factory/PiggyBankFactory.php b/app/Factory/PiggyBankFactory.php index 424e1e6f6e..766c4e8d4e 100644 --- a/app/Factory/PiggyBankFactory.php +++ b/app/Factory/PiggyBankFactory.php @@ -38,11 +38,12 @@ class PiggyBankFactory /** * Constructor. + * @codeCoverageIgnore */ public function __construct() { if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } @@ -90,6 +91,9 @@ class PiggyBankFactory public function findByName(string $name): ?PiggyBank { $set = $this->user->piggyBanks()->get(); + + // TODO no longer need to loop like this + /** @var PiggyBank $piggy */ foreach ($set as $piggy) { if ($piggy->name === $name) { diff --git a/app/Factory/RecurrenceFactory.php b/app/Factory/RecurrenceFactory.php index 1a71192710..acfe1aa586 100644 --- a/app/Factory/RecurrenceFactory.php +++ b/app/Factory/RecurrenceFactory.php @@ -29,9 +29,9 @@ use Carbon\Carbon; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\Recurrence; use FireflyIII\Services\Internal\Support\RecurringTransactionTrait; -use FireflyIII\Services\Internal\Support\TransactionServiceTrait; use FireflyIII\Services\Internal\Support\TransactionTypeTrait; use FireflyIII\User; +use Illuminate\Support\MessageBag; use Log; /** @@ -42,31 +42,39 @@ class RecurrenceFactory /** @var User */ private $user; - use TransactionTypeTrait, TransactionServiceTrait, RecurringTransactionTrait; + /** @var MessageBag */ + private $errors; + + use TransactionTypeTrait, RecurringTransactionTrait; /** * Constructor. + * @codeCoverageIgnore */ public function __construct() { if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } + $this->errors = new MessageBag; } /** * @param array $data * * @return Recurrence + * @throws FireflyException */ - public function create(array $data): ?Recurrence + public function create(array $data): Recurrence { try { $type = $this->findTransactionType(ucfirst($data['recurrence']['type'])); } catch (FireflyException $e) { - Log::error($e->getMessage()); + $message = sprintf('Cannot make a recurring transaction of type "%s"', $data['recurrence']['type']); + Log::error($message); + Log::error($e->getTraceAsString()); - return null; + throw new FireflyException($message); } /** @var Carbon $firstDate */ $firstDate = $data['recurrence']['first_date']; @@ -90,7 +98,19 @@ class RecurrenceFactory $this->updateMetaData($recurrence, $data); $this->createRepetitions($recurrence, $data['repetitions'] ?? []); - $this->createTransactions($recurrence, $data['transactions'] ?? []); + try { + $this->createTransactions($recurrence, $data['transactions'] ?? []); + // @codeCoverageIgnoreStart + } catch (FireflyException $e) { + Log::error($e->getMessage()); + $recurrence->forceDelete(); + $message = sprintf('Could not create recurring transaction: %s', $e->getMessage()); + $this->errors->add('store', $message); + throw new FireflyException($message); + + } + + // @codeCoverageIgnoreEnd return $recurrence; } diff --git a/app/Factory/TagFactory.php b/app/Factory/TagFactory.php index e6060bf656..ef9caf7cbf 100644 --- a/app/Factory/TagFactory.php +++ b/app/Factory/TagFactory.php @@ -41,11 +41,12 @@ class TagFactory /** * Constructor. + * @codeCoverageIgnore */ public function __construct() { if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } @@ -69,7 +70,6 @@ class TagFactory 'longitude' => $longitude, 'zoomLevel' => $zoomLevel, ]; - return Tag::create($array); } @@ -81,17 +81,12 @@ class TagFactory public function findOrCreate(string $tag): ?Tag { $tag = trim($tag); - if (null === $this->tags) { - $this->tags = $this->user->tags()->get(); - } - /** @var Tag $object */ - foreach ($this->tags as $object) { - if (strtolower($object->tag) === strtolower($tag)) { - return $object; - } + /** @var Tag $dbTag */ + $dbTag = $this->user->tags()->where('tag', $tag)->first(); + if (null !== $dbTag) { + return $dbTag; } - $newTag = $this->create( [ 'tag' => $tag, @@ -102,7 +97,6 @@ class TagFactory 'zoom_level' => null, ] ); - $this->tags->push($newTag); return $newTag; } diff --git a/app/Factory/TransactionCurrencyFactory.php b/app/Factory/TransactionCurrencyFactory.php index c29658311a..3d831791a5 100644 --- a/app/Factory/TransactionCurrencyFactory.php +++ b/app/Factory/TransactionCurrencyFactory.php @@ -44,7 +44,7 @@ class TransactionCurrencyFactory public function __construct() { if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } diff --git a/app/Factory/TransactionFactory.php b/app/Factory/TransactionFactory.php index 9dcd6d8c8e..3cf47f1eed 100644 --- a/app/Factory/TransactionFactory.php +++ b/app/Factory/TransactionFactory.php @@ -25,14 +25,12 @@ declare(strict_types=1); namespace FireflyIII\Factory; -use FireflyIII\Exceptions\FireflyException; -use FireflyIII\Models\AccountType; +use FireflyIII\Models\Account; use FireflyIII\Models\Transaction; +use FireflyIII\Models\TransactionCurrency; use FireflyIII\Models\TransactionJournal; -use FireflyIII\Models\TransactionType; -use FireflyIII\Services\Internal\Support\TransactionServiceTrait; use FireflyIII\User; -use Illuminate\Support\Collection; +use Illuminate\Database\QueryException; use Log; /** @@ -40,170 +38,112 @@ use Log; */ class TransactionFactory { + /** @var TransactionJournal */ + private $journal; + /** @var Account */ + private $account; + /** @var TransactionCurrency */ + private $currency; + /** @var TransactionCurrency */ + private $foreignCurrency; /** @var User */ private $user; - - use TransactionServiceTrait; + /** @var bool */ + private $reconciled; /** * Constructor. + * @codeCoverageIgnore */ public function __construct() { if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } + $this->reconciled = false; } /** - * @param array $data - * - * @return Transaction - * @throws FireflyException - * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @param bool $reconciled + * @codeCoverageIgnore */ - public function create(array $data): ?Transaction + public function setReconciled(bool $reconciled): void { - Log::debug('Start of TransactionFactory::create()'); - $currencyId = $data['currency_id'] ?? null; - $currencyId = isset($data['currency']) ? $data['currency']->id : $currencyId; - if ('' === $data['amount']) { - Log::error('Empty string in data.', $data); - throw new FireflyException('Amount is an empty string, which Firefly III cannot handle. Apologies.'); - } - if (null === $currencyId) { - $currency = app('amount')->getDefaultCurrencyByUser($data['account']->user); - $currencyId = $currency->id; - } - $data['foreign_amount'] = '' === (string)$data['foreign_amount'] ? null : $data['foreign_amount']; - Log::debug(sprintf('Create transaction for account #%d ("%s") with amount %s', $data['account']->id, $data['account']->name, $data['amount'])); - - return Transaction::create( - [ - 'reconciled' => $data['reconciled'], - 'account_id' => $data['account']->id, - 'transaction_journal_id' => $data['transaction_journal']->id, - 'description' => $data['description'], - 'transaction_currency_id' => $currencyId, - 'amount' => $data['amount'], - 'foreign_amount' => $data['foreign_amount'], - 'foreign_currency_id' => null, - 'identifier' => $data['identifier'], - ] - ); + $this->reconciled = $reconciled; } /** - * Create a pair of transactions based on the data given in the array. + * @param Account $account + * @codeCoverageIgnore + */ + public function setAccount(Account $account): void + { + $this->account = $account; + } + + /** + * @param TransactionCurrency $currency + * @codeCoverageIgnore + */ + public function setCurrency(TransactionCurrency $currency): void + { + $this->currency = $currency; + } + + /** + * @param TransactionCurrency $foreignCurrency |null + * @codeCoverageIgnore + */ + public function setForeignCurrency(?TransactionCurrency $foreignCurrency): void + { + $this->foreignCurrency = $foreignCurrency; + } + + /** + * Create transaction with negative amount (for source accounts). * + * @param string $amount + * @param string|null $foreignAmount + * @return Transaction|null + */ + public function createNegative(string $amount, ?string $foreignAmount): ?Transaction + { + if (null !== $foreignAmount) { + $foreignAmount = app('steam')->negative($foreignAmount); + } + + return $this->create(app('steam')->negative($amount), $foreignAmount); + } + + /** + * Create transaction with positive amount (for destination accounts). + * + * @param string $amount + * @param string|null $foreignAmount + * @return Transaction|null + */ + public function createPositive(string $amount, ?string $foreignAmount): ?Transaction + { + if (null !== $foreignAmount) { + $foreignAmount = app('steam')->positive($foreignAmount); + } + + return $this->create(app('steam')->positive($amount), $foreignAmount); + } + + + /** * @param TransactionJournal $journal - * @param array $data - * - * @return Collection - * @throws FireflyException - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * @SuppressWarnings(PHPMD.NPathComplexity) - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + * @codeCoverageIgnore */ - public function createPair(TransactionJournal $journal, array $data): Collection + public function setJournal(TransactionJournal $journal): void { - Log::debug('Start of TransactionFactory::createPair()', $data); - // all this data is the same for both transactions: - Log::debug('Searching for currency info.'); - $defaultCurrency = app('amount')->getDefaultCurrencyByUser($this->user); - $currency = $this->findCurrency($data['currency_id'], $data['currency_code']); - $currency = $currency ?? $defaultCurrency; - - // enable currency: - if (false === $currency->enabled) { - $currency->enabled = true; - $currency->save(); - } - - - // type of source account and destination account depends on journal type: - $sourceType = $this->accountType($journal, 'source'); - $destinationType = $this->accountType($journal, 'destination'); - - Log::debug(sprintf('Expect source account to be of type "%s"', $sourceType)); - Log::debug(sprintf('Expect source destination to be of type "%s"', $destinationType)); - - // find source and destination account: - $sourceAccount = $this->findAccount($sourceType, (int)$data['source_id'], $data['source_name']); - $destinationAccount = $this->findAccount($destinationType, (int)$data['destination_id'], $data['destination_name']); - - if (null === $sourceAccount || null === $destinationAccount) { - $debugData = $data; - $debugData['source_type'] = $sourceType; - $debugData['dest_type'] = $destinationType; - Log::error('Info about source/dest:', $debugData); - throw new FireflyException('Could not determine source or destination account.'); - } - - Log::debug(sprintf('Source type is "%s", destination type is "%s"', $sourceAccount->accountType->type, $destinationAccount->accountType->type)); - - // based on the source type, destination type and transaction type, the system can start throwing FireflyExceptions. - $this->validateTransaction($sourceAccount->accountType->type, $destinationAccount->accountType->type, $journal->transactionType->type); - $source = $this->create( - [ - 'description' => $data['description'], - 'amount' => app('steam')->negative((string)$data['amount']), - 'foreign_amount' => null, - 'currency' => $currency, - 'account' => $sourceAccount, - 'transaction_journal' => $journal, - 'reconciled' => $data['reconciled'], - 'identifier' => $data['identifier'], - ] - ); - $dest = $this->create( - [ - 'description' => $data['description'], - 'amount' => app('steam')->positive((string)$data['amount']), - 'foreign_amount' => null, - 'currency' => $currency, - 'account' => $destinationAccount, - 'transaction_journal' => $journal, - 'reconciled' => $data['reconciled'], - 'identifier' => $data['identifier'], - ] - ); - if (null === $source || null === $dest) { - throw new FireflyException('Could not create transactions.'); // @codeCoverageIgnore - } - - // set foreign currency - Log::debug('Trying to find foreign currency information.'); - $foreign = $this->findCurrency($data['foreign_currency_id'], $data['foreign_currency_code']); - $this->setForeignCurrency($source, $foreign); - $this->setForeignCurrency($dest, $foreign); - - // set foreign amount: - if (null !== $data['foreign_amount']) { - $this->setForeignAmount($source, app('steam')->negative((string)$data['foreign_amount'])); - $this->setForeignAmount($dest, app('steam')->positive((string)$data['foreign_amount'])); - } - - // set budget: - if ($journal->transactionType->type !== TransactionType::WITHDRAWAL) { - $data['budget_id'] = null; - $data['budget_name'] = null; - } - - $budget = $this->findBudget($data['budget_id'], $data['budget_name']); - $this->setBudget($source, $budget); - $this->setBudget($dest, $budget); - - // set category - $category = $this->findCategory($data['category_id'], $data['category_name']); - $this->setCategory($source, $category); - $this->setCategory($dest, $category); - - return new Collection([$source, $dest]); + $this->journal = $journal; } /** * @param User $user + * @codeCoverageIgnore */ public function setUser(User $user): void { @@ -211,29 +151,48 @@ class TransactionFactory } /** - * @param string $sourceType - * @param string $destinationType - * @param string $transactionType - * - * @throws FireflyException + * @param string $amount + * @param string|null $foreignAmount + * @return Transaction|null */ - private function validateTransaction(string $sourceType, string $destinationType, string $transactionType): void + private function create(string $amount, ?string $foreignAmount): ?Transaction { - // throw big fat error when source type === dest type and it's not a transfer or reconciliation. - if ($sourceType === $destinationType && $transactionType !== TransactionType::TRANSFER) { - throw new FireflyException(sprintf('Source and destination account cannot be both of the type "%s"', $destinationType)); + $result = null; + $data = [ + 'reconciled' => $this->reconciled, + 'account_id' => $this->account->id, + 'transaction_journal_id' => $this->journal->id, + 'description' => null, + 'transaction_currency_id' => $this->currency->id, + 'amount' => $amount, + 'foreign_amount' => null, + 'foreign_currency_id' => null, + 'identifier' => 0, + ]; + try { + $result = Transaction::create($data); + // @codeCoverageIgnoreStart + } catch (QueryException $e) { + Log::error(sprintf('Could not create transaction: %s', $e->getMessage()), $data); } - // source must be in this list AND dest must be in this list: - $list = [AccountType::DEFAULT, AccountType::ASSET, AccountType::CREDITCARD, AccountType::CASH, AccountType::DEBT, AccountType::MORTGAGE, - AccountType::LOAN, AccountType::MORTGAGE]; - if ( - !\in_array($sourceType, $list, true) - && !\in_array($destinationType, $list, true)) { - throw new FireflyException(sprintf('At least one of the accounts must be an asset account (%s, %s).', $sourceType, $destinationType)); - } - // either of these must be asset or default account. + // @codeCoverageIgnoreEnd + if (null !== $result) { + Log::debug( + sprintf( + 'Created transaction #%d (%s %s, account %s), part of journal #%d', $result->id, $this->currency->code, $amount, $this->account->name, + $this->journal->id + ) + ); + // do foreign currency thing: add foreign currency info to $one and $two if necessary. + if (null !== $this->foreignCurrency && null !== $foreignAmount && $this->foreignCurrency->id !== $this->currency->id) { + $result->foreign_currency_id = $this->foreignCurrency->id; + $result->foreign_amount = $foreignAmount; + + } + $result->save(); + } + + return $result; } - - } diff --git a/app/Factory/TransactionGroupFactory.php b/app/Factory/TransactionGroupFactory.php new file mode 100644 index 0000000000..5a6135c01e --- /dev/null +++ b/app/Factory/TransactionGroupFactory.php @@ -0,0 +1,84 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Factory; + +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Models\TransactionGroup; +use FireflyIII\Models\TransactionJournal; +use FireflyIII\User; + +/** + * Class TransactionGroupFactory + * @codeCoverageIgnore + */ +class TransactionGroupFactory +{ + /** @var TransactionJournalFactory */ + private $journalFactory; + /** @var User The user */ + private $user; + + /** + * TransactionGroupFactory constructor. + */ + public function __construct() + { + $this->journalFactory = app(TransactionJournalFactory::class); + } + + /** + * Store a new transaction journal. + * + * @param array $data + * + * @return TransactionGroup + */ + public function create(array $data): TransactionGroup + { + $this->journalFactory->setUser($this->user); + $collection = $this->journalFactory->create($data); + $title = $data['group_title'] ?? null; + $title = '' === $title ? null : $title; + /** @var TransactionJournal $first */ + $first = $collection->first(); + $group = new TransactionGroup; + $group->user()->associate($first->user); + $group->title = $title; + $group->save(); + + $group->transactionJournals()->saveMany($collection); + + return $group; + } + + /** + * Set the user. + * + * @param User $user + */ + public function setUser(User $user): void + { + $this->user = $user; + } +} \ No newline at end of file diff --git a/app/Factory/TransactionJournalFactory.php b/app/Factory/TransactionJournalFactory.php index 0e6dca0a05..d185e4c975 100644 --- a/app/Factory/TransactionJournalFactory.php +++ b/app/Factory/TransactionJournalFactory.php @@ -25,11 +25,24 @@ declare(strict_types=1); namespace FireflyIII\Factory; use Carbon\Carbon; +use Exception; use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Models\Account; +use FireflyIII\Models\TransactionCurrency; use FireflyIII\Models\TransactionJournal; +use FireflyIII\Models\TransactionType; +use FireflyIII\Repositories\Account\AccountRepositoryInterface; +use FireflyIII\Repositories\Bill\BillRepositoryInterface; +use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; +use FireflyIII\Repositories\Category\CategoryRepositoryInterface; +use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; +use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface; +use FireflyIII\Repositories\TransactionType\TransactionTypeRepositoryInterface; use FireflyIII\Services\Internal\Support\JournalServiceTrait; -use FireflyIII\Services\Internal\Support\TransactionTypeTrait; +use FireflyIII\Support\NullArrayObject; use FireflyIII\User; +use FireflyIII\Validation\AccountValidator; +use Illuminate\Support\Collection; use Log; /** @@ -37,103 +50,102 @@ use Log; */ class TransactionJournalFactory { + use JournalServiceTrait; + + /** @var AccountValidator */ + private $accountValidator; + /** @var AccountRepositoryInterface */ + private $accountRepository; + /** @var BillRepositoryInterface */ + private $billRepository; + /** @var CurrencyRepositoryInterface */ + private $currencyRepository; + /** @var array */ + private $fields; + /** @var PiggyBankEventFactory */ + private $piggyEventFactory; + /** @var PiggyBankRepositoryInterface */ + private $piggyRepository; + /** @var TransactionFactory */ + private $transactionFactory; + /** @var TransactionTypeRepositoryInterface */ + private $typeRepository; /** @var User The user */ private $user; - use JournalServiceTrait, TransactionTypeTrait; - /** * Constructor. + * + * @throws Exception + * @codeCoverageIgnore */ public function __construct() { + $this->fields = [ + // sepa + 'sepa_cc', 'sepa_ct_op', 'sepa_ct_id', + 'sepa_db', 'sepa_country', 'sepa_ep', + 'sepa_ci', 'sepa_batch_id', + + // dates + 'interest_date', 'book_date', 'process_date', + 'due_date', 'payment_date', 'invoice_date', + + // others + 'recurrence_id', 'internal_reference', 'bunq_payment_id', + 'import_hash', 'import_hash_v2', 'external_id', 'original_source']; + + if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } + + $this->currencyRepository = app(CurrencyRepositoryInterface::class); + $this->typeRepository = app(TransactionTypeRepositoryInterface::class); + $this->transactionFactory = app(TransactionFactory::class); + $this->billRepository = app(BillRepositoryInterface::class); + $this->budgetRepository = app(BudgetRepositoryInterface::class); + $this->categoryRepository = app(CategoryRepositoryInterface::class); + $this->piggyRepository = app(PiggyBankRepositoryInterface::class); + $this->piggyEventFactory = app(PiggyBankEventFactory::class); + $this->tagFactory = app(TagFactory::class); + $this->accountValidator = app(AccountValidator::class); + $this->accountRepository = app(AccountRepositoryInterface::class); } /** - * Store a new transaction journal. + * Store a new (set of) transaction journals. * * @param array $data * - * @return TransactionJournal - * @throws FireflyException - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + * @return Collection */ - public function create(array $data): TransactionJournal + public function create(array $data): Collection { + // convert to special object. + $data = new NullArrayObject($data); + Log::debug('Start of TransactionJournalFactory::create()'); - // store basic journal first. - $type = $this->findTransactionType($data['type']); - $defaultCurrency = app('amount')->getDefaultCurrencyByUser($this->user); - Log::debug(sprintf('Going to store a %s', $type->type)); - $description = app('steam')->cleanString($data['description']); - $description = str_replace(["\n", "\t", "\r"], "\x20", $description); - /** @var Carbon $carbon */ - $carbon = $data['date']; - $carbon->setTimezone(config('app.timezone')); + $collection = new Collection; + $transactions = $data['transactions'] ?? []; + if (0 === count($transactions)) { + Log::error('There are no transactions in the array, the TransactionJournalFactory cannot continue.'); - $journal = TransactionJournal::create( - [ - 'user_id' => $data['user'], - 'transaction_type_id' => $type->id, - 'bill_id' => null, - 'transaction_currency_id' => $defaultCurrency->id, - 'description' => $description, - 'date' => $carbon->format('Y-m-d H:i:s'), - 'order' => 0, - 'tag_count' => 0, - 'completed' => 0, - ] - ); - - if (isset($data['transactions'][0]['amount']) && '' === $data['transactions'][0]['amount']) { - Log::error('Empty amount in data', $data); + return new Collection; } - // store basic transactions: - /** @var TransactionFactory $factory */ - $factory = app(TransactionFactory::class); - $factory->setUser($this->user); - $totalAmount = '0'; - Log::debug(sprintf('Found %d transactions in array.', \count($data['transactions']))); - /** @var array $trData */ - foreach ($data['transactions'] as $index => $trData) { - Log::debug(sprintf('Now storing transaction %d of %d', $index + 1, \count($data['transactions']))); - $factory->createPair($journal, $trData); - $totalAmount = bcadd($totalAmount, (string)($trData['amount'] ?? '0')); + /** @var array $row */ + foreach ($transactions as $index => $row) { + Log::debug(sprintf('Now creating journal %d/%d', $index + 1, count($transactions))); + + Log::debug('Going to call createJournal', $row); + $journal = $this->createJournal(new NullArrayObject($row)); + if (null !== $journal) { + $collection->push($journal); + } } - $journal->completed = true; - $journal->save(); - // link bill: - $this->connectBill($journal, $data); - - // link piggy bank (if transfer) - $this->connectPiggyBank($journal, $data); - - // link tags: - $this->connectTags($journal, $data); - - // store note: - $this->storeNote($journal, (string)$data['notes']); - - // store date meta fields (if present): - $fields = ['sepa-cc', 'sepa-ct-op', 'sepa-ct-id', 'sepa-db', 'sepa-country', 'sepa-ep', 'sepa-ci', 'interest_date', 'book_date', 'process_date', - 'due_date', 'recurrence_id', 'payment_date', 'invoice_date', 'internal_reference', 'bunq_payment_id', 'importHash', 'importHashV2', - 'external_id', 'sepa-batch-id', 'original-source']; - - foreach ($fields as $field) { - $this->storeMeta($journal, $data, $field); - } - Log::debug('End of TransactionJournalFactory::create()'); - - // invalidate cache. - app('preferences')->mark(); - - return $journal; + return $collection; } /** @@ -144,26 +156,310 @@ class TransactionJournalFactory public function setUser(User $user): void { $this->user = $user; + $this->currencyRepository->setUser($this->user); + $this->tagFactory->setUser($user); + $this->transactionFactory->setUser($this->user); + $this->billRepository->setUser($this->user); + $this->budgetRepository->setUser($this->user); + $this->categoryRepository->setUser($this->user); + $this->piggyRepository->setUser($this->user); + $this->accountRepository->setUser($this->user); + } + + /** + * @param TransactionJournal $journal + * @param NullArrayObject $data + * @param string $field + */ + protected function storeMeta(TransactionJournal $journal, NullArrayObject $data, string $field): void + { + $set = [ + 'journal' => $journal, + 'name' => $field, + 'data' => (string)($data[$field] ?? ''), + ]; + + Log::debug(sprintf('Going to store meta-field "%s", with value "%s".', $set['name'], $set['data'])); + + /** @var TransactionJournalMetaFactory $factory */ + $factory = app(TransactionJournalMetaFactory::class); + $factory->updateOrCreate($set); } /** * Link a piggy bank to this journal. * * @param TransactionJournal $journal - * @param array $data + * @param NullArrayObject $data */ - protected function connectPiggyBank(TransactionJournal $journal, array $data): void + private function storePiggyEvent(TransactionJournal $journal, NullArrayObject $data): void { - /** @var PiggyBankFactory $factory */ - $factory = app(PiggyBankFactory::class); - $factory->setUser($this->user); + Log::debug('Will now store piggy event.'); + if (!$journal->isTransfer()) { + Log::debug('Journal is not a transfer, do nothing.'); + + return; + } + + $piggyBank = $this->piggyRepository->findPiggyBank((int)$data['piggy_bank_id'], $data['piggy_bank_name']); - $piggyBank = $factory->find($data['piggy_bank_id'], $data['piggy_bank_name']); if (null !== $piggyBank) { - /** @var PiggyBankEventFactory $factory */ - $factory = app(PiggyBankEventFactory::class); - $factory->create($journal, $piggyBank); + $this->piggyEventFactory->create($journal, $piggyBank); + Log::debug('Create piggy event.'); + + return; + } + Log::debug('Create no piggy event'); + } + + /** + * @param NullArrayObject $row + * + * @return TransactionJournal|null + */ + private function createJournal(NullArrayObject $row): ?TransactionJournal + { + $row['import_hash_v2'] = $this->hashArray($row); + + /** Some basic fields */ + $type = $this->typeRepository->findTransactionType(null, $row['type']); + $carbon = $row['date'] ?? new Carbon; + $order = $row['order'] ?? 0; + $currency = $this->currencyRepository->findCurrency((int)$row['currency_id'], $row['currency_code']); + $foreignCurrency = $this->currencyRepository->findCurrencyNull($row['foreign_currency_id'], $row['foreign_currency_code']); + $bill = $this->billRepository->findBill((int)$row['bill_id'], $row['bill_name']); + $billId = TransactionType::WITHDRAWAL === $type->type && null !== $bill ? $bill->id : null; + $description = app('steam')->cleanString((string)$row['description']); + + /** Manipulate basic fields */ + $carbon->setTimezone(config('app.timezone')); + + /** Get source + destination account */ + Log::debug(sprintf('Source info: ID #%d, name "%s"', $row['source_id'], $row['source_name'])); + Log::debug(sprintf('Destination info: ID #%d, name "%s"', $row['destination_id'], $row['destination_name'])); + + + try { + // validate source and destination using a new Validator. + $this->validateAccounts($row); + /** create or get source and destination accounts */ + $sourceAccount = $this->getAccount($type->type, 'source', (int)$row['source_id'], $row['source_name']); + $destinationAccount = $this->getAccount($type->type, 'destination', (int)$row['destination_id'], $row['destination_name']); + // @codeCoverageIgnoreStart + } catch (FireflyException $e) { + Log::error($e->getMessage()); + + return null; + } + // @codeCoverageIgnoreEnd + + // TODO After 4.8.0 better handling below: + + /** double check currencies. */ + $sourceCurrency = $currency; + $destCurrency = $currency; + $sourceForeignCurrency = $foreignCurrency; + $destForeignCurrency = $foreignCurrency; + + if ('Withdrawal' === $type->type) { + // make sure currency is correct. + $currency = $this->getCurrency($currency, $sourceAccount); + // make sure foreign currency != currency. + if (null !== $foreignCurrency && $foreignCurrency->id === $currency->id) { + $foreignCurrency = null; + } + $sourceCurrency = $currency; + $destCurrency = $currency; + $sourceForeignCurrency = $foreignCurrency; + $destForeignCurrency = $foreignCurrency; + } + if ('Deposit' === $type->type) { + // make sure currency is correct. + $currency = $this->getCurrency($currency, $destinationAccount); + // make sure foreign currency != currency. + if (null !== $foreignCurrency && $foreignCurrency->id === $currency->id) { + $foreignCurrency = null; + } + + $sourceCurrency = $currency; + $destCurrency = $currency; + $sourceForeignCurrency = $foreignCurrency; + $destForeignCurrency = $foreignCurrency; + } + + if (TransactionType::TRANSFER === $type->type) { + // get currencies + $currency = $this->getCurrency($currency, $sourceAccount); + $foreignCurrency = $this->getCurrency($foreignCurrency, $destinationAccount); + + $sourceCurrency = $currency; + $destCurrency = $foreignCurrency; + $sourceForeignCurrency = $foreignCurrency; + $destForeignCurrency = $currency; + } + + // if transfer, switch accounts: + if (TransactionType::TRANSFER === $type->type) { + [$sourceAccount, $destinationAccount] = [$destinationAccount, $sourceAccount]; + } + + /** Create a basic journal. */ + $journal = TransactionJournal::create( + [ + 'user_id' => $this->user->id, + 'transaction_type_id' => $type->id, + 'bill_id' => $billId, + 'transaction_currency_id' => $currency->id, + 'description' => '' === $description ? '(empty description)' : $description, + 'date' => $carbon->format('Y-m-d H:i:s'), + 'order' => $order, + 'tag_count' => 0, + 'completed' => 0, + ] + ); + Log::debug(sprintf('Created new journal #%d: "%s"', $journal->id, $journal->description)); + + /** Create two transactions. */ + /** @var TransactionFactory $transactionFactory */ + $transactionFactory = app(TransactionFactory::class); + $transactionFactory->setUser($this->user); + $transactionFactory->setJournal($journal); + $transactionFactory->setAccount($sourceAccount); + $transactionFactory->setCurrency($sourceCurrency); + $transactionFactory->setForeignCurrency($sourceForeignCurrency); + $transactionFactory->setReconciled($row['reconciled'] ?? false); + $transactionFactory->createNegative($row['amount'], $row['foreign_amount']); + + // and the destination one: + /** @var TransactionFactory $transactionFactory */ + $transactionFactory = app(TransactionFactory::class); + $transactionFactory->setUser($this->user); + $transactionFactory->setJournal($journal); + $transactionFactory->setAccount($destinationAccount); + $transactionFactory->setCurrency($destCurrency); + $transactionFactory->setForeignCurrency($destForeignCurrency); + $transactionFactory->setReconciled($row['reconciled'] ?? false); + $transactionFactory->createPositive($row['amount'], $row['foreign_amount']); + + // verify that journal has two transactions. Otherwise, delete and cancel. + // TODO this can't be faked so it can't be tested. +// $count = $journal->transactions()->count(); +// if (2 !== $count) { +// // @codeCoverageIgnoreStart +// Log::error(sprintf('The journal unexpectedly has %d transaction(s). This is not OK. Cancel operation.', $count)); +// try { +// $journal->delete(); +// } catch (Exception $e) { +// Log::debug(sprintf('Dont care: %s.', $e->getMessage())); +// } +// +// return null; +// // @codeCoverageIgnoreEnd +// } + $journal->completed = true; + $journal->save(); + + /** Link all other data to the journal. */ + + /** Link budget */ + $this->storeBudget($journal, $row); + + /** Link category */ + $this->storeCategory($journal, $row); + + /** Set notes */ + $this->storeNotes($journal, $row['notes']); + + /** Set piggy bank */ + $this->storePiggyEvent($journal, $row); + + /** Set tags */ + $this->storeTags($journal, $row['tags']); + + /** set all meta fields */ + $this->storeMetaFields($journal, $row); + + return $journal; + } + + /** + * @param NullArrayObject $row + * + * @return string + */ + private function hashArray(NullArrayObject $row): string + { + $dataRow = $row->getArrayCopy(); + + unset($dataRow['import_hash_v2'], $dataRow['original_source']); + $json = json_encode($dataRow); + if (false === $json) { + // @codeCoverageIgnoreStart + $json = json_encode((string)microtime()); + Log::error(sprintf('Could not hash the original row! %s', json_last_error_msg()), $dataRow); + // @codeCoverageIgnoreEnd + } + $hash = hash('sha256', $json); + Log::debug(sprintf('The hash is: %s', $hash)); + + return $hash; + } + + /** + * @param TransactionJournal $journal + * @param NullArrayObject $transaction + */ + private function storeMetaFields(TransactionJournal $journal, NullArrayObject $transaction): void + { + foreach ($this->fields as $field) { + $this->storeMeta($journal, $transaction, $field); } } + + /** + * @param NullArrayObject $data + * @throws FireflyException + */ + private function validateAccounts(NullArrayObject $data): void + { + $transactionType = $data['type'] ?? 'invalid'; + $this->accountValidator->setUser($this->user); + $this->accountValidator->setTransactionType($transactionType); + + // validate source account. + $sourceId = isset($data['source_id']) ? (int)$data['source_id'] : null; + $sourceName = $data['source_name'] ?? null; + $validSource = $this->accountValidator->validateSource($sourceId, $sourceName); + + // do something with result: + if (false === $validSource) { + throw new FireflyException(sprintf('Source: %s', $this->accountValidator->sourceError)); // @codeCoverageIgnore + } + Log::debug('Source seems valid.'); + // validate destination account + $destinationId = isset($data['destination_id']) ? (int)$data['destination_id'] : null; + $destinationName = $data['destination_name'] ?? null; + $validDestination = $this->accountValidator->validateDestination($destinationId, $destinationName); + // do something with result: + if (false === $validDestination) { + throw new FireflyException(sprintf('Destination: %s', $this->accountValidator->destError)); // @codeCoverageIgnore + } + } + + /** + * @param TransactionCurrency|null $currency + * @param Account $account + * @return TransactionCurrency + */ + private function getCurrency(?TransactionCurrency $currency, Account $account): TransactionCurrency + { + $preference = $this->accountRepository->getAccountCurrency($account); + if (null === $preference && null === $currency) { + // return user's default: + return app('amount')->getDefaultCurrencyByUser($this->user); + } + + return $preference ?? $currency; + } } diff --git a/app/Factory/TransactionJournalMetaFactory.php b/app/Factory/TransactionJournalMetaFactory.php index 30948ef5fa..470468349f 100644 --- a/app/Factory/TransactionJournalMetaFactory.php +++ b/app/Factory/TransactionJournalMetaFactory.php @@ -36,11 +36,12 @@ class TransactionJournalMetaFactory { /** * Constructor. + * @codeCoverageIgnore */ public function __construct() { if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } @@ -53,10 +54,12 @@ class TransactionJournalMetaFactory */ public function updateOrCreate(array $data): ?TransactionJournalMeta { + Log::debug('In updateOrCreate()'); $value = $data['data']; /** @var TransactionJournalMeta $entry */ $entry = $data['journal']->transactionJournalMeta()->where('name', $data['name'])->first(); if (null === $value && null !== $entry) { + Log::debug('Value is empty, delete meta value.'); try { $entry->delete(); } catch (Exception $e) { // @codeCoverageIgnore @@ -67,11 +70,14 @@ class TransactionJournalMetaFactory } if ($data['data'] instanceof Carbon) { + Log::debug('Is a carbon object.'); $value = $data['data']->toW3cString(); } if ('' === (string)$value) { + Log::debug('Is an empty string.'); // don't store blank strings. if (null !== $entry) { + Log::debug('Will not store empty strings, delete meta value'); try { $entry->delete(); } catch (Exception $e) { // @codeCoverageIgnore @@ -83,11 +89,13 @@ class TransactionJournalMetaFactory } if (null === $entry) { + Log::debug('Will create new object.'); Log::debug(sprintf('Going to create new meta-data entry to store "%s".', $data['name'])); $entry = new TransactionJournalMeta(); $entry->transactionJournal()->associate($data['journal']); $entry->name = $data['name']; } + Log::debug('Will update value and return.'); $entry->data = $value; $entry->save(); diff --git a/app/Factory/TransactionTypeFactory.php b/app/Factory/TransactionTypeFactory.php index 9497cae39b..ac5a718a7f 100644 --- a/app/Factory/TransactionTypeFactory.php +++ b/app/Factory/TransactionTypeFactory.php @@ -35,11 +35,12 @@ class TransactionTypeFactory { /** * Constructor. + * @codeCoverageIgnore */ public function __construct() { if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } diff --git a/app/Generator/Chart/Basic/ChartJsGenerator.php b/app/Generator/Chart/Basic/ChartJsGenerator.php index 74447a7257..04a0fcb9ca 100644 --- a/app/Generator/Chart/Basic/ChartJsGenerator.php +++ b/app/Generator/Chart/Basic/ChartJsGenerator.php @@ -32,11 +32,12 @@ class ChartJsGenerator implements GeneratorInterface { /** * Constructor. + * @codeCoverageIgnore */ public function __construct() { if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } @@ -61,7 +62,7 @@ class ChartJsGenerator implements GeneratorInterface $amounts = array_column($data, 'amount'); $next = next($amounts); $sortFlag = SORT_ASC; - if (!\is_bool($next) && 1 === bccomp((string)$next, '0')) { + if (!is_bool($next) && 1 === bccomp((string)$next, '0')) { $sortFlag = SORT_DESC; } array_multisort($amounts, $sortFlag, $data); @@ -118,10 +119,10 @@ class ChartJsGenerator implements GeneratorInterface { reset($data); $first = current($data); - $labels = \is_array($first['entries']) ? array_keys($first['entries']) : []; + $labels = is_array($first['entries']) ? array_keys($first['entries']) : []; $chartData = [ - 'count' => \count($data), + 'count' => count($data), 'labels' => $labels, // take ALL labels from the first set. 'datasets' => [], ]; @@ -173,7 +174,7 @@ class ChartJsGenerator implements GeneratorInterface // different sort when values are positive and when they're negative. asort($data); $next = next($data); - if (!\is_bool($next) && 1 === bccomp((string)$next, '0')) { + if (!is_bool($next) && 1 === bccomp((string)$next, '0')) { // next is positive, sort other way around. arsort($data); } diff --git a/app/Generator/Report/Audit/MonthReportGenerator.php b/app/Generator/Report/Audit/MonthReportGenerator.php index cadc73b157..53b9b13670 100644 --- a/app/Generator/Report/Audit/MonthReportGenerator.php +++ b/app/Generator/Report/Audit/MonthReportGenerator.php @@ -28,11 +28,10 @@ namespace FireflyIII\Generator\Report\Audit; use Carbon\Carbon; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Generator\Report\ReportGeneratorInterface; -use FireflyIII\Helpers\Collector\TransactionCollectorInterface; +use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Models\Account; -use FireflyIII\Models\Transaction; use FireflyIII\Repositories\Account\AccountRepositoryInterface; -use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; +use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use Illuminate\Support\Collection; use Log; use Throwable; @@ -72,13 +71,15 @@ class MonthReportGenerator implements ReportGeneratorInterface $reportType = 'audit'; $accountIds = implode(',', $this->accounts->pluck('id')->toArray()); $hideable = ['buttons', 'icon', 'description', 'balance_before', 'amount', 'balance_after', 'date', - 'interest_date', 'book_date', 'process_date', - // three new optional fields. - 'due_date', 'payment_date', 'invoice_date', + 'from', 'to', 'budget', 'category', 'bill', + // more new optional fields - 'internal_reference', 'notes', 'create_date', 'update_date', + + // date fields. + 'interest_date', 'book_date', 'process_date', + 'due_date', 'payment_date', 'invoice_date', ]; try { $result = view('reports.audit.report', compact('reportType', 'accountIds', 'auditData', 'hideable', 'defaultShow')) @@ -86,71 +87,13 @@ class MonthReportGenerator implements ReportGeneratorInterface ->render(); } catch (Throwable $e) { Log::error(sprintf('Cannot render reports.audit.report: %s', $e->getMessage())); - $result = 'Could not render report view.'; + Log::error($e->getTraceAsString()); + $result = sprintf('Could not render report view: %s', $e->getMessage()); } return $result; } - /** - * Get the audit report. - * - * @param Account $account - * @param Carbon $date - * - * @return array - * - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) // not that long - * @throws FireflyException - */ - public function getAuditReport(Account $account, Carbon $date): array - { - /** @var CurrencyRepositoryInterface $currencyRepos */ - $currencyRepos = app(CurrencyRepositoryInterface::class); - - /** @var AccountRepositoryInterface $accountRepository */ - $accountRepository = app(AccountRepositoryInterface::class); - $accountRepository->setUser($account->user); - - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setAccounts(new Collection([$account]))->setRange($this->start, $this->end); - $journals = $collector->getTransactions(); - $journals = $journals->reverse(); - $dayBeforeBalance = app('steam')->balance($account, $date); - $startBalance = $dayBeforeBalance; - $currency = $currencyRepos->findNull((int)$accountRepository->getMetaValue($account, 'currency_id')); - - if (null === $currency) { - throw new FireflyException('Unexpected NULL value in account currency preference.'); - } - - /** @var Transaction $transaction */ - foreach ($journals as $transaction) { - $transaction->before = $startBalance; - $transactionAmount = $transaction->transaction_amount; - - if ($currency->id === $transaction->foreign_currency_id) { - $transactionAmount = $transaction->transaction_foreign_amount; - } - - $newBalance = bcadd($startBalance, $transactionAmount); - $transaction->after = $newBalance; - $startBalance = $newBalance; - } - - $return = [ - 'journals' => $journals->reverse(), - 'exists' => $journals->count() > 0, - 'end' => $this->end->formatLocalized((string)trans('config.month_and_day')), - 'endBalance' => app('steam')->balance($account, $this->end), - 'dayBefore' => $date->formatLocalized((string)trans('config.month_and_day')), - 'dayBeforeBalance' => $dayBeforeBalance, - ]; - - return $return; - } - /** * Account collection setter. * @@ -217,6 +160,7 @@ class MonthReportGenerator implements ReportGeneratorInterface */ public function setExpense(Collection $expense): ReportGeneratorInterface { + // doesn't use expense collection. return $this; } @@ -247,4 +191,71 @@ class MonthReportGenerator implements ReportGeneratorInterface { return $this; } + + /** + * Get the audit report. + * + * @param Account $account + * @param Carbon $date + * + * @return array + * + * @throws FireflyException + */ + public function getAuditReport(Account $account, Carbon $date): array + { + /** @var AccountRepositoryInterface $accountRepository */ + $accountRepository = app(AccountRepositoryInterface::class); + $accountRepository->setUser($account->user); + + /** @var JournalRepositoryInterface $journalRepository */ + $journalRepository = app(JournalRepositoryInterface::class); + $journalRepository->setUser($account->user); + + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setAccounts(new Collection([$account]))->setRange($this->start, $this->end) + ->withAccountInformation(); + $journals = $collector->getExtractedJournals(); + $dayBeforeBalance = app('steam')->balance($account, $date); + $startBalance = $dayBeforeBalance; + $currency = $accountRepository->getAccountCurrency($account); + + if (null === $currency) { + throw new FireflyException('Unexpected NULL value in account currency preference.'); // @codeCoverageIgnore + } + + foreach ($journals as $index => $journal) { + $journals[$index]['balance_before'] = $startBalance; + $transactionAmount = $journal['amount']; + + if ($currency->id === $journal['foreign_currency_id']) { + $transactionAmount = $journal['foreign_amount']; + } + + $newBalance = bcadd($startBalance, $transactionAmount); + $journals[$index]['balance_after'] = $newBalance; + $startBalance = $newBalance; + + // add meta dates for each journal. + $journals[$index]['interest_date'] = $journalRepository->getMetaDateById($journal['transaction_journal_id'], 'interest_date'); + $journals[$index]['book_date'] = $journalRepository->getMetaDateById($journal['transaction_journal_id'], 'book_date'); + $journals[$index]['process_date'] = $journalRepository->getMetaDateById($journal['transaction_journal_id'], 'process_date'); + $journals[$index]['due_date'] = $journalRepository->getMetaDateById($journal['transaction_journal_id'], 'due_date'); + $journals[$index]['payment_date'] = $journalRepository->getMetaDateById($journal['transaction_journal_id'], 'payment_date'); + $journals[$index]['invoice_date'] = $journalRepository->getMetaDateById($journal['transaction_journal_id'], 'invoice_date'); + + } + + $return = [ + 'journals' => $journals, + 'exists' => count($journals) > 0, + 'end' => $this->end->formatLocalized((string)trans('config.month_and_day')), + 'endBalance' => app('steam')->balance($account, $this->end), + 'dayBefore' => $date->formatLocalized((string)trans('config.month_and_day')), + 'dayBeforeBalance' => $dayBeforeBalance, + ]; + + return $return; + } } diff --git a/app/Generator/Report/Budget/MonthReportGenerator.php b/app/Generator/Report/Budget/MonthReportGenerator.php index 6b7539c0d4..85c0f70789 100644 --- a/app/Generator/Report/Budget/MonthReportGenerator.php +++ b/app/Generator/Report/Budget/MonthReportGenerator.php @@ -27,11 +27,7 @@ namespace FireflyIII\Generator\Report\Budget; use Carbon\Carbon; use FireflyIII\Generator\Report\ReportGeneratorInterface; use FireflyIII\Generator\Report\Support; -use FireflyIII\Helpers\Collector\TransactionCollectorInterface; -use FireflyIII\Helpers\Filter\OpposingAccountFilter; -use FireflyIII\Helpers\Filter\PositiveAmountFilter; -use FireflyIII\Helpers\Filter\TransferFilter; -use FireflyIII\Models\Transaction; +use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Models\TransactionType; use Illuminate\Support\Collection; use Log; @@ -50,7 +46,7 @@ class MonthReportGenerator extends Support implements ReportGeneratorInterface private $budgets; /** @var Carbon The end date. */ private $end; - /** @var Collection The expenses in the report. */ + /** @var array The expenses in the report. */ private $expenses; /** @var Carbon The start date. */ private $start; @@ -93,6 +89,57 @@ class MonthReportGenerator extends Support implements ReportGeneratorInterface return $result; } + /** + * Get the expenses. + * + * @return array + */ + protected function getExpenses(): array + { + if (count($this->expenses) > 0) { + Log::debug('Return previous set of expenses.'); + + return $this->expenses; + } + + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setAccounts($this->accounts)->setRange($this->start, $this->end) + ->setTypes([TransactionType::WITHDRAWAL]) + ->withAccountInformation() + ->withBudgetInformation() + ->setBudgets($this->budgets); + + $journals = $collector->getExtractedJournals(); + $this->expenses = $journals; + + return $journals; + } + + /** + * Summarize a collection by its budget. + * + * @param array $array + * + * @return array + */ + private function summarizeByBudget(array $array): array + { + $result = [ + 'sum' => '0', + ]; + + /** @var array $journal */ + foreach ($array as $journal) { + $budgetId = (int)$journal['budget_id']; + $result[$budgetId] = $result[$budgetId] ?? '0'; + $result[$budgetId] = bcadd($journal['amount'], $result[$budgetId]); + $result['sum'] = bcadd($result['sum'], $journal['amount']); + } + + return $result; + } + /** * Set the involved accounts. * @@ -184,58 +231,4 @@ class MonthReportGenerator extends Support implements ReportGeneratorInterface { return $this; } - - /** - * Get the expenses. - * - * @return Collection - */ - protected function getExpenses(): Collection - { - if ($this->expenses->count() > 0) { - Log::debug('Return previous set of expenses.'); - - return $this->expenses; - } - - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setAccounts($this->accounts)->setRange($this->start, $this->end) - ->setTypes([TransactionType::WITHDRAWAL]) - ->setBudgets($this->budgets)->withOpposingAccount(); - $collector->removeFilter(TransferFilter::class); - - $collector->addFilter(OpposingAccountFilter::class); - $collector->addFilter(PositiveAmountFilter::class); - - $transactions = $collector->getTransactions(); - $this->expenses = $transactions; - - return $transactions; - } - - /** - * Summarize a collection by its budget. - * - * @param Collection $collection - * - * @return array - */ - private function summarizeByBudget(Collection $collection): array - { - $result = [ - 'sum' => '0', - ]; - /** @var Transaction $transaction */ - foreach ($collection as $transaction) { - $jrnlBudId = (int)$transaction->transaction_journal_budget_id; - $transBudId = (int)$transaction->transaction_budget_id; - $budgetId = max($jrnlBudId, $transBudId); - $result[$budgetId] = $result[$budgetId] ?? '0'; - $result[$budgetId] = bcadd($transaction->transaction_amount, $result[$budgetId]); - $result['sum'] = bcadd($result['sum'], $transaction->transaction_amount); - } - - return $result; - } } diff --git a/app/Generator/Report/Category/MonthReportGenerator.php b/app/Generator/Report/Category/MonthReportGenerator.php index cb7a299ac4..faf5a15d1d 100644 --- a/app/Generator/Report/Category/MonthReportGenerator.php +++ b/app/Generator/Report/Category/MonthReportGenerator.php @@ -27,12 +27,7 @@ namespace FireflyIII\Generator\Report\Category; use Carbon\Carbon; use FireflyIII\Generator\Report\ReportGeneratorInterface; use FireflyIII\Generator\Report\Support; -use FireflyIII\Helpers\Collector\TransactionCollectorInterface; -use FireflyIII\Helpers\Filter\NegativeAmountFilter; -use FireflyIII\Helpers\Filter\OpposingAccountFilter; -use FireflyIII\Helpers\Filter\PositiveAmountFilter; -use FireflyIII\Helpers\Filter\TransferFilter; -use FireflyIII\Models\Transaction; +use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Models\TransactionType; use Illuminate\Support\Collection; use Log; @@ -51,9 +46,9 @@ class MonthReportGenerator extends Support implements ReportGeneratorInterface private $categories; /** @var Carbon The end date */ private $end; - /** @var Collection The expenses */ + /** @var array The expenses */ private $expenses; - /** @var Collection The income in the report. */ + /** @var array The income in the report. */ private $income; /** @var Carbon The start date. */ private $start; @@ -79,7 +74,7 @@ class MonthReportGenerator extends Support implements ReportGeneratorInterface $reportType = 'category'; $expenses = $this->getExpenses(); $income = $this->getIncome(); - $accountSummary = $this->getObjectSummary($this->summarizeByAccount($expenses), $this->summarizeByAccount($income)); + $accountSummary = $this->getObjectSummary($this->summarizeByAssetAccount($expenses), $this->summarizeByAssetAccount($income)); $categorySummary = $this->getObjectSummary($this->summarizeByCategory($expenses), $this->summarizeByCategory($income)); $averageExpenses = $this->getAverages($expenses, SORT_ASC); $averageIncome = $this->getAverages($income, SORT_DESC); @@ -106,6 +101,75 @@ class MonthReportGenerator extends Support implements ReportGeneratorInterface return $result; } + /** + * Get the expenses for this report. + * + * @return array + */ + protected function getExpenses(): array + { + if (count($this->expenses) > 0) { + Log::debug('Return previous set of expenses.'); + + return $this->expenses; + } + + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setAccounts($this->accounts)->setRange($this->start, $this->end) + ->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER]) + ->setCategories($this->categories)->withAccountInformation(); + + $transactions = $collector->getExtractedJournals(); + $this->expenses = $transactions; + + return $transactions; + } + + /** + * Get the income for this report. + * + * @return array + */ + protected function getIncome(): array + { + if (count($this->income) > 0) { + return $this->income; + } + + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + + $collector->setAccounts($this->accounts)->setRange($this->start, $this->end) + ->setTypes([TransactionType::DEPOSIT, TransactionType::TRANSFER]) + ->setCategories($this->categories)->withAccountInformation(); + + $transactions = $collector->getExtractedJournals(); + $this->income = $transactions; + + return $transactions; + } + + /** + * Summarize the category. + * + * @param array $array + * + * @return array + */ + private function summarizeByCategory(array $array): array + { + $result = []; + /** @var array $journal */ + foreach ($array as $journal) { + $categoryId = (int)$journal['category_id']; + $result[$categoryId] = $result[$categoryId] ?? '0'; + $result[$categoryId] = bcadd($journal['amount'], $result[$categoryId]); + } + + return $result; + } + /** * Set the involved accounts. * @@ -197,81 +261,4 @@ class MonthReportGenerator extends Support implements ReportGeneratorInterface { return $this; } - - /** - * Get the expenses for this report. - * - * @return Collection - */ - protected function getExpenses(): Collection - { - if ($this->expenses->count() > 0) { - Log::debug('Return previous set of expenses.'); - - return $this->expenses; - } - - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setAccounts($this->accounts)->setRange($this->start, $this->end) - ->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER]) - ->setCategories($this->categories)->withOpposingAccount(); - $collector->removeFilter(TransferFilter::class); - - $collector->addFilter(OpposingAccountFilter::class); - $collector->addFilter(PositiveAmountFilter::class); - - $transactions = $collector->getTransactions(); - $this->expenses = $transactions; - - return $transactions; - } - - /** - * Get the income for this report. - * - * @return Collection - */ - protected function getIncome(): Collection - { - if ($this->income->count() > 0) { - return $this->income; - } - - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setAccounts($this->accounts)->setRange($this->start, $this->end) - ->setTypes([TransactionType::DEPOSIT, TransactionType::TRANSFER]) - ->setCategories($this->categories)->withOpposingAccount(); - - $collector->addFilter(OpposingAccountFilter::class); - $collector->addFilter(NegativeAmountFilter::class); - - $transactions = $collector->getTransactions(); - $this->income = $transactions; - - return $transactions; - } - - /** - * Summarize the category. - * - * @param Collection $collection - * - * @return array - */ - private function summarizeByCategory(Collection $collection): array - { - $result = []; - /** @var Transaction $transaction */ - foreach ($collection as $transaction) { - $jrnlCatId = (int)$transaction->transaction_journal_category_id; - $transCatId = (int)$transaction->transaction_category_id; - $categoryId = max($jrnlCatId, $transCatId); - $result[$categoryId] = $result[$categoryId] ?? '0'; - $result[$categoryId] = bcadd($transaction->transaction_amount, $result[$categoryId]); - } - - return $result; - } } diff --git a/app/Generator/Report/Support.php b/app/Generator/Report/Support.php index dad556dc7a..1f5e8d3b78 100644 --- a/app/Generator/Report/Support.php +++ b/app/Generator/Report/Support.php @@ -24,13 +24,12 @@ declare(strict_types=1); namespace FireflyIII\Generator\Report; -use FireflyIII\Models\Transaction; -use Illuminate\Support\Collection; +use FireflyIII\Models\TransactionType; /** * Class Support. - * @method Collection getExpenses() - * @method Collection getIncome() + * @method array getExpenses() + * @method array getIncome() * * @codeCoverageIgnore */ @@ -39,53 +38,63 @@ class Support /** * Get the top expenses. * - * @return Collection + * @return array */ - public function getTopExpenses(): Collection + public function getTopExpenses(): array { - return $this->getExpenses()->sortBy('transaction_amount'); + $expenses = $this->getExpenses(); + usort($expenses, function ($a, $b) { + return $a['amount'] <=> $b['amount']; + }); + + return $expenses; } /** * Get the top income. * - * @return Collection + * @return array */ - public function getTopIncome(): Collection + public function getTopIncome(): array { - return $this->getIncome()->sortByDesc('transaction_amount'); + $income = $this->getIncome(); + usort($income, function ($a, $b) { + return $b['amount'] <=> $a['amount']; + }); + + return $income; } /** * Get averages from a collection. * - * @param Collection $collection - * @param int $sortFlag + * @param array $array + * @param int $sortFlag * * @return array */ - protected function getAverages(Collection $collection, int $sortFlag): array + protected function getAverages(array $array, int $sortFlag): array { $result = []; - /** @var Transaction $transaction */ - foreach ($collection as $transaction) { + /** @var array $journal */ + foreach ($array as $journal) { // opposing name and ID: - $opposingId = $transaction->opposing_account_id; + $opposingId = $journal['destination_account_id']; // is not set? if (!isset($result[$opposingId])) { - $name = $transaction->opposing_account_name; + $name = $journal['destination_account_name']; $result[$opposingId] = [ 'name' => $name, 'count' => 1, 'id' => $opposingId, - 'average' => $transaction->transaction_amount, - 'sum' => $transaction->transaction_amount, + 'average' => $journal['amount'], + 'sum' => $journal['amount'], ]; continue; } ++$result[$opposingId]['count']; - $result[$opposingId]['sum'] = bcadd($result[$opposingId]['sum'], $transaction->transaction_amount); + $result[$opposingId]['sum'] = bcadd($result[$opposingId]['sum'], $journal['amount']); $result[$opposingId]['average'] = bcdiv($result[$opposingId]['sum'], (string)$result[$opposingId]['count']); } @@ -137,6 +146,7 @@ class Support * @var string $entry */ foreach ($earned as $objectId => $entry) { + $entry = bcmul($entry, '-1'); if (!isset($return[$objectId])) { $return[$objectId] = ['spent' => '0', 'earned' => '0']; } @@ -151,18 +161,50 @@ class Support /** * Summarize the data by account. * - * @param Collection $collection + * @param array $array * * @return array */ - protected function summarizeByAccount(Collection $collection): array + protected function summarizeByAccount(array $array): array { $result = []; - /** @var Transaction $transaction */ - foreach ($collection as $transaction) { - $accountId = $transaction->account_id; + /** @var array $journal */ + foreach ($array as $journal) { + $accountId = $journal['source_account_id'] ?? 0; $result[$accountId] = $result[$accountId] ?? '0'; - $result[$accountId] = bcadd($transaction->transaction_amount, $result[$accountId]); + $result[$accountId] = bcadd($journal['amount'], $result[$accountId]); + } + + return $result; + } + + /** + * Summarize the data by the asset account or liability, depending on the type. + * + * In case of transfers, it will choose the source account. + * + * @param array $array + * + * @return array + */ + protected function summarizeByAssetAccount(array $array): array + { + $result = []; + /** @var array $journal */ + foreach ($array as $journal) { + $accountId = 0; + switch ($journal['transaction_type_type']) { + case TransactionType::WITHDRAWAL: + case TransactionType::TRANSFER: + $accountId = $journal['source_account_id'] ?? 0; + break; + case TransactionType::DEPOSIT: + $accountId = $journal['destination_account_id'] ?? 0; + break; + } + + $result[$accountId] = $result[$accountId] ?? '0'; + $result[$accountId] = bcadd($journal['amount'], $result[$accountId]); } return $result; diff --git a/app/Generator/Report/Tag/MonthReportGenerator.php b/app/Generator/Report/Tag/MonthReportGenerator.php index 165682c172..706bc1da69 100644 --- a/app/Generator/Report/Tag/MonthReportGenerator.php +++ b/app/Generator/Report/Tag/MonthReportGenerator.php @@ -28,14 +28,7 @@ namespace FireflyIII\Generator\Report\Tag; use Carbon\Carbon; use FireflyIII\Generator\Report\ReportGeneratorInterface; use FireflyIII\Generator\Report\Support; -use FireflyIII\Helpers\Collector\TransactionCollectorInterface; -use FireflyIII\Helpers\Filter\DoubleTransactionFilter; -use FireflyIII\Helpers\Filter\NegativeAmountFilter; -use FireflyIII\Helpers\Filter\OpposingAccountFilter; -use FireflyIII\Helpers\Filter\PositiveAmountFilter; -use FireflyIII\Helpers\Filter\TransferFilter; -use FireflyIII\Models\Tag; -use FireflyIII\Models\Transaction; +use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Models\TransactionType; use Illuminate\Support\Collection; use Log; @@ -52,9 +45,9 @@ class MonthReportGenerator extends Support implements ReportGeneratorInterface private $accounts; /** @var Carbon The end date */ private $end; - /** @var Collection The expenses involved */ + /** @var array The expenses involved */ private $expenses; - /** @var Collection The income involved */ + /** @var array The income involved */ private $income; /** @var Carbon The start date */ private $start; @@ -84,7 +77,7 @@ class MonthReportGenerator extends Support implements ReportGeneratorInterface $reportType = 'tag'; $expenses = $this->getExpenses(); $income = $this->getIncome(); - $accountSummary = $this->getObjectSummary($this->summarizeByAccount($expenses), $this->summarizeByAccount($income)); + $accountSummary = $this->getObjectSummary($this->summarizeByAssetAccount($expenses), $this->summarizeByAssetAccount($income)); $tagSummary = $this->getObjectSummary($this->summarizeByTag($expenses), $this->summarizeByTag($income)); $averageExpenses = $this->getAverages($expenses, SORT_ASC); $averageIncome = $this->getAverages($income, SORT_DESC); @@ -202,80 +195,72 @@ class MonthReportGenerator extends Support implements ReportGeneratorInterface /** * Get expense collection for report. * - * @return Collection + * @return array */ - protected function getExpenses(): Collection + protected function getExpenses(): array { - if ($this->expenses->count() > 0) { + if (count($this->expenses) > 0) { Log::debug('Return previous set of expenses.'); return $this->expenses; } - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); $collector->setAccounts($this->accounts)->setRange($this->start, $this->end) ->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER]) - ->setTags($this->tags)->withOpposingAccount(); - $collector->removeFilter(TransferFilter::class); - $collector->addFilter(OpposingAccountFilter::class); - $collector->addFilter(PositiveAmountFilter::class); - $collector->addFilter(DoubleTransactionFilter::class); + ->setTags($this->tags)->withAccountInformation(); - $transactions = $collector->getTransactions(); - $this->expenses = $transactions; + $journals = $collector->getExtractedJournals(); + $this->expenses = $journals; - return $transactions; + return $journals; } /** * Get the income for this report. * - * @return Collection + * @return array */ - protected function getIncome(): Collection + protected function getIncome(): array { - if ($this->income->count() > 0) { + if (count($this->income) > 0) { return $this->income; } - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); $collector->setAccounts($this->accounts)->setRange($this->start, $this->end) ->setTypes([TransactionType::DEPOSIT, TransactionType::TRANSFER]) - ->setTags($this->tags)->withOpposingAccount(); + ->setTags($this->tags)->withAccountInformation(); - $collector->addFilter(OpposingAccountFilter::class); - $collector->addFilter(NegativeAmountFilter::class); - $collector->addFilter(DoubleTransactionFilter::class); + $journals = $collector->getExtractedJournals(); + $this->income = $journals; - $transactions = $collector->getTransactions(); - $this->income = $transactions; - - return $transactions; + return $journals; } /** * Summarize by tag. * - * @param Collection $collection + * @param array $array * * @return array */ - protected function summarizeByTag(Collection $collection): array + protected function summarizeByTag(array $array): array { $tagIds = array_map('\intval', $this->tags->pluck('id')->toArray()); $result = []; - /** @var Transaction $transaction */ - foreach ($collection as $transaction) { - $journal = $transaction->transactionJournal; - $journalTags = $journal->tags; - /** @var Tag $journalTag */ - foreach ($journalTags as $journalTag) { - $journalTagId = (int)$journalTag->id; - if (\in_array($journalTagId, $tagIds, true)) { - $result[$journalTagId] = $result[$journalTagId] ?? '0'; - $result[$journalTagId] = bcadd($transaction->transaction_amount, $result[$journalTagId]); + /** @var array $journal */ + foreach ($array as $journal) { + /** + * @var int $id + * @var array $tag + */ + foreach ($journal['tags'] as $id => $tag) { + if (in_array($id, $tagIds, true)) { + $result[$id] = $result[$id] ?? '0'; + $result[$id] = bcadd($journal['amount'], $result[$id]); } } } diff --git a/app/Handlers/Events/StoredGroupEventHandler.php b/app/Handlers/Events/StoredGroupEventHandler.php new file mode 100644 index 0000000000..7faa916c3a --- /dev/null +++ b/app/Handlers/Events/StoredGroupEventHandler.php @@ -0,0 +1,60 @@ +. + */ +declare(strict_types=1); + +namespace FireflyIII\Handlers\Events; + +use FireflyIII\Events\StoredTransactionGroup; +use FireflyIII\Models\TransactionJournal; +use FireflyIII\TransactionRules\Engine\RuleEngine; +use Log; + +/** + * Class StoredGroupEventHandler + */ +class StoredGroupEventHandler +{ + /** + * This method grabs all the users rules and processes them. + * + * @param StoredTransactionGroup $storedJournalEvent + */ + public function processRules(StoredTransactionGroup $storedJournalEvent): void + { + if (false === $storedJournalEvent->applyRules) { + return; + } + Log::debug('Now in StoredGroupEventHandler::processRules()'); + + /** @var RuleEngine $ruleEngine */ + $ruleEngine = app(RuleEngine::class); + $ruleEngine->setUser($storedJournalEvent->transactionGroup->user); + $ruleEngine->setAllRules(true); + $ruleEngine->setTriggerMode(RuleEngine::TRIGGER_STORE); + $journals = $storedJournalEvent->transactionGroup->transactionJournals; + + /** @var TransactionJournal $journal */ + foreach ($journals as $journal) { + $ruleEngine->processTransactionJournal($journal); + } + } + +} diff --git a/app/Handlers/Events/StoredJournalEventHandler.php b/app/Handlers/Events/StoredJournalEventHandler.php deleted file mode 100644 index 0b20bb6cf5..0000000000 --- a/app/Handlers/Events/StoredJournalEventHandler.php +++ /dev/null @@ -1,73 +0,0 @@ -. - */ -declare(strict_types=1); - -namespace FireflyIII\Handlers\Events; - -use FireflyIII\Events\StoredTransactionJournal; -use FireflyIII\Models\Rule; -use FireflyIII\Models\RuleGroup; -use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface; -use FireflyIII\TransactionRules\Processor; - -/** - * Class StoredJournalEventHandler - */ -class StoredJournalEventHandler -{ - /** - * This method grabs all the users rules and processes them. - * - * @param StoredTransactionJournal $storedJournalEvent - * - * @return bool - * @throws \FireflyIII\Exceptions\FireflyException - */ - public function processRules(StoredTransactionJournal $storedJournalEvent): bool - { - $journal = $storedJournalEvent->journal; - - // create objects: - /** @var RuleGroupRepositoryInterface $ruleGroupRepos */ - $ruleGroupRepos = app(RuleGroupRepositoryInterface::class); - $ruleGroupRepos->setUser($journal->user); - $groups = $ruleGroupRepos->getActiveGroups($journal->user); - - /** @var RuleGroup $group */ - foreach ($groups as $group) { - $rules = $ruleGroupRepos->getActiveStoreRules($group); - /** @var Rule $rule */ - foreach ($rules as $rule) { - /** @var Processor $processor */ - $processor = app(Processor::class); - $processor->make($rule); - $processor->handleTransactionJournal($journal); - - if ($rule->stop_processing) { - break; - } - } - } - - return true; - } - -} diff --git a/app/Handlers/Events/UpdatedGroupEventHandler.php b/app/Handlers/Events/UpdatedGroupEventHandler.php new file mode 100644 index 0000000000..18bbd8d066 --- /dev/null +++ b/app/Handlers/Events/UpdatedGroupEventHandler.php @@ -0,0 +1,55 @@ +. + */ +declare(strict_types=1); + +namespace FireflyIII\Handlers\Events; + +use FireflyIII\Events\UpdatedTransactionGroup; +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Models\TransactionJournal; +use FireflyIII\TransactionRules\Engine\RuleEngine; + +/** + * Class UpdatedGroupEventHandler + */ +class UpdatedGroupEventHandler +{ + /** + * This method will check all the rules when a journal is updated. + * + * @param UpdatedTransactionGroup $updatedJournalEvent + */ + public function processRules(UpdatedTransactionGroup $updatedJournalEvent): void + { + /** @var RuleEngine $ruleEngine */ + $ruleEngine = app(RuleEngine::class); + $ruleEngine->setUser($updatedJournalEvent->transactionGroup->user); + $ruleEngine->setAllRules(true); + $ruleEngine->setTriggerMode(RuleEngine::TRIGGER_UPDATE); + $journals = $updatedJournalEvent->transactionGroup->transactionJournals; + + /** @var TransactionJournal $journal */ + foreach ($journals as $journal) { + $ruleEngine->processTransactionJournal($journal); + } + } + +} diff --git a/app/Handlers/Events/UpdatedJournalEventHandler.php b/app/Handlers/Events/UpdatedJournalEventHandler.php deleted file mode 100644 index 0561ad928f..0000000000 --- a/app/Handlers/Events/UpdatedJournalEventHandler.php +++ /dev/null @@ -1,74 +0,0 @@ -. - */ -declare(strict_types=1); - -namespace FireflyIII\Handlers\Events; - -use FireflyIII\Events\UpdatedTransactionJournal; -use FireflyIII\Models\Rule; -use FireflyIII\Models\RuleGroup; -use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface; -use FireflyIII\TransactionRules\Processor; - -/** - * Class UpdatedJournalEventHandler - */ -class UpdatedJournalEventHandler -{ - /** - * This method will check all the rules when a journal is updated. - * - * @param UpdatedTransactionJournal $updatedJournalEvent - * - * @return bool - * @throws \FireflyIII\Exceptions\FireflyException - */ - public function processRules(UpdatedTransactionJournal $updatedJournalEvent): bool - { - // get all the user's rule groups, with the rules, order by 'order'. - $journal = $updatedJournalEvent->journal; - - /** @var RuleGroupRepositoryInterface $ruleGroupRepos */ - $ruleGroupRepos = app(RuleGroupRepositoryInterface::class); - $ruleGroupRepos->setUser($journal->user); - - $groups = $ruleGroupRepos->getActiveGroups($journal->user); - - /** @var RuleGroup $group */ - foreach ($groups as $group) { - $rules = $ruleGroupRepos->getActiveUpdateRules($group); - /** @var Rule $rule */ - foreach ($rules as $rule) { - /** @var Processor $processor */ - $processor = app(Processor::class); - $processor->make($rule); - $processor->handleTransactionJournal($journal); - - if ($rule->stop_processing) { - break; - } - } - } - - return true; - } - -} diff --git a/app/Handlers/Events/UserEventHandler.php b/app/Handlers/Events/UserEventHandler.php index b4a3ebb92d..d19dcfb33a 100644 --- a/app/Handlers/Events/UserEventHandler.php +++ b/app/Handlers/Events/UserEventHandler.php @@ -43,7 +43,6 @@ use Mail; * This class responds to any events that have anything to do with the User object. * * The method name reflects what is being done. This is in the present tense. - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class UserEventHandler { diff --git a/app/Handlers/Events/VersionCheckEventHandler.php b/app/Handlers/Events/VersionCheckEventHandler.php index 5b7efb4e45..9a0029c74c 100644 --- a/app/Handlers/Events/VersionCheckEventHandler.php +++ b/app/Handlers/Events/VersionCheckEventHandler.php @@ -25,7 +25,6 @@ declare(strict_types=1); namespace FireflyIII\Handlers\Events; -use FireflyConfig; use FireflyIII\Events\RequestedVersionCheckStatus; use FireflyIII\Helpers\Update\UpdateTrait; use FireflyIII\Models\Configuration; @@ -71,7 +70,7 @@ class VersionCheckEventHandler } /** @var Configuration $lastCheckTime */ - $lastCheckTime = FireflyConfig::get('last_update_check', time()); + $lastCheckTime = app('fireflyconfig')->get('last_update_check', time()); $now = time(); $diff = $now - $lastCheckTime->data; Log::debug(sprintf('Last check time is %d, current time is %d, difference is %d', $lastCheckTime->data, $now, $diff)); @@ -90,6 +89,6 @@ class VersionCheckEventHandler // flash info session()->flash('info', $resultString); } - FireflyConfig::set('last_update_check', time()); + app('fireflyconfig')->set('last_update_check', time()); } } diff --git a/app/Helpers/Attachments/AttachmentHelper.php b/app/Helpers/Attachments/AttachmentHelper.php index 0da5cd83c7..7ef3ce9a05 100644 --- a/app/Helpers/Attachments/AttachmentHelper.php +++ b/app/Helpers/Attachments/AttachmentHelper.php @@ -56,6 +56,7 @@ class AttachmentHelper implements AttachmentHelperInterface /** * AttachmentHelper constructor. + * @codeCoverageIgnore */ public function __construct() { @@ -67,7 +68,7 @@ class AttachmentHelper implements AttachmentHelperInterface $this->uploadDisk = Storage::disk('upload'); if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } @@ -98,7 +99,7 @@ class AttachmentHelper implements AttachmentHelperInterface * Returns the file path relative to upload disk for an attachment, * * @param Attachment $attachment - * + * @codeCoverageIgnore * @return string */ public function getAttachmentLocation(Attachment $attachment): string @@ -108,7 +109,7 @@ class AttachmentHelper implements AttachmentHelperInterface /** * Get all attachments. - * + * @codeCoverageIgnore * @return Collection */ public function getAttachments(): Collection @@ -120,6 +121,7 @@ class AttachmentHelper implements AttachmentHelperInterface * Get all errors. * * @return MessageBag + * @codeCoverageIgnore */ public function getErrors(): MessageBag { @@ -130,13 +132,13 @@ class AttachmentHelper implements AttachmentHelperInterface * Get all messages. * * @return MessageBag + * @codeCoverageIgnore */ public function getMessages(): MessageBag { return $this->messages; } - /** @noinspection MultipleReturnStatementsInspection */ /** * Uploads a file as a string. * @@ -160,7 +162,7 @@ class AttachmentHelper implements AttachmentHelperInterface $finfo = finfo_open(FILEINFO_MIME_TYPE); $mime = finfo_file($finfo, $path); $allowedMime = config('firefly.allowedMimes'); - if (!\in_array($mime, $allowedMime, true)) { + if (!in_array($mime, $allowedMime, true)) { Log::error(sprintf('Mime type %s is not allowed for API file upload.', $mime)); return false; @@ -172,7 +174,7 @@ class AttachmentHelper implements AttachmentHelperInterface // update attachment. $attachment->md5 = md5_file($path); $attachment->mime = $mime; - $attachment->size = \strlen($content); + $attachment->size = strlen($content); $attachment->uploaded = true; $attachment->save(); @@ -193,8 +195,8 @@ class AttachmentHelper implements AttachmentHelperInterface if (!($model instanceof Model)) { return false; // @codeCoverageIgnore } - Log::debug(sprintf('Now in saveAttachmentsForModel for model %s', \get_class($model))); - if (\is_array($files)) { + Log::debug(sprintf('Now in saveAttachmentsForModel for model %s', get_class($model))); + if (is_array($files)) { Log::debug('$files is an array.'); /** @var UploadedFile $entry */ foreach ($files as $entry) { @@ -204,7 +206,7 @@ class AttachmentHelper implements AttachmentHelperInterface } Log::debug('Done processing uploads.'); } - if (!\is_array($files) || (\is_array($files) && 0 === \count($files))) { + if (!is_array($files) || (is_array($files) && 0 === count($files))) { Log::debug('Array of files is not an array. Probably nothing uploaded. Will not store attachments.'); } @@ -223,7 +225,7 @@ class AttachmentHelper implements AttachmentHelperInterface { $md5 = md5_file($file->getRealPath()); $name = $file->getClientOriginalName(); - $class = \get_class($model); + $class = get_class($model); /** @noinspection PhpUndefinedFieldInspection */ $count = $model->user->attachments()->where('md5', $md5)->where('attachable_id', $model->id)->where('attachable_type', $class)->count(); $result = false; @@ -274,8 +276,8 @@ class AttachmentHelper implements AttachmentHelperInterface $content = $fileObject->fread($file->getSize()); $encrypted = Crypt::encrypt($content); - Log::debug(sprintf('Full file length is %d and upload size is %d.', \strlen($content), $file->getSize())); - Log::debug(sprintf('Encrypted content is %d', \strlen($encrypted))); + Log::debug(sprintf('Full file length is %d and upload size is %d.', strlen($content), $file->getSize())); + Log::debug(sprintf('Encrypted content is %d', strlen($encrypted))); // store it: $this->uploadDisk->put($attachment->fileName(), $encrypted); @@ -307,7 +309,7 @@ class AttachmentHelper implements AttachmentHelperInterface Log::debug('Valid mimes are', $this->allowedMimes); $result = true; - if (!\in_array($mime, $this->allowedMimes, true)) { + if (!in_array($mime, $this->allowedMimes, true)) { $msg = (string)trans('validation.file_invalid_mime', ['name' => $name, 'mime' => $mime]); $this->errors->add('attachments', $msg); Log::error($msg); diff --git a/app/Helpers/Chart/MetaPieChart.php b/app/Helpers/Chart/MetaPieChart.php index 63eb17c81c..55c58704a7 100644 --- a/app/Helpers/Chart/MetaPieChart.php +++ b/app/Helpers/Chart/MetaPieChart.php @@ -24,13 +24,8 @@ declare(strict_types=1); namespace FireflyIII\Helpers\Chart; use Carbon\Carbon; -use FireflyIII\Helpers\Collector\TransactionCollectorInterface; -use FireflyIII\Helpers\Filter\NegativeAmountFilter; -use FireflyIII\Helpers\Filter\OpposingAccountFilter; -use FireflyIII\Helpers\Filter\PositiveAmountFilter; -use FireflyIII\Helpers\Filter\TransferFilter; +use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Models\Tag; -use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; @@ -48,9 +43,9 @@ class MetaPieChart implements MetaPieChartInterface /** @var array The ways to group transactions, given the type of chart. */ static protected $grouping = [ - 'account' => ['opposing_account_id'], - 'budget' => ['transaction_journal_budget_id', 'transaction_budget_id'], - 'category' => ['transaction_journal_category_id', 'transaction_category_id'], + 'account' => ['destination_account_id'], + 'budget' => ['budget_id'], + 'category' => ['category_id'], 'tag' => [], ]; /** @var Collection Involved accounts. */ @@ -91,7 +86,7 @@ class MetaPieChart implements MetaPieChartInterface $this->tags = new Collection; if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } @@ -114,25 +109,27 @@ class MetaPieChart implements MetaPieChartInterface // also collect all other transactions if ($this->collectOtherObjects && 'expense' === $direction) { - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); + + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setUser($this->user); $collector->setAccounts($this->accounts)->setRange($this->start, $this->end)->setTypes([TransactionType::WITHDRAWAL]); - $journals = $collector->getTransactions(); - $sum = (string)$journals->sum('transaction_amount'); + $sum = $collector->getSum(); $sum = bcmul($sum, '-1'); $sum = bcsub($sum, $this->total); $chartData[$key] = $sum; } if ($this->collectOtherObjects && 'income' === $direction) { - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); + + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setUser($this->user); $collector->setAccounts($this->accounts)->setRange($this->start, $this->end)->setTypes([TransactionType::DEPOSIT]); - $journals = $collector->getTransactions(); - $sum = (string)$journals->sum('transaction_amount'); + $sum = $collector->getSum(); $sum = bcsub($sum, $this->total); $chartData[$key] = $sum; } @@ -140,6 +137,131 @@ class MetaPieChart implements MetaPieChartInterface return $chartData; } + /** + * Get all transactions. + * + * @param string $direction + * + * @return array + */ + protected function getTransactions(string $direction): array + { + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + + $types = [TransactionType::DEPOSIT, TransactionType::TRANSFER]; + if ('expense' === $direction) { + $types = [TransactionType::WITHDRAWAL, TransactionType::TRANSFER]; + } + + $collector->setUser($this->user); + $collector->setAccounts($this->accounts); + $collector->setRange($this->start, $this->end); + $collector->setTypes($types); + $collector->withAccountInformation(); + + $collector->setBudgets($this->budgets); + $collector->setCategories($this->categories); + + // @codeCoverageIgnoreStart + if ($this->tags->count() > 0) { + $collector->setTags($this->tags); + } + + // @codeCoverageIgnoreEnd + + return $collector->getExtractedJournals(); + } + + /** + * Group by a specific field. + * + * @param array $array + * @param array $fields + * + * @return array + * + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * + */ + protected function groupByFields(array $array, array $fields): array + { + $grouped = []; + if (0 === count($fields) && $this->tags->count() > 0) { + // do a special group on tags: + $grouped = $this->groupByTag($array); // @codeCoverageIgnore + } + + if (0 !== count($fields) || $this->tags->count() <= 0) { + $grouped = []; + /** @var array $journal */ + foreach ($array as $journal) { + $values = []; + foreach ($fields as $field) { + $values[] = (int)$journal[$field]; + } + $value = max($values); + $grouped[$value] = $grouped[$value] ?? '0'; + $grouped[$value] = bcadd($journal['amount'], $grouped[$value]); + } + } + + return $grouped; + } + + /** + * Group by tag (slightly different). + * + * @codeCoverageIgnore + * + * @param array $array + * + * @return array + */ + private function groupByTag(array $array): array + { + $grouped = []; + /** @var array $journal */ + foreach ($array as $journal) { + $tags = $journal['tags'] ?? []; + /** @var Tag $tag */ + foreach ($tags as $id => $tag) { + $grouped[$id] = $grouped[$id] ?? '0'; + $grouped[$id] = bcadd($journal['amount'], $grouped[$id]); + } + } + + return $grouped; + } + + /** + * Organise by certain type. + * + * @param string $type + * @param array $array + * + * @return array + */ + protected function organizeByType(string $type, array $array): array + { + $chartData = []; + $names = []; + $repository = app($this->repositories[$type]); + $repository->setUser($this->user); + foreach ($array as $objectId => $amount) { + if (!isset($names[$objectId])) { + $object = $repository->findNull((int)$objectId); + $name = null === $object ? '(no name)' : $object->name; + $names[$objectId] = $name ?? $object->tag; + } + $amount = app('steam')->positive($amount); + $this->total = bcadd($this->total, $amount); + $chartData[$names[$objectId]] = $amount; + } + + return $chartData; + } + /** * Accounts setter. * @@ -268,139 +390,5 @@ class MetaPieChart implements MetaPieChartInterface return $this; } - /** - * Get all transactions. - * - * @param string $direction - * - * @return Collection - */ - protected function getTransactions(string $direction): Collection - { - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $types = [TransactionType::DEPOSIT, TransactionType::TRANSFER]; - $collector->addFilter(NegativeAmountFilter::class); - if ('expense' === $direction) { - $types = [TransactionType::WITHDRAWAL, TransactionType::TRANSFER]; - $collector->addFilter(PositiveAmountFilter::class); - $collector->removeFilter(NegativeAmountFilter::class); - } - $collector->setUser($this->user); - $collector->setAccounts($this->accounts); - $collector->setRange($this->start, $this->end); - $collector->setTypes($types); - $collector->withOpposingAccount(); - $collector->addFilter(OpposingAccountFilter::class); - - if ('income' === $direction) { - $collector->removeFilter(TransferFilter::class); - } - - $collector->setBudgets($this->budgets); - $collector->setCategories($this->categories); - - // @codeCoverageIgnoreStart - if ($this->tags->count() > 0) { - $collector->setTags($this->tags); - $collector->withCategoryInformation(); - $collector->withBudgetInformation(); - } - - // @codeCoverageIgnoreEnd - - return $collector->getTransactions(); - } - - /** - * Group by a specific field. - * - * @param Collection $set - * @param array $fields - * - * @return array - * - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * - */ - protected function groupByFields(Collection $set, array $fields): array - { - $grouped = []; - if (0 === \count($fields) && $this->tags->count() > 0) { - // do a special group on tags: - $grouped = $this->groupByTag($set); // @codeCoverageIgnore - } - - if (0 !== \count($fields) || $this->tags->count() <= 0) { - $grouped = []; - /** @var Transaction $transaction */ - foreach ($set as $transaction) { - $values = []; - foreach ($fields as $field) { - $values[] = (int)$transaction->$field; - } - $value = max($values); - $grouped[$value] = $grouped[$value] ?? '0'; - $grouped[$value] = bcadd($transaction->transaction_amount, $grouped[$value]); - } - } - - return $grouped; - } - - /** - * Organise by certain type. - * - * @param string $type - * @param array $array - * - * @return array - */ - protected function organizeByType(string $type, array $array): array - { - $chartData = []; - $names = []; - $repository = app($this->repositories[$type]); - $repository->setUser($this->user); - foreach ($array as $objectId => $amount) { - if (!isset($names[$objectId])) { - $object = $repository->findNull((int)$objectId); - $name = null === $object ? '(no name)' : $object->name; - $names[$objectId] = $name ?? $object->tag; - } - $amount = app('steam')->positive($amount); - $this->total = bcadd($this->total, $amount); - $chartData[$names[$objectId]] = $amount; - } - - return $chartData; - } - - /** - * Group by tag (slightly different). - * - * @codeCoverageIgnore - * - * @param Collection $set - * - * @return array - */ - private function groupByTag(Collection $set): array - { - $grouped = []; - /** @var Transaction $transaction */ - foreach ($set as $transaction) { - $journal = $transaction->transactionJournal; - $tags = $journal->tags; - /** @var Tag $tag */ - foreach ($tags as $tag) { - $tagId = $tag->id; - $grouped[$tagId] = $grouped[$tagId] ?? '0'; - $grouped[$tagId] = bcadd($transaction->transaction_amount, $grouped[$tagId]); - } - } - - return $grouped; - } } diff --git a/app/Helpers/Collection/Bill.php b/app/Helpers/Collection/Bill.php index f950a93914..7b50e12c2d 100644 --- a/app/Helpers/Collection/Bill.php +++ b/app/Helpers/Collection/Bill.php @@ -103,16 +103,7 @@ class Bill */ public function getBills(): Collection { - $set = $this->bills->sortBy( - function (BillLine $bill) { - $active = 0 === (int)$bill->getBill()->active ? 1 : 0; - $name = $bill->getBill()->name; - - return $active . $name; - } - ); - - return $set; + return $this->bills; } /** diff --git a/app/Helpers/Collection/Category.php b/app/Helpers/Collection/Category.php index ce774387ae..35b5529dd1 100644 --- a/app/Helpers/Collection/Category.php +++ b/app/Helpers/Collection/Category.php @@ -77,7 +77,7 @@ class Category public function getCategories(): Collection { $set = $this->categories->sortBy( - function (CategoryModel $category) { + static function (CategoryModel $category) { return $category->spent; } ); diff --git a/app/Helpers/Collector/GroupCollector.php b/app/Helpers/Collector/GroupCollector.php new file mode 100644 index 0000000000..c364719f0e --- /dev/null +++ b/app/Helpers/Collector/GroupCollector.php @@ -0,0 +1,1013 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Helpers\Collector; + +use Carbon\Carbon; +use Carbon\Exceptions\InvalidDateException; +use Exception; +use FireflyIII\Models\Bill; +use FireflyIII\Models\Budget; +use FireflyIII\Models\Category; +use FireflyIII\Models\Tag; +use FireflyIII\Models\TransactionCurrency; +use FireflyIII\Models\TransactionGroup; +use FireflyIII\User; +use Illuminate\Database\Eloquent\Builder as EloquentBuilder; +use Illuminate\Database\Eloquent\Relations\HasMany; +use Illuminate\Database\Query\JoinClause; +use Illuminate\Pagination\LengthAwarePaginator; +use Illuminate\Support\Collection; +use Log; + +/** + * Class GroupCollector + * @codeCoverageIgnore + */ +class GroupCollector implements GroupCollectorInterface +{ + /** @var array The accounts to filter on. Asset accounts or liabilities. */ + private $accountIds; + /** @var array The standard fields to select. */ + private $fields; + /** @var bool Will be set to true if query result contains account information. (see function withAccountInformation). */ + private $hasAccountInfo; + /** @var bool Will be true if query result includes bill information. */ + private $hasBillInformation; + /** @var bool Will be true if query result contains budget info. */ + private $hasBudgetInformation; + /** @var bool Will be true if query result contains category info. */ + private $hasCatInformation; + /** @var bool Will be true of the query has the tag info tables joined. */ + private $hasJoinedTagTables; + /** @var int The maximum number of results. */ + private $limit; + /** @var int The page to return. */ + private $page; + /** @var HasMany The query object. */ + private $query; + /** @var int Total number of results. */ + private $total; + /** @var User The user object. */ + private $user; + + /** + * Group collector constructor. + */ + public function __construct() + { + if ('testing' === config('app.env')) { + app('log')->warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); + } + $this->hasAccountInfo = false; + $this->hasCatInformation = false; + $this->hasBudgetInformation = false; + $this->hasBillInformation = false; + $this->hasJoinedTagTables = false; + + $this->total = 0; + $this->limit = 50; + $this->page = 0; + $this->fields = [ + # group + 'transaction_groups.id as transaction_group_id', + 'transaction_groups.user_id as user_id', + 'transaction_groups.created_at as created_at', + 'transaction_groups.updated_at as updated_at', + 'transaction_groups.title as transaction_group_title', + + # journal + 'transaction_journals.id as transaction_journal_id', + 'transaction_journals.transaction_type_id', + 'transaction_types.type as transaction_type_type', + 'transaction_journals.description', + 'transaction_journals.date', + 'transaction_journals.order', + + # source info (always present) + 'source.id as source_transaction_id', + 'source.account_id as source_account_id', + 'source.reconciled', + + # currency info: + 'source.amount as amount', + 'source.transaction_currency_id as currency_id', + 'currency.code as currency_code', + 'currency.name as currency_name', + 'currency.symbol as currency_symbol', + 'currency.decimal_places as currency_decimal_places', + + # foreign currency info + 'source.foreign_amount as foreign_amount', + 'source.foreign_currency_id as foreign_currency_id', + 'foreign_currency.code as foreign_currency_code', + 'foreign_currency.name as foreign_currency_name', + 'foreign_currency.symbol as foreign_currency_symbol', + 'foreign_currency.decimal_places as foreign_currency_decimal_places', + + # destination account info (always present) + #'destination.id as destination_transaction_id', // not interesting. + 'destination.account_id as destination_account_id', + ]; + } + + /** + * Same as getGroups but everything is in a paginator. + * + * @return LengthAwarePaginator + */ + public function getPaginatedGroups(): LengthAwarePaginator + { + $set = $this->getGroups(); + + return new LengthAwarePaginator($set, $this->total, $this->limit, $this->page); + } + + /** + * Return the groups. + * + * @return Collection + */ + public function getGroups(): Collection + { + /** @var Collection $result */ + $result = $this->query->get($this->fields); + + // now to parse this into an array. + $collection = $this->parseArray($result); + $this->total = $collection->count(); + + // now filter the array according to the page and the + $offset = $this->page * $this->limit; + + return $collection->slice($offset, $this->limit); + + } + + /** + * Define which accounts can be part of the source and destination transactions. + * + * @param Collection $accounts + * + * @return GroupCollectorInterface + */ + public function setAccounts(Collection $accounts): GroupCollectorInterface + { + if ($accounts->count() > 0) { + $accountIds = $accounts->pluck('id')->toArray(); + $this->query->where( + static function (EloquentBuilder $query) use ($accountIds) { + $query->whereIn('source.account_id', $accountIds); + $query->orWhereIn('destination.account_id', $accountIds); + } + ); + app('log')->debug(sprintf('GroupCollector: setAccounts: %s', implode(', ', $accountIds))); + $this->accountIds = $accountIds; + } + + return $this; + } + + /** + * Limit the search to a specific bill. + * + * @param Bill $bill + * + * @return GroupCollectorInterface + */ + public function setBill(Bill $bill): GroupCollectorInterface + { + $this->withBillInformation(); + $this->query->where('transaction_journals.bill_id', '=', $bill->id); + + return $this; + } + + /** + * Will include bill name + ID, if any. + * + * @return GroupCollectorInterface + */ + public function withBillInformation(): GroupCollectorInterface + { + if (false === $this->hasBillInformation) { + // join bill table + $this->query->leftJoin('bills', 'bills.id', '=', 'transaction_journals.bill_id'); + // add fields + $this->fields[] = 'bills.id as bill_id'; + $this->fields[] = 'bills.name as bill_name'; + $this->hasBillInformation = true; + } + + return $this; + } + + /** + * Limit the search to a specific budget. + * + * @param Budget $budget + * + * @return GroupCollectorInterface + */ + public function setBudget(Budget $budget): GroupCollectorInterface + { + $this->withBudgetInformation(); + $this->query->where('budgets.id', $budget->id); + + return $this; + } + + /** + * Will include budget ID + name, if any. + * + * @return GroupCollectorInterface + */ + public function withBudgetInformation(): GroupCollectorInterface + { + if (false === $this->hasBudgetInformation) { + // join link table + $this->query->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id'); + // join cat table + $this->query->leftJoin('budgets', 'budget_transaction_journal.budget_id', '=', 'budgets.id'); + // add fields + $this->fields[] = 'budgets.id as budget_id'; + $this->fields[] = 'budgets.name as budget_name'; + $this->hasBudgetInformation = true; + } + + return $this; + } + + /** + * Limit the search to a specific set of budgets. + * + * @param Collection $budgets + * + * @return GroupCollectorInterface + */ + public function setBudgets(Collection $budgets): GroupCollectorInterface + { + if ($budgets->count() > 0) { + $this->withBudgetInformation(); + $this->query->whereIn('budgets.id', $budgets->pluck('id')->toArray()); + } + + return $this; + } + + /** + * Limit the search to a specific category. + * + * @param Category $category + * + * @return GroupCollectorInterface + */ + public function setCategory(Category $category): GroupCollectorInterface + { + $this->withCategoryInformation(); + $this->query->where('categories.id', $category->id); + + return $this; + } + + /** + * Will include category ID + name, if any. + * + * @return GroupCollectorInterface + */ + public function withCategoryInformation(): GroupCollectorInterface + { + if (false === $this->hasCatInformation) { + // join link table + $this->query->leftJoin('category_transaction_journal', 'category_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id'); + // join cat table + $this->query->leftJoin('categories', 'category_transaction_journal.category_id', '=', 'categories.id'); + // add fields + $this->fields[] = 'categories.id as category_id'; + $this->fields[] = 'categories.name as category_name'; + $this->hasCatInformation = true; + } + + return $this; + } + + /** + * Limit results to a specific currency, either foreign or normal one. + * + * @param TransactionCurrency $currency + * + * @return GroupCollectorInterface + */ + public function setCurrency(TransactionCurrency $currency): GroupCollectorInterface + { + $this->query->where( + function (EloquentBuilder $q) use ($currency) { + $q->where('source.transaction_currency_id', $currency->id); + $q->orWhere('source.foreign_currency_id', $currency->id); + } + ); + + return $this; + } + + /** + * Limit the result to a set of specific journals. + * + * @param array $journalIds + * + * @return GroupCollectorInterface + */ + public function setJournalIds(array $journalIds): GroupCollectorInterface + { + if (count($journalIds) > 0) { + $this->query->whereIn('transaction_journals.id', $journalIds); + } + + return $this; + } + + /** + * Limit the number of returned entries. + * + * @param int $limit + * + * @return GroupCollectorInterface + */ + public function setLimit(int $limit): GroupCollectorInterface + { + $this->limit = $limit; + app('log')->debug(sprintf('GroupCollector: The limit is now %d', $limit)); + + return $this; + } + + /** + * Set the page to get. + * + * @param int $page + * + * @return GroupCollectorInterface + */ + public function setPage(int $page): GroupCollectorInterface + { + $page = 0 === $page ? 0 : $page - 1; + $this->page = $page; + app('log')->debug(sprintf('GroupCollector: page is now %d (is minus 1)', $page)); + + return $this; + } + + /** + * Set the start and end time of the results to return. + * + * @param Carbon $start + * @param Carbon $end + * + * @return GroupCollectorInterface + */ + public function setRange(Carbon $start, Carbon $end): GroupCollectorInterface + { + if ($end < $start) { + [$start, $end] = [$end, $start]; + } + $startStr = $start->format('Y-m-d H:i:s'); + $endStr = $end->format('Y-m-d H:i:s'); + + $this->query->where('transaction_journals.date', '>=', $startStr); + $this->query->where('transaction_journals.date', '<=', $endStr); + app('log')->debug(sprintf('GroupCollector range is now %s - %s (inclusive)', $startStr, $endStr)); + + return $this; + } + + /** + * Limit results to a specific tag. + * + * @param Tag $tag + * + * @return GroupCollectorInterface + */ + public function setTag(Tag $tag): GroupCollectorInterface + { + $this->withTagInformation(); + $this->query->where('tag_transaction_journal.tag_id', $tag->id); + + return $this; + } + + /** + * Limit the search to one specific transaction group. + * + * @param TransactionGroup $transactionGroup + * + * @return GroupCollectorInterface + */ + public function setTransactionGroup(TransactionGroup $transactionGroup): GroupCollectorInterface + { + $this->query->where('transaction_groups.id', $transactionGroup->id); + + return $this; + } + + /** + * Limit the included transaction types. + * + * @param array $types + * + * @return GroupCollectorInterface + */ + public function setTypes(array $types): GroupCollectorInterface + { + $this->query->whereIn('transaction_types.type', $types); + + return $this; + } + + /** + * Set the user object and start the query. + * + * @param User $user + * + * @return GroupCollectorInterface + */ + public function setUser(User $user): GroupCollectorInterface + { + $this->user = $user; + $this->startQuery(); + + return $this; + } + + /** + * Automatically include all stuff required to make API calls work. + * + * @return GroupCollectorInterface + */ + public function withAPIInformation(): GroupCollectorInterface + { + // include source + destination account name and type. + $this->withAccountInformation() + // include category ID + name (if any) + ->withCategoryInformation() + // include budget ID + name (if any) + ->withBudgetInformation() + // include bill ID + name (if any) + ->withBillInformation(); + + return $this; + } + + /** + * Will include the source and destination account names and types. + * + * @return GroupCollectorInterface + */ + public function withAccountInformation(): GroupCollectorInterface + { + if (false === $this->hasAccountInfo) { + // join source account table + $this->query->leftJoin('accounts as source_account', 'source_account.id', '=', 'source.account_id'); + // join source account type table + $this->query->leftJoin('account_types as source_account_type', 'source_account_type.id', '=', 'source_account.account_type_id'); + + // add source account fields: + $this->fields[] = 'source_account.name as source_account_name'; + $this->fields[] = 'source_account.iban as source_account_iban'; + $this->fields[] = 'source_account_type.type as source_account_type'; + + // same for dest + $this->query->leftJoin('accounts as dest_account', 'dest_account.id', '=', 'destination.account_id'); + $this->query->leftJoin('account_types as dest_account_type', 'dest_account_type.id', '=', 'dest_account.account_type_id'); + + // and add fields: + $this->fields[] = 'dest_account.name as destination_account_name'; + $this->fields[] = 'dest_account.iban as destination_account_iban'; + $this->fields[] = 'dest_account_type.type as destination_account_type'; + + + $this->hasAccountInfo = true; + } + + return $this; + } + + /** + * Limit the search to a specific bunch of categories. + * + * @param Collection $categories + * + * @return GroupCollectorInterface + */ + public function setCategories(Collection $categories): GroupCollectorInterface + { + if ($categories->count() > 0) { + $this->withCategoryInformation(); + $this->query->whereIn('categories.id', $categories->pluck('id')->toArray()); + } + + return $this; + } + + /** + * Limit results to a specific set of tags. + * + * @param Collection $tags + * + * @return GroupCollectorInterface + */ + public function setTags(Collection $tags): GroupCollectorInterface + { + $this->withTagInformation(); + $this->query->whereIn('tag_transaction_journal.tag_id', $tags->pluck('id')->toArray()); + + return $this; + } + + /** + * Limit results to a transactions without a budget.. + * + * @return GroupCollectorInterface + */ + public function withoutBudget(): GroupCollectorInterface + { + $this->withBudgetInformation(); + $this->query->where( + function (EloquentBuilder $q) { + $q->whereNull('budget_transaction_journal.budget_id'); + } + ); + + return $this; + } + + /** + * Limit results to a transactions without a category. + * + * @return GroupCollectorInterface + */ + public function withoutCategory(): GroupCollectorInterface + { + $this->withCategoryInformation(); + $this->query->where( + function (EloquentBuilder $q) { + $q->whereNull('category_transaction_journal.category_id'); + } + ); + + return $this; + } + + /** + * + */ + public function dumpQuery(): void + { + + echo $this->query->toSql(); + echo '
';
+        print_r($this->query->getBindings());
+        echo '
'; + } + + /** + * Return the sum of all journals. + * TODO ignores the currency. + * + * @return string + */ + public function getSum(): string + { + $journals = $this->getExtractedJournals(); + $sum = '0'; + /** @var array $journal */ + foreach ($journals as $journal) { + $amount = (string)$journal['amount']; + $sum = bcadd($sum, $amount); + } + + return $sum; + } + + /** + * Return the transaction journals without group information. Is useful in some instances. + * + * @return array + */ + public function getExtractedJournals(): array + { + $selection = $this->getGroups(); + $return = []; + /** @var array $group */ + foreach ($selection as $group) { + $count = count($group['transactions']); + foreach ($group['transactions'] as $journalId => $journal) { + $journal['group_title'] = $group['title']; + $journal['journals_in_group'] = $count; + $return[$journalId] = $journal; + } + } + + return $return; + } + + /** + * Search for words in descriptions. + * + * @param array $array + * + * @return GroupCollectorInterface + */ + public function setSearchWords(array $array): GroupCollectorInterface + { + $this->query->where( + function (EloquentBuilder $q) use ($array) { + $q->where( + function (EloquentBuilder $q1) use ($array) { + foreach ($array as $word) { + $keyword = sprintf('%%%s%%', $word); + $q1->where('transaction_journals.description', 'LIKE', $keyword); + } + } + ); + $q->orWhere( + function (EloquentBuilder $q2) use ($array) { + foreach ($array as $word) { + $keyword = sprintf('%%%s%%', $word); + $q2->where('transaction_groups.title', 'LIKE', $keyword); + } + } + ); + } + ); + + return $this; + } + + /** + * Limit the result to a specific transaction group. + * + * @param TransactionGroup $transactionGroup + * + * @return GroupCollectorInterface + */ + public function setGroup(TransactionGroup $transactionGroup): GroupCollectorInterface + { + $this->query->where('transaction_groups.id', $transactionGroup->id); + + return $this; + } + + /** + * Limit the search to a specific set of bills. + * + * @param Collection $bills + * + * @return GroupCollectorInterface + */ + public function setBills(Collection $bills): GroupCollectorInterface + { + $this->withBillInformation(); + $this->query->whereIn('transaction_journals.bill_id', $bills->pluck('id')->toArray()); + + return $this; + } + + /** + * Get transactions with a specific amount. + * + * @param string $amount + * + * @return GroupCollectorInterface + */ + public function amountIs(string $amount): GroupCollectorInterface + { + $this->query->where( + function (EloquentBuilder $q) use ($amount) { + $q->where('source.amount', app('steam')->negative($amount)); + } + ); + + return $this; + } + + /** + * Get transactions where the amount is less than. + * + * @param string $amount + * + * @return GroupCollectorInterface + */ + public function amountLess(string $amount): GroupCollectorInterface + { + $this->query->where( + function (EloquentBuilder $q) use ($amount) { + $q->where('destination.amount', '<', app('steam')->positive($amount)); + } + ); + + return $this; + } + + /** + * Get transactions where the amount is more than. + * + * @param string $amount + * + * @return GroupCollectorInterface + */ + public function amountMore(string $amount): GroupCollectorInterface + { + $this->query->where( + function (EloquentBuilder $q) use ($amount) { + $q->where('destination.amount', '>', app('steam')->positive($amount)); + } + ); + + return $this; + } + + /** + * Collect transactions before a specific date. + * + * @param Carbon $date + * + * @return GroupCollectorInterface + */ + public function setBefore(Carbon $date): GroupCollectorInterface + { + $beforeStr = $date->format('Y-m-d 00:00:00'); + $this->query->where('transaction_journals.date', '<=', $beforeStr); + Log::debug(sprintf('GroupCollector range is now before %s (inclusive)', $beforeStr)); + + return $this; + } + + /** + * Collect transactions after a specific date. + * + * @param Carbon $date + * + * @return GroupCollectorInterface + */ + public function setAfter(Carbon $date): GroupCollectorInterface + { + $afterStr = $date->format('Y-m-d 00:00:00'); + $this->query->where('transaction_journals.date', '>=', $afterStr); + Log::debug(sprintf('GroupCollector range is now after %s (inclusive)', $afterStr)); + + return $this; + } + + /** + * @return GroupCollectorInterface + */ + public function withTagInformation(): GroupCollectorInterface + { + $this->fields[] = 'tags.id as tag_id'; + $this->fields[] = 'tags.tag as tag_name'; + $this->fields[] = 'tags.date as tag_date'; + $this->fields[] = 'tags.description as tag_description'; + $this->fields[] = 'tags.latitude as tag_latitude'; + $this->fields[] = 'tags.longitude as tag_longitude'; + $this->fields[] = 'tags.zoomLevel as tag_zoom_level'; + + $this->joinTagTables(); + + return $this; + } + + /** + * @param Collection $collection + * + * @return Collection + */ + private function parseArray(Collection $collection): Collection + { + $groups = []; + /** @var TransactionGroup $augmentedGroup */ + foreach ($collection as $augmentedGroup) { + $groupId = $augmentedGroup->transaction_group_id; + if (!isset($groups[$groupId])) { + // make new array + $parsedGroup = $this->parseAugmentedGroup($augmentedGroup); + $groupArray = [ + 'id' => $augmentedGroup->transaction_group_id, + 'user_id' => $augmentedGroup->user_id, + 'title' => $augmentedGroup->transaction_group_title, + 'transaction_type' => $parsedGroup['transaction_type_type'], + 'count' => 1, + 'sums' => [], + 'transactions' => [], + ]; + $journalId = (int)$augmentedGroup->transaction_journal_id; + $groupArray['transactions'][$journalId] = $parsedGroup; + $groups[$groupId] = $groupArray; + continue; + } + // or parse the rest. + $journalId = (int)$augmentedGroup->transaction_journal_id; + $groups[$groupId]['count']++; + + if (isset($groups[$groupId]['transactions'][$journalId])) { + $groups[$groupId]['transactions'][$journalId] = + $this->mergeTags($groups[$groupId]['transactions'][$journalId], $augmentedGroup); + } + + if (!isset($groups[$groupId]['transactions'][$journalId])) { + $groups[$groupId]['transactions'][$journalId] = $this->parseAugmentedGroup($augmentedGroup); + } + + } + $groups = $this->parseSums($groups); + + return new Collection($groups); + } + + /** + * @param TransactionGroup $augmentedGroup + * + * @return array + */ + private function parseAugmentedGroup(TransactionGroup $augmentedGroup): array + { + $result = $augmentedGroup->toArray(); + $result['tags'] = []; + try { + $result['date'] = new Carbon($result['date']); + $result['created_at'] = new Carbon($result['created_at']); + $result['updated_at'] = new Carbon($result['updated_at']); + } catch (Exception $e) { + Log::error($e->getMessage()); + } + $result['reconciled'] = 1 === (int)$result['reconciled']; + if (isset($augmentedGroup['tag_id'])) { // assume the other fields are present as well. + $tagId = (int)$augmentedGroup['tag_id']; + $tagDate = null; + try { + $tagDate = Carbon::parse($augmentedGroup['tag_date']); + } catch (InvalidDateException $e) { + Log::debug(sprintf('Could not parse date: %s', $e->getMessage())); + } + + $result['tags'][$tagId] = [ + 'id' => (int)$result['tag_id'], + 'name' => $result['tag_name'], + 'date' => $tagDate, + 'description' => $result['tag_description'], + 'latitude' => $result['tag_latitude'], + 'longitude' => $result['tag_longitude'], + 'zoom_level' => $result['tag_zoom_level'], + ]; + } + + return $result; + } + + /** + * @param array $existingJournal + * @param TransactionGroup $newGroup + * @return array + */ + private function mergeTags(array $existingJournal, TransactionGroup $newGroup): array + { + $newArray = $newGroup->toArray(); + if (isset($newArray['tag_id'])) { // assume the other fields are present as well. + $tagId = (int)$newGroup['tag_id']; + + $tagDate = null; + try { + $tagDate = Carbon::parse($newArray['tag_date']); + } catch (InvalidDateException $e) { + Log::debug(sprintf('Could not parse date: %s', $e->getMessage())); + } + + $existingJournal['tags'][$tagId] = [ + 'id' => (int)$newArray['tag_id'], + 'name' => $newArray['tag_name'], + 'date' => $tagDate, + 'description' => $newArray['tag_description'], + 'latitude' => $newArray['tag_latitude'], + 'longitude' => $newArray['tag_longitude'], + 'zoom_level' => $newArray['tag_zoom_level'], + ]; + } + + return $existingJournal; + } + + /** + * @param array $groups + * + * @return array + */ + private function parseSums(array $groups): array + { + /** + * @var int $groudId + * @var array $group + */ + foreach ($groups as $groudId => $group) { + /** @var array $transaction */ + foreach ($group['transactions'] as $transaction) { + $currencyId = (int)$transaction['currency_id']; + + // set default: + if (!isset($groups[$groudId]['sums'][$currencyId])) { + $groups[$groudId]['sums'][$currencyId]['currency_id'] = $currencyId; + $groups[$groudId]['sums'][$currencyId]['currency_code'] = $transaction['currency_code']; + $groups[$groudId]['sums'][$currencyId]['currency_symbol'] = $transaction['currency_symbol']; + $groups[$groudId]['sums'][$currencyId]['currency_decimal_places'] = $transaction['currency_decimal_places']; + $groups[$groudId]['sums'][$currencyId]['amount'] = '0'; + } + $groups[$groudId]['sums'][$currencyId]['amount'] = bcadd($groups[$groudId]['sums'][$currencyId]['amount'], $transaction['amount'] ?? '0'); + + if (null !== $transaction['foreign_amount'] && null !== $transaction['foreign_currency_id']) { + $currencyId = (int)$transaction['foreign_currency_id']; + + // set default: + if (!isset($groups[$groudId]['sums'][$currencyId])) { + $groups[$groudId]['sums'][$currencyId]['currency_id'] = $currencyId; + $groups[$groudId]['sums'][$currencyId]['currency_code'] = $transaction['foreign_currency_code']; + $groups[$groudId]['sums'][$currencyId]['currency_symbol'] = $transaction['foreign_currency_symbol']; + $groups[$groudId]['sums'][$currencyId]['currency_decimal_places'] = $transaction['foreign_currency_decimal_places']; + $groups[$groudId]['sums'][$currencyId]['amount'] = '0'; + } + $groups[$groudId]['sums'][$currencyId]['amount'] = bcadd($groups[$groudId]['sums'][$currencyId]['amount'], $transaction['foreign_amount'] ?? '0'); + } + } + } + + return $groups; + } + + /** + * Join table to get tag information. + */ + private function joinTagTables(): void + { + if (false === $this->hasJoinedTagTables) { + // join some extra tables: + $this->hasJoinedTagTables = true; + $this->query->leftJoin('tag_transaction_journal', 'tag_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id'); + $this->query->leftJoin('tags', 'tag_transaction_journal.tag_id', '=', 'tags.id'); + } + } + + /** + * Build the query. + */ + private function startQuery(): void + { + app('log')->debug('GroupCollector::startQuery'); + $this->query = $this->user + ->transactionGroups() + ->leftJoin('transaction_journals', 'transaction_journals.transaction_group_id', 'transaction_groups.id') + // join source transaction. + ->leftJoin( + 'transactions as source', function (JoinClause $join) { + $join->on('source.transaction_journal_id', '=', 'transaction_journals.id') + ->where('source.amount', '<', 0); + } + ) + // join destination transaction + ->leftJoin( + 'transactions as destination', function (JoinClause $join) { + $join->on('destination.transaction_journal_id', '=', 'transaction_journals.id') + ->where('destination.amount', '>', 0); + } + ) + // left join transaction type. + ->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id') + ->leftJoin('transaction_currencies as currency', 'currency.id', '=', 'source.transaction_currency_id') + ->leftJoin('transaction_currencies as foreign_currency', 'foreign_currency.id', '=', 'source.foreign_currency_id') + ->whereNull('transaction_groups.deleted_at') + ->whereNull('transaction_journals.deleted_at') + ->whereNull('source.deleted_at') + ->whereNull('destination.deleted_at') + ->orderBy('transaction_journals.date', 'DESC') + ->orderBy('transaction_journals.order', 'ASC') + ->orderBy('transaction_journals.id', 'DESC') + ->orderBy('transaction_journals.description', 'DESC') + ->orderBy('source.amount', 'DESC'); + } +} \ No newline at end of file diff --git a/app/Helpers/Collector/GroupCollectorInterface.php b/app/Helpers/Collector/GroupCollectorInterface.php new file mode 100644 index 0000000000..2880576c3c --- /dev/null +++ b/app/Helpers/Collector/GroupCollectorInterface.php @@ -0,0 +1,344 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Helpers\Collector; + +use Carbon\Carbon; +use FireflyIII\Models\Bill; +use FireflyIII\Models\Budget; +use FireflyIII\Models\Category; +use FireflyIII\Models\Tag; +use FireflyIII\Models\TransactionCurrency; +use FireflyIII\Models\TransactionGroup; +use FireflyIII\User; +use Illuminate\Pagination\LengthAwarePaginator; +use Illuminate\Support\Collection; + +/** + * Interface GroupCollectorInterface + */ +interface GroupCollectorInterface +{ + /** + * Return the transaction journals without group information. Is useful in some instances. + * + * @return array + */ + public function getExtractedJournals(): array; + + /** + * Return the sum of all journals. + * + * @return string + */ + public function getSum(): string; + + /** + * Add tag info. + * + * @return GroupCollectorInterface + */ + public function withTagInformation(): GroupCollectorInterface; + + /** + * Return the groups. + * + * @return Collection + */ + public function getGroups(): Collection; + + /** + * Same as getGroups but everything is in a paginator. + * + * @return LengthAwarePaginator + */ + public function getPaginatedGroups(): LengthAwarePaginator; + + /** + * Define which accounts can be part of the source and destination transactions. + * + * @param Collection $accounts + * + * @return GroupCollectorInterface + */ + public function setAccounts(Collection $accounts): GroupCollectorInterface; + + /** + * Limit the search to a specific bill. + * + * @param Bill $bill + * + * @return GroupCollectorInterface + */ + public function setBill(Bill $bill): GroupCollectorInterface; + + /** + * Limit the search to a specific set of bills. + * + * @param Collection $bills + * + * @return GroupCollectorInterface + */ + public function setBills(Collection $bills): GroupCollectorInterface; + + /** + * Get transactions with a specific amount. + * + * @param string $amount + * + * @return GroupCollectorInterface + */ + public function amountIs(string $amount): GroupCollectorInterface; + + /** + * Get transactions where the amount is less than. + * + * @param string $amount + * + * @return GroupCollectorInterface + */ + public function amountLess(string $amount): GroupCollectorInterface; + + /** + * Get transactions where the amount is more than. + * + * @param string $amount + * + * @return GroupCollectorInterface + */ + public function amountMore(string $amount): GroupCollectorInterface; + + /** + * Limit the search to a specific budget. + * + * @param Budget $budget + * + * @return GroupCollectorInterface + */ + public function setBudget(Budget $budget): GroupCollectorInterface; + + /** + * Limit the search to a specific set of budgets. + * + * @param Collection $budgets + * + * @return GroupCollectorInterface + */ + public function setBudgets(Collection $budgets): GroupCollectorInterface; + + /** + * Limit the search to a specific category. + * + * @param Category $category + * + * @return GroupCollectorInterface + */ + public function setCategory(Category $category): GroupCollectorInterface; + + + /** + * Limit results to a specific currency, either foreign or normal one. + * + * @param TransactionCurrency $currency + * + * @return GroupCollectorInterface + */ + public function setCurrency(TransactionCurrency $currency): GroupCollectorInterface; + + /** + * Limit the result to a set of specific transaction journals. + * + * @param array $journalIds + * + * @return GroupCollectorInterface + */ + public function setJournalIds(array $journalIds): GroupCollectorInterface; + + /** + * Limit the result to a specific transaction group. + * + * @param TransactionGroup $transactionGroup + * + * @return GroupCollectorInterface + */ + public function setGroup(TransactionGroup $transactionGroup): GroupCollectorInterface; + + /** + * Search for words in descriptions. + * + * @param array $array + * + * @return GroupCollectorInterface + */ + public function setSearchWords(array $array): GroupCollectorInterface; + + /** + * Limit the number of returned entries. + * + * @param int $limit + * + * @return GroupCollectorInterface + */ + public function setLimit(int $limit): GroupCollectorInterface; + + /** + * Set the page to get. + * + * @param int $page + * + * @return GroupCollectorInterface + */ + public function setPage(int $page): GroupCollectorInterface; + + /** + * Set the start and end time of the results to return. + * + * @param Carbon $start + * @param Carbon $end + * + * @return GroupCollectorInterface + */ + public function setRange(Carbon $start, Carbon $end): GroupCollectorInterface; + + /** + * Limit results to a specific tag. + * + * @param Tag $tag + * + * @return GroupCollectorInterface + */ + public function setTag(Tag $tag): GroupCollectorInterface; + + /** + * Limit results to a specific set of tags. + * + * @param Collection $tags + * + * @return GroupCollectorInterface + */ + public function setTags(Collection $tags): GroupCollectorInterface; + + /** + * Limit results to a transactions without a budget. + * + * @return GroupCollectorInterface + */ + public function withoutBudget(): GroupCollectorInterface; + + /** + * Limit results to a transactions without a category. + * + * @return GroupCollectorInterface + */ + public function withoutCategory(): GroupCollectorInterface; + + /** + * Limit the search to one specific transaction group. + * + * @param TransactionGroup $transactionGroup + * + * @return GroupCollectorInterface + */ + public function setTransactionGroup(TransactionGroup $transactionGroup): GroupCollectorInterface; + + /** + * Limit the included transaction types. + * + * @param array $types + * + * @return GroupCollectorInterface + */ + public function setTypes(array $types): GroupCollectorInterface; + + /** + * Set the user object and start the query. + * + * @param User $user + * + * @return GroupCollectorInterface + */ + public function setUser(User $user): GroupCollectorInterface; + + /** + * Automatically include all stuff required to make API calls work. + * + * @return GroupCollectorInterface + */ + public function withAPIInformation(): GroupCollectorInterface; + + /** + * Will include the source and destination account names and types. + * + * @return GroupCollectorInterface + */ + public function withAccountInformation(): GroupCollectorInterface; + + /** + * Limit the search to a specific bunch of categories. + * + * @param Collection $categories + * + * @return GroupCollectorInterface + */ + public function setCategories(Collection $categories): GroupCollectorInterface; + + /** + * Collect transactions before a specific date. + * + * @param Carbon $date + * + * @return GroupCollectorInterface + */ + public function setBefore(Carbon $date): GroupCollectorInterface; + + /** + * Collect transactions after a specific date. + * + * @param Carbon $date + * + * @return GroupCollectorInterface + */ + public function setAfter(Carbon $date): GroupCollectorInterface; + + /** + * Include bill name + ID. + * + * @return GroupCollectorInterface + */ + public function withBillInformation(): GroupCollectorInterface; + + /** + * Will include budget ID + name, if any. + * + * @return GroupCollectorInterface + */ + public function withBudgetInformation(): GroupCollectorInterface; + + /** + * Will include category ID + name, if any. + * + * @return GroupCollectorInterface + */ + public function withCategoryInformation(): GroupCollectorInterface; + +} \ No newline at end of file diff --git a/app/Helpers/Collector/TransactionCollector.php b/app/Helpers/Collector/TransactionCollector.php deleted file mode 100644 index eabf55e30f..0000000000 --- a/app/Helpers/Collector/TransactionCollector.php +++ /dev/null @@ -1,961 +0,0 @@ -. - */ - -declare(strict_types=1); - -namespace FireflyIII\Helpers\Collector; - - -use Carbon\Carbon; -use DB; -use FireflyIII\Exceptions\FireflyException; -use FireflyIII\Helpers\Filter\CountAttachmentsFilter; -use FireflyIII\Helpers\Filter\DoubleTransactionFilter; -use FireflyIII\Helpers\Filter\FilterInterface; -use FireflyIII\Helpers\Filter\InternalTransferFilter; -use FireflyIII\Helpers\Filter\NegativeAmountFilter; -use FireflyIII\Helpers\Filter\OpposingAccountFilter; -use FireflyIII\Helpers\Filter\PositiveAmountFilter; -use FireflyIII\Helpers\Filter\SplitIndicatorFilter; -use FireflyIII\Helpers\Filter\TransactionViewFilter; -use FireflyIII\Helpers\Filter\TransferFilter; -use FireflyIII\Models\AccountType; -use FireflyIII\Models\Budget; -use FireflyIII\Models\Category; -use FireflyIII\Models\Tag; -use FireflyIII\Models\Transaction; -use FireflyIII\Models\TransactionCurrency; -use FireflyIII\Repositories\Account\AccountRepositoryInterface; -use FireflyIII\Support\CacheProperties; -use FireflyIII\User; -use Illuminate\Database\Eloquent\Builder as EloquentBuilder; -use Illuminate\Database\Query\JoinClause; -use Illuminate\Pagination\LengthAwarePaginator; -use Illuminate\Support\Collection; -use Log; - -/** - * Class TransactionCollector - * - * @codeCoverageIgnore - */ -class TransactionCollector implements TransactionCollectorInterface -{ - /** @var array */ - private $accountIds = []; - /** @var int */ - private $count = 0; - /** @var array */ - private $fields - = [ - 'transaction_journals.id as journal_id', - 'transaction_journals.description', - 'transaction_journals.date', - 'transaction_journals.encrypted', - 'transaction_journals.created_at', - 'transaction_journals.updated_at', - 'transaction_types.type as transaction_type_type', - 'transaction_journals.bill_id', - 'transaction_journals.updated_at', - 'bills.name as bill_name', - 'bills.name_encrypted as bill_name_encrypted', - - 'transactions.id as id', - 'transactions.description as transaction_description', - 'transactions.account_id', - 'transactions.reconciled', - 'transactions.identifier', - 'transactions.transaction_journal_id', - 'transactions.amount as transaction_amount', - 'transactions.transaction_currency_id as transaction_currency_id', - - 'transaction_currencies.name as transaction_currency_name', - 'transaction_currencies.code as transaction_currency_code', - 'transaction_currencies.symbol as transaction_currency_symbol', - 'transaction_currencies.decimal_places as transaction_currency_dp', - - 'transactions.foreign_amount as transaction_foreign_amount', - 'transactions.foreign_currency_id as foreign_currency_id', - - 'foreign_currencies.code as foreign_currency_code', - 'foreign_currencies.symbol as foreign_currency_symbol', - 'foreign_currencies.decimal_places as foreign_currency_dp', - - 'accounts.name as account_name', - 'accounts.encrypted as account_encrypted', - 'accounts.iban as account_iban', - 'account_types.type as account_type', - ]; - /** @var array */ - private $filters = [InternalTransferFilter::class]; - /** @var bool */ - private $ignoreCache = false; - /** @var bool */ - private $joinedBudget = false; - /** @var bool */ - private $joinedCategory = false; - /** @var bool */ - private $joinedOpposing = false; - /** @var bool */ - private $joinedTag = false; - /** @var int */ - private $limit; - /** @var int */ - private $offset; - /** @var int */ - private $page = 1; - /** @var EloquentBuilder */ - private $query; - /** @var bool */ - private $run = false; - /** @var User */ - private $user; - - /** - * Constructor. - */ - public function __construct() - { - if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); - } - } - - /** - * @param string $filter - * - * @return TransactionCollectorInterface - */ - public function addFilter(string $filter): TransactionCollectorInterface - { - $interfaces = class_implements($filter); - if (\in_array(FilterInterface::class, $interfaces, true) && !\in_array($filter, $this->filters, true)) { - Log::debug(sprintf('Enabled filter %s', $filter)); - $this->filters[] = $filter; - } - - return $this; - } - - /** - * @param string $amount - * - * @return TransactionCollectorInterface - */ - public function amountIs(string $amount): TransactionCollectorInterface - { - $this->query->where( - function (EloquentBuilder $q) use ($amount) { - $q->where('transactions.amount', $amount); - $q->orWhere('transactions.amount', bcmul($amount, '-1')); - } - ); - - return $this; - } - - /** - * @param string $amount - * - * @return TransactionCollectorInterface - */ - public function amountLess(string $amount): TransactionCollectorInterface - { - $this->query->where( - function (EloquentBuilder $q1) use ($amount) { - $q1->where( - function (EloquentBuilder $q2) use ($amount) { - // amount < 0 and .amount > -$amount - $invertedAmount = bcmul($amount, '-1'); - $q2->where('transactions.amount', '<', 0)->where('transactions.amount', '>', $invertedAmount); - } - ) - ->orWhere( - function (EloquentBuilder $q3) use ($amount) { - // amount > 0 and .amount < $amount - $q3->where('transactions.amount', '>', 0)->where('transactions.amount', '<', $amount); - } - ); - } - ); - - return $this; - } - - /** - * @param string $amount - * - * @return TransactionCollectorInterface - */ - public function amountMore(string $amount): TransactionCollectorInterface - { - $this->query->where( - function (EloquentBuilder $q1) use ($amount) { - $q1->where( - function (EloquentBuilder $q2) use ($amount) { - // amount < 0 and .amount < -$amount - $invertedAmount = bcmul($amount, '-1'); - $q2->where('transactions.amount', '<', 0)->where('transactions.amount', '<', $invertedAmount); - } - ) - ->orWhere( - function (EloquentBuilder $q3) use ($amount) { - // amount > 0 and .amount > $amount - $q3->where('transactions.amount', '>', 0)->where('transactions.amount', '>', $amount); - } - ); - } - ); - - return $this; - } - - /** - * @return int - * - * @throws FireflyException - */ - public function count(): int - { - if (true === $this->run) { - throw new FireflyException('Cannot count after run in TransactionCollector.'); - } - - $countQuery = clone $this->query; - - // dont need some fields: - $countQuery->getQuery()->limit = null; - $countQuery->getQuery()->offset = null; - $countQuery->getQuery()->unionLimit = null; - $countQuery->getQuery()->groups = null; - $countQuery->getQuery()->orders = null; - $countQuery->groupBy('accounts.user_id'); - $this->count = (int)$countQuery->count(); - - return $this->count; - } - - /** - * @return LengthAwarePaginator - * @throws FireflyException - */ - public function getPaginatedTransactions(): LengthAwarePaginator - { - if (true === $this->run) { - throw new FireflyException('Cannot getPaginatedTransactions after run in TransactionCollector.'); - } - $this->count(); - $set = $this->getTransactions(); - $journals = new LengthAwarePaginator($set, $this->count, $this->limit, $this->page); - - return $journals; - } - - /** - * @return EloquentBuilder - */ - public function getQuery(): EloquentBuilder - { - return $this->query; - } - - /** - * @return Collection - */ - public function getTransactions(): Collection - { - $this->run = true; - - // find query set in cache. - $hash = hash('sha256', $this->query->toSql() . serialize($this->query->getBindings())); - $key = 'query-' . substr($hash, -8); - $cache = new CacheProperties; - $cache->addProperty($key); - foreach ($this->filters as $filter) { - $cache->addProperty((string)$filter); - } - if (false === $this->ignoreCache && $cache->has()) { - Log::debug(sprintf('Return cache of query with ID "%s".', $key)); - - return $cache->get(); // @codeCoverageIgnore - - } - /** @var Collection $set */ - $set = $this->query->get(array_values($this->fields)); - - // run all filters: - $set = $this->filter($set); - - // loop for date. - $set->each( - function (Transaction $transaction) { - $transaction->date = new Carbon($transaction->date); - } - - ); - Log::debug(sprintf('Cached query with ID "%s".', $key)); - $cache->store($set); - - return $set; - } - - /** - * @return TransactionCollectorInterface - */ - public function ignoreCache(): TransactionCollectorInterface - { - $this->ignoreCache = true; - - return $this; - } - - /** - * @param string $filter - * - * @return TransactionCollectorInterface - */ - public function removeFilter(string $filter): TransactionCollectorInterface - { - $key = array_search($filter, $this->filters, true); - if (!(false === $key)) { - Log::debug(sprintf('Removed filter %s', $filter)); - unset($this->filters[$key]); - } - - return $this; - } - - /** - * @param Collection $accounts - * - * @return TransactionCollectorInterface - */ - public function setAccounts(Collection $accounts): TransactionCollectorInterface - { - if ($accounts->count() > 0) { - $accountIds = $accounts->pluck('id')->toArray(); - $this->query->whereIn('transactions.account_id', $accountIds); - Log::debug(sprintf('setAccounts: %s', implode(', ', $accountIds))); - $this->accountIds = $accountIds; - } - - if ($accounts->count() > 1) { - $this->addFilter(TransferFilter::class); - } - - return $this; - } - - /** - * @param Carbon $after - * - * @return TransactionCollectorInterface - */ - public function setAfter(Carbon $after): TransactionCollectorInterface - { - $afterStr = $after->format('Y-m-d 00:00:00'); - $this->query->where('transaction_journals.date', '>=', $afterStr); - Log::debug(sprintf('TransactionCollector range is now after %s (inclusive)', $afterStr)); - - return $this; - } - - /** - * @return TransactionCollectorInterface - */ - public function setAllAssetAccounts(): TransactionCollectorInterface - { - /** @var AccountRepositoryInterface $repository */ - $repository = app(AccountRepositoryInterface::class); - $repository->setUser($this->user); - $accounts = $repository->getAccountsByType([AccountType::ASSET, AccountType::DEFAULT]); - if ($accounts->count() > 0) { - $accountIds = $accounts->pluck('id')->toArray(); - $this->query->whereIn('transactions.account_id', $accountIds); - $this->accountIds = $accountIds; - } - - if ($accounts->count() > 1) { - $this->addFilter(TransferFilter::class); - } - - return $this; - } - - /** - * @param Carbon $before - * - * @return TransactionCollectorInterface - */ - public function setBefore(Carbon $before): TransactionCollectorInterface - { - $beforeStr = $before->format('Y-m-d 00:00:00'); - $this->query->where('transaction_journals.date', '<=', $beforeStr); - Log::debug(sprintf('TransactionCollector range is now before %s (inclusive)', $beforeStr)); - - return $this; - } - - /** - * @param Collection $bills - * - * @return TransactionCollectorInterface - */ - public function setBills(Collection $bills): TransactionCollectorInterface - { - if ($bills->count() > 0) { - $billIds = $bills->pluck('id')->toArray(); - $this->query->whereIn('transaction_journals.bill_id', $billIds); - } - - return $this; - } - - /** - * @param Budget $budget - * - * @return TransactionCollectorInterface - */ - public function setBudget(Budget $budget): TransactionCollectorInterface - { - $this->joinBudgetTables(); - - $this->query->where( - function (EloquentBuilder $q) use ($budget) { - $q->where('budget_transaction.budget_id', $budget->id); - $q->orWhere('budget_transaction_journal.budget_id', $budget->id); - } - ); - - return $this; - } - - /** - * @param Collection $budgets - * - * @return TransactionCollectorInterface - */ - public function setBudgets(Collection $budgets): TransactionCollectorInterface - { - $budgetIds = $budgets->pluck('id')->toArray(); - if (0 !== \count($budgetIds)) { - $this->joinBudgetTables(); - Log::debug('Journal collector will filter for budgets', $budgetIds); - - $this->query->where( - function (EloquentBuilder $q) use ($budgetIds) { - $q->whereIn('budget_transaction.budget_id', $budgetIds); - $q->orWhereIn('budget_transaction_journal.budget_id', $budgetIds); - } - ); - } - - return $this; - } - - /** - * @param Collection $categories - * - * @return TransactionCollectorInterface - */ - public function setCategories(Collection $categories): TransactionCollectorInterface - { - $categoryIds = $categories->pluck('id')->toArray(); - if (0 !== \count($categoryIds)) { - $this->joinCategoryTables(); - - $this->query->where( - function (EloquentBuilder $q) use ($categoryIds) { - $q->whereIn('category_transaction.category_id', $categoryIds); - $q->orWhereIn('category_transaction_journal.category_id', $categoryIds); - } - ); - } - - return $this; - } - - /** - * @param Category $category - * - * @return TransactionCollectorInterface - */ - public function setCategory(Category $category): TransactionCollectorInterface - { - $this->joinCategoryTables(); - - $this->query->where( - function (EloquentBuilder $q) use ($category) { - $q->where('category_transaction.category_id', $category->id); - $q->orWhere('category_transaction_journal.category_id', $category->id); - } - ); - - return $this; - } - - /** - * Set the required currency (local or foreign) - * - * @param TransactionCurrency $currency - * - * @return TransactionCollectorInterface - */ - public function setCurrency(TransactionCurrency $currency): TransactionCollectorInterface - { - $this->query->where( - function (EloquentBuilder $builder) use ($currency) { - $builder->where('transactions.transaction_currency_id', $currency->id); - $builder->orWhere('transactions.foreign_currency_id', $currency->id); - } - ); - - return $this; - } - - /** - * @param array $journalIds - * - * @return TransactionCollectorInterface - */ - public function setJournalIds(array $journalIds): TransactionCollectorInterface - { - $this->query->where( - function (EloquentBuilder $q) use ($journalIds) { - $q->whereIn('transaction_journals.id', $journalIds); - } - ); - - return $this; - } - - /** - * @param Collection $journals - * - * @return TransactionCollectorInterface - */ - public function setJournals(Collection $journals): TransactionCollectorInterface - { - $ids = $journals->pluck('id')->toArray(); - $this->query->where( - function (EloquentBuilder $q) use ($ids) { - $q->whereIn('transaction_journals.id', $ids); - } - ); - - return $this; - } - - /** - * @param int $limit - * - * @return TransactionCollectorInterface - */ - public function setLimit(int $limit): TransactionCollectorInterface - { - $this->limit = $limit; - $this->query->limit($limit); - Log::debug(sprintf('Set limit to %d', $limit)); - - return $this; - } - - /** - * @param int $offset - * - * @return TransactionCollectorInterface - */ - public function setOffset(int $offset): TransactionCollectorInterface - { - $this->offset = $offset; - - return $this; - } - - /** - * @param Collection $accounts - * - * @return TransactionCollectorInterface - */ - public function setOpposingAccounts(Collection $accounts): TransactionCollectorInterface - { - $this->withOpposingAccount(); - - $this->query->whereIn('opposing.account_id', $accounts->pluck('id')->toArray()); - - return $this; - } - - /** - * @param int $page - * - * @return TransactionCollectorInterface - */ - public function setPage(int $page): TransactionCollectorInterface - { - if ($page < 1) { - $page = 1; - } - - $this->page = $page; - - if ($page > 0) { - --$page; - } - Log::debug(sprintf('Page is %d', $page)); - - if (null !== $this->limit) { - $offset = ($this->limit * $page); - $this->offset = $offset; - $this->query->skip($offset); - Log::debug(sprintf('Changed offset to %d', $offset)); - } - - return $this; - } - - /** - * @param Carbon $start - * @param Carbon $end - * - * @return TransactionCollectorInterface - */ - public function setRange(Carbon $start, Carbon $end): TransactionCollectorInterface - { - if ($start <= $end) { - $startStr = $start->format('Y-m-d 00:00:00'); - $endStr = $end->format('Y-m-d 23:59:59'); - $this->query->where('transaction_journals.date', '>=', $startStr); - $this->query->where('transaction_journals.date', '<=', $endStr); - Log::debug(sprintf('TransactionCollector range is now %s - %s (inclusive)', $startStr, $endStr)); - } - - return $this; - } - - /** - * Search for words in descriptions. - * - * @param array $array - * - * @return TransactionCollectorInterface - */ - public function setSearchWords(array $array): TransactionCollectorInterface - { - // 'transaction_journals.description', - $this->query->where( - function (EloquentBuilder $q) use ($array) { - $q->where( - function (EloquentBuilder $q1) use ($array) { - foreach ($array as $word) { - $keyword = sprintf('%%%s%%', $word); - $q1->where('transaction_journals.description', 'LIKE', $keyword); - } - } - ); - $q->orWhere( - function (EloquentBuilder $q2) use ($array) { - foreach ($array as $word) { - $keyword = sprintf('%%%s%%', $word); - $q2->where('transactions.description', 'LIKE', $keyword); - } - } - ); - } - ); - - return $this; - } - - /** - * @param Tag $tag - * - * @return TransactionCollectorInterface - */ - public function setTag(Tag $tag): TransactionCollectorInterface - { - $this->joinTagTables(); - $this->query->where('tag_transaction_journal.tag_id', $tag->id); - - return $this; - } - - /** - * @param Collection $tags - * - * @return TransactionCollectorInterface - */ - public function setTags(Collection $tags): TransactionCollectorInterface - { - $this->joinTagTables(); - $tagIds = $tags->pluck('id')->toArray(); - $this->query->whereIn('tag_transaction_journal.tag_id', $tagIds); - - return $this; - } - - /** - * @param array $types - * - * @return TransactionCollectorInterface - */ - public function setTypes(array $types): TransactionCollectorInterface - { - if (\count($types) > 0) { - Log::debug('Set query collector types', $types); - $this->query->whereIn('transaction_types.type', $types); - } - - return $this; - } - - /** - * @param User $user - */ - public function setUser(User $user) - { - Log::debug(sprintf('Journal collector now collecting for user #%d', $user->id)); - $this->user = $user; - $this->startQuery(); - } - - /** - * - */ - public function startQuery(): void - { - Log::debug('TransactionCollector::startQuery'); - /** @var EloquentBuilder $query */ - $query = Transaction::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') - ->leftJoin('transaction_types', 'transaction_types.id', 'transaction_journals.transaction_type_id') - ->leftJoin('bills', 'bills.id', 'transaction_journals.bill_id') - ->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id') - ->leftJoin('account_types', 'accounts.account_type_id', 'account_types.id') - ->leftJoin('transaction_currencies', 'transaction_currencies.id', 'transactions.transaction_currency_id') - ->leftJoin('transaction_currencies as foreign_currencies', 'foreign_currencies.id', 'transactions.foreign_currency_id') - ->whereNull('transactions.deleted_at') - ->whereNull('transaction_journals.deleted_at') - ->where('transaction_journals.user_id', $this->user->id) - ->orderBy('transaction_journals.date', 'DESC') - ->orderBy('transaction_journals.order', 'ASC') - ->orderBy('transaction_journals.id', 'DESC') - ->orderBy('transaction_journals.description', 'DESC') - ->orderBy('transactions.identifier', 'ASC') - ->orderBy('transactions.amount', 'DESC'); - - $this->query = $query; - } - - /** - * @return TransactionCollectorInterface - */ - public function withBudgetInformation(): TransactionCollectorInterface - { - $this->joinBudgetTables(); - - return $this; - } - - /** - * @return TransactionCollectorInterface - */ - public function withCategoryInformation(): TransactionCollectorInterface - { - $this->joinCategoryTables(); - - return $this; - } - - /** - * @return TransactionCollectorInterface - */ - public function withOpposingAccount(): TransactionCollectorInterface - { - $this->joinOpposingTables(); - - return $this; - } - - /** - * @return TransactionCollectorInterface - */ - public function withoutBudget(): TransactionCollectorInterface - { - $this->joinBudgetTables(); - - $this->query->where( - function (EloquentBuilder $q) { - $q->whereNull('budget_transaction.budget_id'); - $q->whereNull('budget_transaction_journal.budget_id'); - } - ); - - return $this; - } - - /** - * @return TransactionCollectorInterface - */ - public function withoutCategory(): TransactionCollectorInterface - { - $this->joinCategoryTables(); - - $this->query->where( - function (EloquentBuilder $q) { - $q->whereNull('category_transaction.category_id'); - $q->whereNull('category_transaction_journal.category_id'); - } - ); - - return $this; - } - - /** - * @param Collection $set - * - * @return Collection - */ - private function filter(Collection $set): Collection - { - // create all possible filters: - $filters = [ - InternalTransferFilter::class => new InternalTransferFilter($this->accountIds), - OpposingAccountFilter::class => new OpposingAccountFilter($this->accountIds), - TransferFilter::class => new TransferFilter, - PositiveAmountFilter::class => new PositiveAmountFilter, - NegativeAmountFilter::class => new NegativeAmountFilter, - SplitIndicatorFilter::class => new SplitIndicatorFilter, - CountAttachmentsFilter::class => new CountAttachmentsFilter, - TransactionViewFilter::class => new TransactionViewFilter, - DoubleTransactionFilter::class => new DoubleTransactionFilter, - ]; - Log::debug(sprintf('Will run %d filters on the set.', \count($this->filters))); - foreach ($this->filters as $enabled) { - if (isset($filters[$enabled])) { - Log::debug(sprintf('Before filter %s: %d', $enabled, $set->count())); - /** @var Collection $set */ - $set = $filters[$enabled]->filter($set); - Log::debug(sprintf('After filter %s: %d', $enabled, $set->count())); - } - } - - return $set; - } - - /** - * - */ - private function joinBudgetTables(): void - { - if (!$this->joinedBudget) { - // join some extra tables: - $this->joinedBudget = true; - $this->query->leftJoin('budget_transaction_journal', 'budget_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id'); - $this->query->leftJoin('budgets as transaction_journal_budgets', 'transaction_journal_budgets.id', '=', 'budget_transaction_journal.budget_id'); - $this->query->leftJoin('budget_transaction', 'budget_transaction.transaction_id', '=', 'transactions.id'); - $this->query->leftJoin('budgets as transaction_budgets', 'transaction_budgets.id', '=', 'budget_transaction.budget_id'); - $this->query->whereNull('transaction_journal_budgets.deleted_at'); - $this->query->whereNull('transaction_budgets.deleted_at'); - - $this->fields[] = 'budget_transaction_journal.budget_id as transaction_journal_budget_id'; - $this->fields[] = 'transaction_journal_budgets.encrypted as transaction_journal_budget_encrypted'; - $this->fields[] = 'transaction_journal_budgets.name as transaction_journal_budget_name'; - - $this->fields[] = 'budget_transaction.budget_id as transaction_budget_id'; - $this->fields[] = 'transaction_budgets.encrypted as transaction_budget_encrypted'; - $this->fields[] = 'transaction_budgets.name as transaction_budget_name'; - } - } - - /** - * - */ - private function joinCategoryTables(): void - { - if (!$this->joinedCategory) { - // join some extra tables: - $this->joinedCategory = true; - $this->query->leftJoin('category_transaction_journal', 'category_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id'); - $this->query->leftJoin( - 'categories as transaction_journal_categories', - 'transaction_journal_categories.id', - '=', - 'category_transaction_journal.category_id' - ); - - $this->query->leftJoin('category_transaction', 'category_transaction.transaction_id', '=', 'transactions.id'); - $this->query->leftJoin('categories as transaction_categories', 'transaction_categories.id', '=', 'category_transaction.category_id'); - $this->query->whereNull('transaction_journal_categories.deleted_at'); - $this->query->whereNull('transaction_categories.deleted_at'); - $this->fields[] = 'category_transaction_journal.category_id as transaction_journal_category_id'; - $this->fields[] = 'transaction_journal_categories.encrypted as transaction_journal_category_encrypted'; - $this->fields[] = 'transaction_journal_categories.name as transaction_journal_category_name'; - - $this->fields[] = 'category_transaction.category_id as transaction_category_id'; - $this->fields[] = 'transaction_categories.encrypted as transaction_category_encrypted'; - $this->fields[] = 'transaction_categories.name as transaction_category_name'; - } - } - - /** - * - */ - private function joinOpposingTables(): void - { - if (!$this->joinedOpposing) { - Log::debug('joinedOpposing is false'); - // join opposing transaction (hard): - $this->query->leftJoin( - 'transactions as opposing', - function (JoinClause $join) { - $join->on('opposing.transaction_journal_id', '=', 'transactions.transaction_journal_id') - ->where('opposing.identifier', '=', DB::raw('transactions.identifier')) - ->where('opposing.amount', '=', DB::raw('transactions.amount * -1')); - } - ); - $this->query->leftJoin('accounts as opposing_accounts', 'opposing.account_id', '=', 'opposing_accounts.id'); - $this->query->leftJoin('account_types as opposing_account_types', 'opposing_accounts.account_type_id', '=', 'opposing_account_types.id'); - $this->query->whereNull('opposing.deleted_at'); - - $this->fields[] = 'opposing.id as opposing_id'; - $this->fields[] = 'opposing.account_id as opposing_account_id'; - $this->fields[] = 'opposing_accounts.name as opposing_account_name'; - $this->fields[] = 'opposing_accounts.encrypted as opposing_account_encrypted'; - $this->fields[] = 'opposing_accounts.iban as opposing_account_iban'; - - $this->fields[] = 'opposing_account_types.type as opposing_account_type'; - $this->joinedOpposing = true; - Log::debug('joinedOpposing is now true!'); - } - } - - /** - * - */ - private function joinTagTables(): void - { - if (!$this->joinedTag) { - // join some extra tables: - $this->joinedTag = true; - $this->query->leftJoin('tag_transaction_journal', 'tag_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id'); - } - } -} diff --git a/app/Helpers/Collector/TransactionCollectorInterface.php b/app/Helpers/Collector/TransactionCollectorInterface.php deleted file mode 100644 index f5309cff16..0000000000 --- a/app/Helpers/Collector/TransactionCollectorInterface.php +++ /dev/null @@ -1,359 +0,0 @@ -. - */ - -declare(strict_types=1); - -namespace FireflyIII\Helpers\Collector; - -use Carbon\Carbon; -use FireflyIII\Models\Budget; -use FireflyIII\Models\Category; -use FireflyIII\Models\Tag; -use FireflyIII\Models\TransactionCurrency; -use FireflyIII\User; -use Illuminate\Database\Eloquent\Builder as EloquentBuilder; -use Illuminate\Pagination\LengthAwarePaginator; -use Illuminate\Support\Collection; - -/** - * Interface TransactionCollectorInterface - * - */ -interface TransactionCollectorInterface -{ - - /** - * Add a specific filter. - * - * @param string $filter - * - * @return TransactionCollectorInterface - */ - public function addFilter(string $filter): TransactionCollectorInterface; - - /** - * Get transactions with a specific amount. - * - * @param string $amount - * - * @return TransactionCollectorInterface - */ - public function amountIs(string $amount): TransactionCollectorInterface; - - /** - * Get transactions where the amount is less than. - * - * @param string $amount - * - * @return TransactionCollectorInterface - */ - public function amountLess(string $amount): TransactionCollectorInterface; - - /** - * Get transactions where the amount is more than. - * - * @param string $amount - * - * @return TransactionCollectorInterface - */ - public function amountMore(string $amount): TransactionCollectorInterface; - - /** - * Count the result. - * - * @return int - */ - public function count(): int; - - /** - * Get a paginated result. - * - * @return LengthAwarePaginator - */ - public function getPaginatedTransactions(): LengthAwarePaginator; - - /** - * Get the query. - * - * @return EloquentBuilder - */ - public function getQuery(): EloquentBuilder; - - /** - * Get all transactions. - * - * @return Collection - */ - public function getTransactions(): Collection; - - /** - * Set to ignore the cache. - * - * @return TransactionCollectorInterface - */ - public function ignoreCache(): TransactionCollectorInterface; - - /** - * Remove a filter. - * - * @param string $filter - * - * @return TransactionCollectorInterface - */ - public function removeFilter(string $filter): TransactionCollectorInterface; - - /** - * Set the accounts to collect from. - * - * @param Collection $accounts - * - * @return TransactionCollectorInterface - */ - public function setAccounts(Collection $accounts): TransactionCollectorInterface; - - /** - * Collect transactions after a specific date. - * - * @param Carbon $after - * - * @return TransactionCollectorInterface - */ - public function setAfter(Carbon $after): TransactionCollectorInterface; - - /** - * Include all asset accounts. - * - * @return TransactionCollectorInterface - */ - public function setAllAssetAccounts(): TransactionCollectorInterface; - - /** - * Collect transactions before a specific date. - * - * @param Carbon $before - * - * @return TransactionCollectorInterface - */ - public function setBefore(Carbon $before): TransactionCollectorInterface; - - /** - * Set the bills to filter on. - * - * @param Collection $bills - * - * @return TransactionCollectorInterface - */ - public function setBills(Collection $bills): TransactionCollectorInterface; - - /** - * Set the budget to filter on. - * - * @param Budget $budget - * - * @return TransactionCollectorInterface - */ - public function setBudget(Budget $budget): TransactionCollectorInterface; - - /** - * Set the budgets to filter on. - * - * @param Collection $budgets - * - * @return TransactionCollectorInterface - */ - public function setBudgets(Collection $budgets): TransactionCollectorInterface; - - /** - * Set the categories to filter on. - * - * @param Collection $categories - * - * @return TransactionCollectorInterface - */ - public function setCategories(Collection $categories): TransactionCollectorInterface; - - /** - * Set the category to filter on. - * - * @param Category $category - * - * @return TransactionCollectorInterface - */ - public function setCategory(Category $category): TransactionCollectorInterface; - - /** - * Set the required currency (local or foreign) - * - * @param TransactionCurrency $currency - * - * @return TransactionCollectorInterface - */ - public function setCurrency(TransactionCurrency $currency): TransactionCollectorInterface; - - /** - * Set the journal IDs to filter on. - * - * @param array $journalIds - * - * @return TransactionCollectorInterface - */ - public function setJournalIds(array $journalIds): TransactionCollectorInterface; - - /** - * Set the journals to filter on. - * - * @param Collection $journals - * - * @return TransactionCollectorInterface - */ - public function setJournals(Collection $journals): TransactionCollectorInterface; - - /** - * Set the page limit. - * - * @param int $limit - * - * @return TransactionCollectorInterface - */ - public function setLimit(int $limit): TransactionCollectorInterface; - - /** - * Set the offset. - * - * @param int $offset - * - * @return TransactionCollectorInterface - */ - public function setOffset(int $offset): TransactionCollectorInterface; - - /** - * Set the opposing accounts to collect from. - * - * @param Collection $accounts - * - * @return TransactionCollectorInterface - */ - public function setOpposingAccounts(Collection $accounts): TransactionCollectorInterface; - - /** - * Set the page to get. - * - * @param int $page - * - * @return TransactionCollectorInterface - */ - public function setPage(int $page): TransactionCollectorInterface; - - /** - * Set the date range. - * - * @param Carbon $start - * @param Carbon $end - * - * @return TransactionCollectorInterface - */ - public function setRange(Carbon $start, Carbon $end): TransactionCollectorInterface; - - /** - * Search for words in descriptions. - * - * @param array $array - * - * @return TransactionCollectorInterface - */ - public function setSearchWords(array $array): TransactionCollectorInterface; - - /** - * Set the tag to collect from. - * - * @param Tag $tag - * - * @return TransactionCollectorInterface - */ - public function setTag(Tag $tag): TransactionCollectorInterface; - - /** - * Set the tags to collect from. - * - * @param Collection $tags - * - * @return TransactionCollectorInterface - */ - public function setTags(Collection $tags): TransactionCollectorInterface; - - /** - * Set the types to collect. - * - * @param array $types - * - * @return TransactionCollectorInterface - */ - public function setTypes(array $types): TransactionCollectorInterface; - - /** - * Set the user. - * - * @param User $user - * - * @return mixed - */ - public function setUser(User $user); - - /** - * Start the query. - */ - public function startQuery(); - - /** - * Include budget information. - * - * @return TransactionCollectorInterface - */ - public function withBudgetInformation(): TransactionCollectorInterface; - - /** - * Include category information. - * - * @return TransactionCollectorInterface - */ - public function withCategoryInformation(): TransactionCollectorInterface; - - /** - * Include opposing account information. - * - * @return TransactionCollectorInterface - */ - public function withOpposingAccount(): TransactionCollectorInterface; - - /** - * Include tranactions without a budget. - * - * @return TransactionCollectorInterface - */ - public function withoutBudget(): TransactionCollectorInterface; - - /** - * Include tranactions without a category. - * - * @return TransactionCollectorInterface - */ - public function withoutCategory(): TransactionCollectorInterface; -} diff --git a/app/Helpers/Filter/AmountFilter.php b/app/Helpers/Filter/AmountFilter.php deleted file mode 100644 index e977e4d0a0..0000000000 --- a/app/Helpers/Filter/AmountFilter.php +++ /dev/null @@ -1,72 +0,0 @@ -. - */ -declare(strict_types=1); - -namespace FireflyIII\Helpers\Filter; - -use FireflyIII\Models\Transaction; -use Illuminate\Support\Collection; -use Log; - -/** - * Class AmountFilter. - * - * This filter removes transactions with either a positive amount ($parameters = 1) or a negative amount - * ($parameter = -1). This is helpful when a Collection has you with both transactions in a journal. - */ -class AmountFilter implements FilterInterface -{ - /** @var int Either -1 or +1 for the filter. */ - private $modifier; - - /** - * AmountFilter constructor. - * - * @param int $modifier - */ - public function __construct(int $modifier) - { - $this->modifier = $modifier; - } - - /** - * Filter on amount. - * - * @param Collection $set - * - * @return Collection - */ - public function filter(Collection $set): Collection - { - return $set->filter( - function (Transaction $transaction) { - // remove by amount - if (bccomp($transaction->transaction_amount, '0') === $this->modifier) { - Log::debug(sprintf('Filtered #%d because amount is %f.', $transaction->id, $transaction->transaction_amount)); - - return null; - } - - return $transaction; - } - ); - } -} diff --git a/app/Helpers/Filter/CountAttachmentsFilter.php b/app/Helpers/Filter/CountAttachmentsFilter.php deleted file mode 100644 index 6c67591299..0000000000 --- a/app/Helpers/Filter/CountAttachmentsFilter.php +++ /dev/null @@ -1,71 +0,0 @@ -. - */ - -declare(strict_types=1); - -namespace FireflyIII\Helpers\Filter; - - -use DB; -use FireflyIII\Models\Transaction; -use FireflyIII\Models\TransactionJournal; -use Illuminate\Support\Collection; - -/** - * Class CountAttachmentsFilter - * - * @codeCoverageIgnore - */ -class CountAttachmentsFilter implements FilterInterface -{ - - /** - * Adds the number of transactions to each given transaction. - * - * @param Collection $set - * - * @return Collection - */ - public function filter(Collection $set): Collection - { - // grab journal ID's: - $ids = $set->pluck('journal_id')->toArray(); - - $result = DB::table('attachments') - ->whereNull('deleted_at') - ->whereIn('attachable_id', $ids) - ->where('attachable_type', TransactionJournal::class) - ->groupBy('attachable_id')->get(['attachable_id', DB::raw('COUNT(*) as number')]); - $counter = []; - foreach ($result as $row) { - $counter[$row->attachable_id] = $row->number; - } - $set->each( - function (Transaction $transaction) use ($counter) { - $id = (int)$transaction->journal_id; - $count = (int)($counter[$id] ?? 0.0); - $transaction->attachmentCount = $count; - } - ); - - return $set; - } -} diff --git a/app/Helpers/Filter/DoubleTransactionFilter.php b/app/Helpers/Filter/DoubleTransactionFilter.php deleted file mode 100644 index 02eb5db062..0000000000 --- a/app/Helpers/Filter/DoubleTransactionFilter.php +++ /dev/null @@ -1,62 +0,0 @@ -. - */ - -declare(strict_types=1); - -namespace FireflyIII\Helpers\Filter; - - -use FireflyIII\Models\Transaction; -use Illuminate\Support\Collection; - -/** - * - * Used when the final collection contains double transactions, which can happen when viewing the tag report. - * Class DoubleTransactionFilter - * - * @codeCoverageIgnore - */ -class DoubleTransactionFilter implements FilterInterface -{ - - /** - * Apply the filter. - * - * @param Collection $set - * - * @return Collection - */ - public function filter(Collection $set): Collection - { - $count = []; - $result = new Collection; - /** @var Transaction $transaction */ - foreach ($set as $transaction) { - $id = (int)$transaction->id; - $count[$id] = isset($count[$id]) ? $count[$id] + 1 : 1; - if (1 === $count[$id]) { - $result->push($transaction); - } - } - - return $result; - } -} diff --git a/app/Helpers/Filter/InternalTransferFilter.php b/app/Helpers/Filter/InternalTransferFilter.php deleted file mode 100644 index a72474c4e0..0000000000 --- a/app/Helpers/Filter/InternalTransferFilter.php +++ /dev/null @@ -1,86 +0,0 @@ -. - */ -declare(strict_types=1); - -namespace FireflyIII\Helpers\Filter; - -use FireflyIII\Models\Transaction; -use Illuminate\Support\Collection; -use Log; - -/** - * Class InternalTransferFilter. - * - * This filter removes any filters that are from A to B or from B to A given a set of - * account id's (in $parameters) where A and B are mentioned. So transfers between the mentioned - * accounts will be removed. - * - * @codeCoverageIgnore - */ -class InternalTransferFilter implements FilterInterface -{ - /** @var array The accounts */ - private $accounts; - - /** - * InternalTransferFilter constructor. - * - * @param array $accounts - */ - public function __construct(array $accounts) - { - $this->accounts = $accounts; - } - - /** - * See class description. - * - * @param Collection $set - * - * @return Collection - */ - public function filter(Collection $set): Collection - { - return $set->filter( - function (Transaction $transaction) { - if (null === $transaction->opposing_account_id) { - return $transaction; - } - // both id's in $parameters? - if (\in_array($transaction->account_id, $this->accounts, true) && \in_array($transaction->opposing_account_id, $this->accounts, true)) { - Log::debug( - sprintf( - 'Transaction #%d has #%d and #%d in set, so removed', - $transaction->id, - $transaction->account_id, - $transaction->opposing_account_id - ), - $this->accounts - ); - - return false; - } - - return $transaction; - } - ); - } -} diff --git a/app/Helpers/Filter/NegativeAmountFilter.php b/app/Helpers/Filter/NegativeAmountFilter.php deleted file mode 100644 index 58ebc6ed3b..0000000000 --- a/app/Helpers/Filter/NegativeAmountFilter.php +++ /dev/null @@ -1,60 +0,0 @@ -. - */ -declare(strict_types=1); - -namespace FireflyIII\Helpers\Filter; - -use FireflyIII\Models\Transaction; -use Illuminate\Support\Collection; -use Log; - -/** - * Class NegativeAmountFilter. - * - * This filter removes entries with a negative amount (the original modifier is -1). - * - * @codeCoverageIgnore - */ -class NegativeAmountFilter implements FilterInterface -{ - /** - * See class description. - * - * @param Collection $set - * - * @return Collection - */ - public function filter(Collection $set): Collection - { - return $set->filter( - function (Transaction $transaction) { - // remove by amount - if (bccomp($transaction->transaction_amount, '0') === -1) { - Log::debug(sprintf('Filtered #%d because amount is %f.', $transaction->id, $transaction->transaction_amount)); - - return null; - } - - return $transaction; - } - ); - } -} diff --git a/app/Helpers/Filter/OpposingAccountFilter.php b/app/Helpers/Filter/OpposingAccountFilter.php deleted file mode 100644 index 681a4c26b2..0000000000 --- a/app/Helpers/Filter/OpposingAccountFilter.php +++ /dev/null @@ -1,75 +0,0 @@ -. - */ -declare(strict_types=1); - -namespace FireflyIII\Helpers\Filter; - -use FireflyIII\Models\Transaction; -use Illuminate\Support\Collection; -use Log; - -/** - * Class OpposingAccountFilter. - * - * This filter is similar to the internal transfer filter but only removes transactions when the opposing account is - * amongst $parameters (list of account ID's). - * - * @codeCoverageIgnore - */ -class OpposingAccountFilter implements FilterInterface -{ - /** @var array The asset accounts. */ - private $accounts; - - /** - * InternalTransferFilter constructor. - * - * @param array $accounts - */ - public function __construct(array $accounts) - { - $this->accounts = $accounts; - } - - /** - * Only return specific transactions. See class description. - * - * @param Collection $set - * - * @return Collection - */ - public function filter(Collection $set): Collection - { - return $set->filter( - function (Transaction $transaction) { - $opposing = $transaction->opposing_account_id; - // remove internal transfer - if (\in_array($opposing, $this->accounts, true)) { - Log::debug(sprintf('Filtered #%d because its opposite is in accounts.', $transaction->id), $this->accounts); - - return null; - } - - return $transaction; - } - ); - } -} diff --git a/app/Helpers/Filter/PositiveAmountFilter.php b/app/Helpers/Filter/PositiveAmountFilter.php deleted file mode 100644 index 257bdf43b4..0000000000 --- a/app/Helpers/Filter/PositiveAmountFilter.php +++ /dev/null @@ -1,63 +0,0 @@ -. - */ -declare(strict_types=1); - -namespace FireflyIII\Helpers\Filter; - -use FireflyIII\Models\Transaction; -use Illuminate\Support\Collection; -use Log; - -/** - * Class PositiveAmountFilter. - * - * This filter removes entries with a negative amount (the original modifier is -1). - * - * This filter removes transactions with either a positive amount ($parameters = 1) or a negative amount - * ($parameter = -1). This is helpful when a Collection has you with both transactions in a journal. - * - * @codeCoverageIgnore - */ -class PositiveAmountFilter implements FilterInterface -{ - /** - * See class description. - * - * @param Collection $set - * - * @return Collection - */ - public function filter(Collection $set): Collection - { - return $set->filter( - function (Transaction $transaction) { - // remove by amount - if (1 === bccomp($transaction->transaction_amount, '0')) { - Log::debug(sprintf('Filtered #%d because amount is %f.', $transaction->id, $transaction->transaction_amount)); - - return null; - } - - return $transaction; - } - ); - } -} diff --git a/app/Helpers/Filter/SplitIndicatorFilter.php b/app/Helpers/Filter/SplitIndicatorFilter.php deleted file mode 100644 index 609c3a424e..0000000000 --- a/app/Helpers/Filter/SplitIndicatorFilter.php +++ /dev/null @@ -1,71 +0,0 @@ -. - */ - -declare(strict_types=1); - -namespace FireflyIII\Helpers\Filter; - - -use DB; -use FireflyIII\Models\Transaction; -use Illuminate\Support\Collection; - -/** - * Class SplitIndicatorFilter - * - * @codeCoverageIgnore - */ -class SplitIndicatorFilter implements FilterInterface -{ - - /** - * Adds a property if the journal is a split one. - * - * @param Collection $set - * - * @return Collection - */ - public function filter(Collection $set): Collection - { - // grab journal ID's: - $ids = $set->pluck('journal_id')->toArray(); - - $result = DB::table('transactions') - ->whereNull('deleted_at')->whereIn('transaction_journal_id', $ids) - ->groupBy('transaction_journal_id')->get(['transaction_journal_id', DB::raw('COUNT(*) as number')]); - $counter = []; - foreach ($result as $row) { - $counter[$row->transaction_journal_id] = $row->number; - } - $set->each( - function (Transaction $transaction) use ($counter) { - $id = (int)$transaction->journal_id; - $count = (int)($counter[$id] ?? 0.0); - $transaction->is_split = false; - if ($count > 2) { - $transaction->is_split = true; - } - } - ); - - return $set; - } -} diff --git a/app/Helpers/Filter/TransactionViewFilter.php b/app/Helpers/Filter/TransactionViewFilter.php deleted file mode 100644 index baae12173d..0000000000 --- a/app/Helpers/Filter/TransactionViewFilter.php +++ /dev/null @@ -1,85 +0,0 @@ -. - */ -declare(strict_types=1); - -namespace FireflyIII\Helpers\Filter; - -use FireflyIII\Models\Transaction; -use FireflyIII\Models\TransactionType; -use Illuminate\Support\Collection; -use Log; - -/** - * Class TransactionViewFilter. - * - * This filter removes the entry with a negative amount when it's a withdrawal - * And the positive amount when it's a deposit or transfer - * - * This is used in the mass-edit routine. - * - * @codeCoverageIgnore - * - */ -class TransactionViewFilter implements FilterInterface -{ - /** - * See class description. - * - * @param Collection $set - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * - * @return Collection - */ - public function filter(Collection $set): Collection - { - return $set->filter( - function (Transaction $transaction) { - // remove if amount is less than zero and type is withdrawal. - if ($transaction->transaction_type_type === TransactionType::WITHDRAWAL && 1 === bccomp($transaction->transaction_amount, '0')) { - Log::debug( - sprintf( - 'Filtered #%d because amount is %f and type is %s.', $transaction->id, $transaction->transaction_amount, - $transaction->transaction_type_type - ) - ); - - return null; - } - - if ($transaction->transaction_type_type === TransactionType::DEPOSIT && -1 === bccomp($transaction->transaction_amount, '0')) { - Log::debug( - sprintf( - 'Filtered #%d because amount is %f and type is %s.', $transaction->id, $transaction->transaction_amount, - $transaction->transaction_type_type - ) - ); - - return null; - } - Log::debug( - sprintf('#%d: amount is %f and type is %s.', $transaction->id, $transaction->transaction_amount, $transaction->transaction_type_type) - ); - - return $transaction; - } - ); - } -} diff --git a/app/Helpers/Filter/TransferFilter.php b/app/Helpers/Filter/TransferFilter.php deleted file mode 100644 index ed0dec3350..0000000000 --- a/app/Helpers/Filter/TransferFilter.php +++ /dev/null @@ -1,79 +0,0 @@ -. - */ -declare(strict_types=1); - -namespace FireflyIII\Helpers\Filter; - -use FireflyIII\Models\Transaction; -use FireflyIII\Models\TransactionType; -use Illuminate\Support\Collection; -use Log; - -/** - * Class TransferFilter. - * - * This filter removes any transfers that are in the collection twice (from A to B and from B to A). - * - * @codeCoverageIgnore - */ -class TransferFilter implements FilterInterface -{ - /** - * See class transaction. - * - * @param Collection $set - * - * @return Collection - */ - public function filter(Collection $set): Collection - { - $count = []; - $new = new Collection; - /** @var Transaction $transaction */ - foreach ($set as $transaction) { - if (TransactionType::TRANSFER !== $transaction->transaction_type_type) { - Log::debug(sprintf('Transaction #%d is not a transfer, add it.', $transaction->id)); - $new->push($transaction); - continue; - } - // make property string: - $journalId = $transaction->transaction_journal_id; - $amount = app('steam')->positive($transaction->transaction_amount); - $accountIds = [(int)$transaction->account_id, (int)$transaction->opposing_account_id]; - $transactionIds = [$transaction->id, (int)$transaction->opposing_id]; - sort($accountIds); - sort($transactionIds); - $key = $journalId . '-' . implode(',', $transactionIds) . '-' . implode(',', $accountIds) . '-' . $amount; - Log::debug(sprintf('Current transaction key is "%s"', $key)); - if (!isset($count[$key])) { - Log::debug(sprintf('First instance of transaction #%d, add it.', $transaction->id)); - // not yet counted? add to new set and count it: - $new->push($transaction); - $count[$key] = 1; - } - if (isset($count[$key])) { - Log::debug(sprintf('Second instance of transaction #%d, do NOT add it.', $transaction->id)); - } - } - - return $new; - } -} diff --git a/app/Helpers/FiscalHelper.php b/app/Helpers/Fiscal/FiscalHelper.php similarity index 95% rename from app/Helpers/FiscalHelper.php rename to app/Helpers/Fiscal/FiscalHelper.php index 19bf74e9d9..787ec60726 100644 --- a/app/Helpers/FiscalHelper.php +++ b/app/Helpers/Fiscal/FiscalHelper.php @@ -1,7 +1,7 @@ useCustomFiscalYear = app('preferences')->get('customFiscalYear', false)->data; if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } diff --git a/app/Helpers/FiscalHelperInterface.php b/app/Helpers/Fiscal/FiscalHelperInterface.php similarity index 94% rename from app/Helpers/FiscalHelperInterface.php rename to app/Helpers/Fiscal/FiscalHelperInterface.php index c7e73d50c5..d1917e4172 100644 --- a/app/Helpers/FiscalHelperInterface.php +++ b/app/Helpers/Fiscal/FiscalHelperInterface.php @@ -1,7 +1,7 @@ userAgent = sprintf($this->userAgent, config('firefly.version')); } /** diff --git a/app/Helpers/Report/BalanceReportHelper.php b/app/Helpers/Report/BalanceReportHelper.php index 181e5532bc..82c87fb459 100644 --- a/app/Helpers/Report/BalanceReportHelper.php +++ b/app/Helpers/Report/BalanceReportHelper.php @@ -53,7 +53,7 @@ class BalanceReportHelper implements BalanceReportHelperInterface $this->budgetRepository = $budgetRepository; if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } diff --git a/app/Helpers/Report/BudgetReportHelper.php b/app/Helpers/Report/BudgetReportHelper.php index 6f64024634..238df2b504 100644 --- a/app/Helpers/Report/BudgetReportHelper.php +++ b/app/Helpers/Report/BudgetReportHelper.php @@ -49,7 +49,7 @@ class BudgetReportHelper implements BudgetReportHelperInterface $this->repository = $repository; if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } @@ -144,11 +144,6 @@ class BudgetReportHelper implements BudgetReportHelperInterface $set->push($budget); } } - $set = $set->sortBy( - function (Budget $budget) { - return $budget->name; - } - ); return $set; } diff --git a/app/Helpers/Report/NetWorth.php b/app/Helpers/Report/NetWorth.php index 3afec01a1b..4e54426df3 100644 --- a/app/Helpers/Report/NetWorth.php +++ b/app/Helpers/Report/NetWorth.php @@ -53,7 +53,7 @@ class NetWorth implements NetWorthInterface public function __construct() { if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } @@ -111,7 +111,7 @@ class NetWorth implements NetWorthInterface // if the account is a credit card, subtract the virtual balance from the balance, // to better reflect that this is not money that is actually "yours". - $role = (string)$this->accountRepository->getMetaValue($account, 'accountRole'); + $role = (string)$this->accountRepository->getMetaValue($account, 'account_role'); $virtualBalance = (string)$account->virtual_balance; if ('ccAsset' === $role && '' !== $virtualBalance && (float)$virtualBalance > 0) { $balance = bcsub($balance, $virtualBalance); diff --git a/app/Helpers/Report/PopupReport.php b/app/Helpers/Report/PopupReport.php index 634935ed74..e9903565a3 100644 --- a/app/Helpers/Report/PopupReport.php +++ b/app/Helpers/Report/PopupReport.php @@ -22,7 +22,7 @@ declare(strict_types=1); namespace FireflyIII\Helpers\Report; -use FireflyIII\Helpers\Collector\TransactionCollectorInterface; +use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Models\Account; use FireflyIII\Models\Budget; use FireflyIII\Models\Category; @@ -45,61 +45,61 @@ class PopupReport implements PopupReportInterface public function __construct() { if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } /** - * Collect the tranactions for one account and one budget. + * Collect the transactions for one account and one budget. * - * @param Budget $budget + * @param Budget $budget * @param Account $account - * @param array $attributes + * @param array $attributes * - * @return Collection + * @return array */ - public function balanceForBudget(Budget $budget, Account $account, array $attributes): Collection + public function balanceForBudget(Budget $budget, Account $account, array $attributes): array { - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); $collector->setAccounts(new Collection([$account]))->setRange($attributes['startDate'], $attributes['endDate'])->setBudget($budget); - return $collector->getTransactions(); + return $collector->getExtractedJournals(); } /** - * Collect the tranactions for one account and no budget. + * Collect the transactions for one account and no budget. * * @param Account $account - * @param array $attributes + * @param array $attributes * - * @return Collection + * @return array */ - public function balanceForNoBudget(Account $account, array $attributes): Collection + public function balanceForNoBudget(Account $account, array $attributes): array { - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); $collector ->setAccounts(new Collection([$account])) ->setTypes([TransactionType::WITHDRAWAL]) ->setRange($attributes['startDate'], $attributes['endDate']) ->withoutBudget(); - return $collector->getTransactions(); + return $collector->getExtractedJournals(); } /** - * Collect the tranactions for a budget. + * Collect the transactions for a budget. * * @param Budget $budget - * @param array $attributes + * @param array $attributes * - * @return Collection + * @return array */ - public function byBudget(Budget $budget, array $attributes): Collection + public function byBudget(Budget $budget, array $attributes): array { - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); $collector->setAccounts($attributes['accounts'])->setRange($attributes['startDate'], $attributes['endDate']); @@ -110,97 +110,93 @@ class PopupReport implements PopupReportInterface $collector->setBudget($budget); } - return $collector->getTransactions(); + return $collector->getExtractedJournals(); } /** * Collect journals by a category. * * @param Category $category - * @param array $attributes + * @param array $attributes * - * @return Collection + * @return array */ - public function byCategory(Category $category, array $attributes): Collection + public function byCategory(Category $category, array $attributes): array { - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setAccounts($attributes['accounts'])->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER]) - ->setRange($attributes['startDate'], $attributes['endDate'])->withOpposingAccount() + ->setRange($attributes['startDate'], $attributes['endDate'])->withAccountInformation() ->setCategory($category); - return $collector->getTransactions(); + return $collector->getExtractedJournals(); } /** * Group transactions by expense. * * @param Account $account - * @param array $attributes + * @param array $attributes * - * @return Collection + * @return array */ - public function byExpenses(Account $account, array $attributes): Collection + public function byExpenses(Account $account, array $attributes): array { /** @var JournalRepositoryInterface $repository */ $repository = app(JournalRepositoryInterface::class); $repository->setUser($account->user); - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); $collector->setAccounts(new Collection([$account]))->setRange($attributes['startDate'], $attributes['endDate']) ->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER]); - $transactions = $collector->getTransactions(); - + $journals = $collector->getExtractedJournals(); $report = $attributes['accounts']->pluck('id')->toArray(); // accounts used in this report - // filter for transfers and withdrawals TO the given $account - $transactions = $transactions->filter( - function (Transaction $transaction) use ($report, $repository) { - // get the destinations: - $sources = $repository->getJournalSourceAccounts($transaction->transactionJournal)->pluck('id')->toArray(); - - // do these intersect with the current list? - return !empty(array_intersect($report, $sources)); + $filtered = []; + // TODO not sure if filter is necessary. + /** @var array $journal */ + foreach ($journals as $journal) { + if (in_array($journal['source_account_id'], $report, true)) { + $filtered[] = $journal; } - ); - - return $transactions; + } + return $filtered; } /** * Collect transactions by income. * * @param Account $account - * @param array $attributes + * @param array $attributes * - * @return Collection + * @return array */ - public function byIncome(Account $account, array $attributes): Collection + public function byIncome(Account $account, array $attributes): array { /** @var JournalRepositoryInterface $repository */ $repository = app(JournalRepositoryInterface::class); $repository->setUser($account->user); - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); + + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); $collector->setAccounts(new Collection([$account]))->setRange($attributes['startDate'], $attributes['endDate']) - ->setTypes([TransactionType::DEPOSIT, TransactionType::TRANSFER]); - $transactions = $collector->getTransactions(); - $report = $attributes['accounts']->pluck('id')->toArray(); // accounts used in this report + ->setTypes([TransactionType::DEPOSIT, TransactionType::TRANSFER]) + ->withAccountInformation(); + $journals = $collector->getExtractedJournals(); + $report = $attributes['accounts']->pluck('id')->toArray(); // accounts used in this report // filter the set so the destinations outside of $attributes['accounts'] are not included. - $transactions = $transactions->filter( - function (Transaction $transaction) use ($report, $repository) { - // get the destinations: - $journal = $transaction->transactionJournal; - $destinations = $repository->getJournalDestinationAccounts($journal)->pluck('id')->toArray(); - - // do these intersect with the current list? - return !empty(array_intersect($report, $destinations)); + // TODO not sure if filter is necessary. + $filtered = []; + /** @var array $journal */ + foreach ($journals as $journal) { + if (in_array($journal['destination_account_id'], $report, true)) { + $filtered[] = $journal; } - ); - - return $transactions; + } + return $filtered; } } diff --git a/app/Helpers/Report/PopupReportInterface.php b/app/Helpers/Report/PopupReportInterface.php index ca38466bca..d8b05f093c 100644 --- a/app/Helpers/Report/PopupReportInterface.php +++ b/app/Helpers/Report/PopupReportInterface.php @@ -39,9 +39,9 @@ interface PopupReportInterface * @param Account $account * @param array $attributes * - * @return Collection + * @return array */ - public function balanceForBudget(Budget $budget, Account $account, array $attributes): Collection; + public function balanceForBudget(Budget $budget, Account $account, array $attributes): array; /** * Get balances for transactions without a budget. @@ -49,9 +49,9 @@ interface PopupReportInterface * @param Account $account * @param array $attributes * - * @return Collection + * @return array */ - public function balanceForNoBudget(Account $account, array $attributes): Collection; + public function balanceForNoBudget(Account $account, array $attributes): array; /** * Group by budget. @@ -59,9 +59,9 @@ interface PopupReportInterface * @param Budget $budget * @param array $attributes * - * @return Collection + * @return array */ - public function byBudget(Budget $budget, array $attributes): Collection; + public function byBudget(Budget $budget, array $attributes): array; /** * Group by category. @@ -69,9 +69,9 @@ interface PopupReportInterface * @param Category $category * @param array $attributes * - * @return Collection + * @return array */ - public function byCategory(Category $category, array $attributes): Collection; + public function byCategory(Category $category, array $attributes): array; /** * Do something with expense. Sorry, I am not very inspirational here. @@ -79,9 +79,9 @@ interface PopupReportInterface * @param Account $account * @param array $attributes * - * @return Collection + * @return array */ - public function byExpenses(Account $account, array $attributes): Collection; + public function byExpenses(Account $account, array $attributes): array; /** * Do something with income. Sorry, I am not very inspirational here. @@ -89,7 +89,7 @@ interface PopupReportInterface * @param Account $account * @param array $attributes * - * @return Collection + * @return array */ - public function byIncome(Account $account, array $attributes): Collection; + public function byIncome(Account $account, array $attributes): array; } diff --git a/app/Helpers/Report/ReportHelper.php b/app/Helpers/Report/ReportHelper.php index f84868512b..85dcde11ca 100644 --- a/app/Helpers/Report/ReportHelper.php +++ b/app/Helpers/Report/ReportHelper.php @@ -25,10 +25,9 @@ namespace FireflyIII\Helpers\Report; use Carbon\Carbon; use FireflyIII\Helpers\Collection\Bill as BillCollection; use FireflyIII\Helpers\Collection\BillLine; -use FireflyIII\Helpers\Collector\TransactionCollectorInterface; -use FireflyIII\Helpers\FiscalHelperInterface; +use FireflyIII\Helpers\Collector\GroupCollectorInterface; +use FireflyIII\Helpers\Fiscal\FiscalHelperInterface; use FireflyIII\Models\Bill; -use FireflyIII\Models\Transaction; use FireflyIII\Repositories\Bill\BillRepositoryInterface; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use Illuminate\Support\Collection; @@ -55,7 +54,7 @@ class ReportHelper implements ReportHelperInterface $this->budgetRepository = $budgetRepository; if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } @@ -70,8 +69,8 @@ class ReportHelper implements ReportHelperInterface * @SuppressWarnings(PHPMD.CyclomaticComplexity) * @SuppressWarnings(PHPMD.ExcessiveMethodLength) * - * @param Carbon $start - * @param Carbon $end + * @param Carbon $start + * @param Carbon $end * @param Collection $accounts * * @return BillCollection @@ -92,10 +91,11 @@ class ReportHelper implements ReportHelperInterface foreach ($expectedDates as $payDate) { $endOfPayPeriod = app('navigation')->endOfX($payDate, $bill->repeat_freq, null); - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setAccounts($accounts)->setRange($payDate, $endOfPayPeriod)->setBills($bills); - $transactions = $collector->getTransactions(); + + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setAccounts($accounts)->setRange($payDate, $endOfPayPeriod)->setBill($bill); + $journals = $collector->getExtractedJournals(); $billLine = new BillLine; $billLine->setBill($bill); @@ -105,16 +105,15 @@ class ReportHelper implements ReportHelperInterface $billLine->setMin((string)$bill->amount_min); $billLine->setMax((string)$bill->amount_max); $billLine->setHit(false); - $entry = $transactions->filter( - function (Transaction $transaction) use ($bill) { - return $transaction->bill_id === $bill->id; - } - ); - $first = $entry->first(); + /** @var array $first */ + $first = null; + if (count($journals) > 0) { + $first = reset($journals); + } if (null !== $first) { - $billLine->setTransactionJournalId($first->id); - $billLine->setAmount($first->transaction_amount); - $billLine->setLastHitDate($first->date); + $billLine->setTransactionJournalId($first['transaction_journal_id']); + $billLine->setAmount($first['amount']); + $billLine->setLastHitDate($first['date']); $billLine->setHit(true); } if ($billLine->isActive() || $billLine->isHit()) { diff --git a/app/Helpers/Update/UpdateTrait.php b/app/Helpers/Update/UpdateTrait.php index e57b1f12a4..dfc280d99d 100644 --- a/app/Helpers/Update/UpdateTrait.php +++ b/app/Helpers/Update/UpdateTrait.php @@ -57,9 +57,9 @@ trait UpdateTrait // get releases from array. $releases = $request->getReleases(); - Log::debug(sprintf('Found %d releases', \count($releases))); + Log::debug(sprintf('Found %d releases', count($releases))); - if (\count($releases) > 0) { + if (count($releases) > 0) { // first entry should be the latest entry: /** @var Release $first */ $first = reset($releases); @@ -123,10 +123,12 @@ trait UpdateTrait $return = (string)trans('firefly.update_newer_version_alert', ['your_version' => $current, 'new_version' => $release->getTitle()]); } + // @codeCoverageIgnoreStart if (false === $triggered) { Log::debug('No option was triggered.'); $return = (string)trans('firefly.update_check_error'); } + // @codeCoverageIgnoreEnd return $return; } diff --git a/app/Http/Controllers/Account/CreateController.php b/app/Http/Controllers/Account/CreateController.php index dcaf2cd6d7..37c1cf9f1f 100644 --- a/app/Http/Controllers/Account/CreateController.php +++ b/app/Http/Controllers/Account/CreateController.php @@ -28,6 +28,7 @@ use FireflyIII\Http\Controllers\Controller; use FireflyIII\Http\Requests\AccountFormRequest; use FireflyIII\Models\AccountType; use FireflyIII\Repositories\Account\AccountRepositoryInterface; +use FireflyIII\Support\Http\Controllers\ModelInformation; use Illuminate\Http\Request; use Log; @@ -37,11 +38,13 @@ use Log; */ class CreateController extends Controller { + use ModelInformation; /** @var AccountRepositoryInterface The account repository */ private $repository; /** * CreateController constructor. + * @codeCoverageIgnore */ public function __construct() { @@ -63,32 +66,19 @@ class CreateController extends Controller /** * Create a new account. * - * @param Request $request - * @param string|null $what + * @param Request $request + * @param string|null $objectType * * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View */ - public function create(Request $request, string $what = null) + public function create(Request $request, string $objectType = null) { - $what = $what ?? 'asset'; + $objectType = $objectType ?? 'asset'; $defaultCurrency = app('amount')->getDefaultCurrency(); - $subTitleIcon = config('firefly.subIconsByIdentifier.' . $what); - $subTitle = (string)trans('firefly.make_new_' . $what . '_account'); - $roles = []; - foreach (config('firefly.accountRoles') as $role) { - $roles[$role] = (string)trans('firefly.account_role_' . $role); - } - - // types of liability: - $debt = $this->repository->getAccountTypeByType(AccountType::DEBT); - $loan = $this->repository->getAccountTypeByType(AccountType::LOAN); - $mortgage = $this->repository->getAccountTypeByType(AccountType::MORTGAGE); - $liabilityTypes = [ - $debt->id => (string)trans('firefly.account_type_' . AccountType::DEBT), - $loan->id => (string)trans('firefly.account_type_' . AccountType::LOAN), - $mortgage->id => (string)trans('firefly.account_type_' . AccountType::MORTGAGE), - ]; - asort($liabilityTypes); + $subTitleIcon = config(sprintf('firefly.subIconsByIdentifier.%s', $objectType)); + $subTitle = (string)trans(sprintf('firefly.make_new_%s_account', $objectType)); + $roles = $this->getRoles(); + $liabilityTypes = $this->getLiabilityTypes(); // interest calculation periods: $interestPeriods = [ @@ -111,12 +101,11 @@ class CreateController extends Controller $this->rememberPreviousUri('accounts.create.uri'); } $request->session()->forget('accounts.create.fromStore'); - Log::channel('audit')->info('Create new account.'); + Log::channel('audit')->info('Creating new account.'); - return view('accounts.create', compact('subTitleIcon', 'what', 'interestPeriods', 'subTitle', 'roles', 'liabilityTypes')); + return view('accounts.create', compact('subTitleIcon', 'objectType', 'interestPeriods', 'subTitle', 'roles', 'liabilityTypes')); } - /** * Store the new account. * @@ -132,15 +121,13 @@ class CreateController extends Controller $request->session()->flash('success', (string)trans('firefly.stored_new_account', ['name' => $account->name])); app('preferences')->mark(); - Log::channel('audit')->info('Store new account.', $data); + Log::channel('audit')->info('Stored new account.', $data); // update preferences if necessary: $frontPage = app('preferences')->get('frontPageAccounts', [])->data; - if (AccountType::ASSET === $account->accountType->type && \count($frontPage) > 0) { - // @codeCoverageIgnoreStart + if (AccountType::ASSET === $account->accountType->type) { $frontPage[] = $account->id; app('preferences')->set('frontPageAccounts', $frontPage); - // @codeCoverageIgnoreEnd } // redirect to previous URL. $redirect = redirect($this->getPreviousUri('accounts.create.uri')); @@ -148,10 +135,12 @@ class CreateController extends Controller // set value so create routine will not overwrite URL: $request->session()->put('accounts.create.fromStore', true); - $redirect = redirect(route('accounts.create', [$request->input('what')]))->withInput(); + $redirect = redirect(route('accounts.create', [$request->input('objectType')]))->withInput(); } return $redirect; } + + } diff --git a/app/Http/Controllers/Account/DeleteController.php b/app/Http/Controllers/Account/DeleteController.php index 3ec08fe917..c997c4814e 100644 --- a/app/Http/Controllers/Account/DeleteController.php +++ b/app/Http/Controllers/Account/DeleteController.php @@ -39,6 +39,7 @@ class DeleteController extends Controller /** * DeleteController constructor. + * @codeCoverageIgnore */ public function __construct() { @@ -66,16 +67,16 @@ class DeleteController extends Controller */ public function delete(Account $account) { - $typeName = config('firefly.shortNamesByFullName.' . $account->accountType->type); - $subTitle = (string)trans('firefly.delete_' . $typeName . '_account', ['name' => $account->name]); + $typeName = config(sprintf('firefly.shortNamesByFullName.%s', $account->accountType->type)); + $subTitle = (string)trans(sprintf('firefly.delete_%s_account', $typeName), ['name' => $account->name]); $accountList = app('expandedform')->makeSelectListWithEmpty($this->repository->getAccountsByType([$account->accountType->type])); - $what = $typeName; + $objectType = $typeName; unset($accountList[$account->id]); // put previous url in session $this->rememberPreviousUri('accounts.delete.uri'); - return view('accounts.delete', compact('account', 'subTitle', 'accountList', 'what')); + return view('accounts.delete', compact('account', 'subTitle', 'accountList', 'objectType')); } /** @@ -89,13 +90,13 @@ class DeleteController extends Controller public function destroy(Request $request, Account $account) { $type = $account->accountType->type; - $typeName = config('firefly.shortNamesByFullName.' . $type); + $typeName = config(sprintf('firefly.shortNamesByFullName.%s', $type)); $name = $account->name; $moveTo = $this->repository->findNull((int)$request->get('move_account_before_delete')); $this->repository->destroy($account, $moveTo); - $request->session()->flash('success', (string)trans('firefly.' . $typeName . '_deleted', ['name' => $name])); + $request->session()->flash('success', (string)trans(sprintf('firefly.%s_deleted', $typeName), ['name' => $name])); app('preferences')->mark(); return redirect($this->getPreviousUri('accounts.delete.uri')); diff --git a/app/Http/Controllers/Account/EditController.php b/app/Http/Controllers/Account/EditController.php index b714381a38..117f515dca 100644 --- a/app/Http/Controllers/Account/EditController.php +++ b/app/Http/Controllers/Account/EditController.php @@ -27,9 +27,9 @@ namespace FireflyIII\Http\Controllers\Account; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Http\Requests\AccountFormRequest; use FireflyIII\Models\Account; -use FireflyIII\Models\AccountType; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; +use FireflyIII\Support\Http\Controllers\ModelInformation; use Illuminate\Http\Request; /** @@ -38,6 +38,7 @@ use Illuminate\Http\Request; */ class EditController extends Controller { + use ModelInformation; /** @var CurrencyRepositoryInterface The currency repository */ private $currencyRepos; /** @var AccountRepositoryInterface The account repository */ @@ -67,8 +68,8 @@ class EditController extends Controller /** * Edit account overview. * - * @param Request $request - * @param Account $account + * @param Request $request + * @param Account $account * @param AccountRepositoryInterface $repository * * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View @@ -78,24 +79,11 @@ class EditController extends Controller */ public function edit(Request $request, Account $account, AccountRepositoryInterface $repository) { - $what = config('firefly.shortNamesByFullName')[$account->accountType->type]; - $subTitle = (string)trans('firefly.edit_' . $what . '_account', ['name' => $account->name]); - $subTitleIcon = config('firefly.subIconsByIdentifier.' . $what); - $roles = []; - foreach (config('firefly.accountRoles') as $role) { - $roles[$role] = (string)trans('firefly.account_role_' . $role); - } - - // types of liability: - $debt = $this->repository->getAccountTypeByType(AccountType::DEBT); - $loan = $this->repository->getAccountTypeByType(AccountType::LOAN); - $mortgage = $this->repository->getAccountTypeByType(AccountType::MORTGAGE); - $liabilityTypes = [ - $debt->id => (string)trans('firefly.account_type_' . AccountType::DEBT), - $loan->id => (string)trans('firefly.account_type_' . AccountType::LOAN), - $mortgage->id => (string)trans('firefly.account_type_' . AccountType::MORTGAGE), - ]; - asort($liabilityTypes); + $objectType = config('firefly.shortNamesByFullName')[$account->accountType->type]; + $subTitle = (string)trans(sprintf('firefly.edit_%s_account', $objectType), ['name' => $account->name]); + $subTitleIcon = config(sprintf('firefly.subIconsByIdentifier.%s', $objectType)); + $roles = $this->getRoles(); + $liabilityTypes = $this->getLiabilityTypes(); // interest calculation periods: $interestPeriods = [ @@ -112,44 +100,36 @@ class EditController extends Controller $openingBalanceAmount = (string)$repository->getOpeningBalanceAmount($account); $openingBalanceDate = $repository->getOpeningBalanceDate($account); - $default = app('amount')->getDefaultCurrency(); - $currency = $this->currencyRepos->findNull((int)$repository->getMetaValue($account, 'currency_id')); + $currency = $this->repository->getAccountCurrency($account) ?? app('amount')->getDefaultCurrency(); // include this account in net-worth charts? $includeNetWorth = $repository->getMetaValue($account, 'include_net_worth'); $includeNetWorth = null === $includeNetWorth ? true : '1' === $includeNetWorth; - if (null === $currency) { - $currency = $default; - } - // code to handle active-checkboxes $hasOldInput = null !== $request->old('_token'); $preFilled = [ - 'accountNumber' => $repository->getMetaValue($account, 'accountNumber'), - 'accountRole' => $repository->getMetaValue($account, 'accountRole'), - 'ccType' => $repository->getMetaValue($account, 'ccType'), - 'ccMonthlyPaymentDate' => $repository->getMetaValue($account, 'ccMonthlyPaymentDate'), - 'BIC' => $repository->getMetaValue($account, 'BIC'), - 'openingBalanceDate' => $openingBalanceDate, - 'liability_type_id' => $account->account_type_id, - 'openingBalance' => $openingBalanceAmount, - 'virtualBalance' => $account->virtual_balance, - 'currency_id' => $currency->id, - 'include_net_worth' => $includeNetWorth, - 'interest' => $repository->getMetaValue($account, 'interest'), - 'interest_period' => $repository->getMetaValue($account, 'interest_period'), - 'notes' => $this->repository->getNoteText($account), - 'active' => $hasOldInput ? (bool)$request->old('active') : $account->active, + 'account_number' => $repository->getMetaValue($account, 'account_number'), + 'account_role' => $repository->getMetaValue($account, 'account_role'), + 'cc_type' => $repository->getMetaValue($account, 'cc_type'), + 'cc_monthly_payment_date' => $repository->getMetaValue($account, 'cc_monthly_payment_date'), + 'BIC' => $repository->getMetaValue($account, 'BIC'), + 'opening_balance_date' => $openingBalanceDate, + 'liability_type_id' => $account->account_type_id, + 'opening_balance' => $openingBalanceAmount, + 'virtual_balance' => $account->virtual_balance, + 'currency_id' => $currency->id, + 'include_net_worth' => $includeNetWorth, + 'interest' => $repository->getMetaValue($account, 'interest'), + 'interest_period' => $repository->getMetaValue($account, 'interest_period'), + 'notes' => $this->repository->getNoteText($account), + 'active' => $hasOldInput ? (bool)$request->old('active') : $account->active, ]; - if ('liabilities' === $what) { - $preFilled['openingBalance'] = bcmul($preFilled['openingBalance'], '-1'); - } $request->session()->flash('preFilled', $preFilled); return view( - 'accounts.edit', compact('account', 'currency', 'subTitle', 'subTitleIcon', 'what', 'roles', 'preFilled', 'liabilityTypes', 'interestPeriods') + 'accounts.edit', compact('account', 'currency', 'subTitle', 'subTitleIcon', 'objectType', 'roles', 'preFilled', 'liabilityTypes', 'interestPeriods') ); } @@ -158,7 +138,7 @@ class EditController extends Controller * Update the account. * * @param AccountFormRequest $request - * @param Account $account + * @param Account $account * * @return $this|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector */ diff --git a/app/Http/Controllers/Account/IndexController.php b/app/Http/Controllers/Account/IndexController.php index 6c9e8a19eb..36f0b0e8b3 100644 --- a/app/Http/Controllers/Account/IndexController.php +++ b/app/Http/Controllers/Account/IndexController.php @@ -43,6 +43,7 @@ class IndexController extends Controller /** * IndexController constructor. + * @codeCoverageIgnore */ public function __construct() { @@ -65,30 +66,21 @@ class IndexController extends Controller * Show list of accounts. * * @param Request $request - * @param string $what + * @param string $objectType * * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View */ - public function index(Request $request, string $what) + public function index(Request $request, string $objectType) { - $what = $what ?? 'asset'; - $subTitle = (string)trans('firefly.' . $what . '_accounts'); - $subTitleIcon = config('firefly.subIconsByIdentifier.' . $what); - $types = config('firefly.accountTypesByIdentifier.' . $what); + $objectType = $objectType ?? 'asset'; + $subTitle = (string)trans(sprintf('firefly.%s_accounts', $objectType)); + $subTitleIcon = config(sprintf('firefly.subIconsByIdentifier.%s', $objectType)); + $types = config(sprintf('firefly.accountTypesByIdentifier.%s', $objectType)); $collection = $this->repository->getAccountsByType($types); $total = $collection->count(); - - // sort collection: - $collection = $collection->sortBy( - function (Account $account) { - return ($account->active ? '0' : '1') . $account->name; - } - ); - - - $page = 0 === (int)$request->get('page') ? 1 : (int)$request->get('page'); - $pageSize = (int)app('preferences')->get('listPageSize', 50)->data; - $accounts = $collection->slice(($page - 1) * $pageSize, $pageSize); + $page = 0 === (int)$request->get('page') ? 1 : (int)$request->get('page'); + $pageSize = (int)app('preferences')->get('listPageSize', 50)->data; + $accounts = $collection->slice(($page - 1) * $pageSize, $pageSize); unset($collection); /** @var Carbon $start */ $start = clone session('start', Carbon::now()->startOfMonth()); @@ -108,16 +100,16 @@ class IndexController extends Controller $account->endBalance = $this->isInArray($endBalances, $account->id); $account->difference = bcsub($account->endBalance, $account->startBalance); $account->interest = round($this->repository->getMetaValue($account, 'interest'), 6); - $account->interestPeriod = (string)trans('firefly.interest_calc_' . $this->repository->getMetaValue($account, 'interest_period')); - $account->accountTypeString = (string)trans('firefly.account_type_' . $account->accountType->type); + $account->interestPeriod = (string)trans(sprintf('firefly.interest_calc_%s', $this->repository->getMetaValue($account, 'interest_period'))); + $account->accountTypeString = (string)trans(sprintf('firefly.account_type_%s', $account->accountType->type)); } ); // make paginator: $accounts = new LengthAwarePaginator($accounts, $total, $pageSize, $page); - $accounts->setPath(route('accounts.index', [$what])); + $accounts->setPath(route('accounts.index', [$objectType])); - return view('accounts.index', compact('what', 'subTitleIcon', 'subTitle', 'page', 'accounts')); + return view('accounts.index', compact('objectType', 'subTitleIcon', 'subTitle', 'page', 'accounts')); } diff --git a/app/Http/Controllers/Account/ReconcileController.php b/app/Http/Controllers/Account/ReconcileController.php index 69bd4f07f3..0b4fd5d40d 100644 --- a/app/Http/Controllers/Account/ReconcileController.php +++ b/app/Http/Controllers/Account/ReconcileController.php @@ -24,25 +24,23 @@ declare(strict_types=1); namespace FireflyIII\Http\Controllers\Account; use Carbon\Carbon; +use Exception; use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Factory\TransactionGroupFactory; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Http\Requests\ReconciliationStoreRequest; -use FireflyIII\Http\Requests\ReconciliationUpdateRequest; use FireflyIII\Models\Account; use FireflyIII\Models\AccountType; -use FireflyIII\Models\Transaction; -use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use FireflyIII\Support\Http\Controllers\UserNavigation; +use FireflyIII\User; use Log; /** * Class ReconcileController. - * - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class ReconcileController extends Controller { @@ -56,6 +54,7 @@ class ReconcileController extends Controller /** * ReconcileController constructor. + * @codeCoverageIgnore */ public function __construct() { @@ -75,300 +74,153 @@ class ReconcileController extends Controller ); } - /** - * Edit a reconciliation. - * - * @param TransactionJournal $journal - * - * @return $this|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector - */ - public function edit(TransactionJournal $journal) - { - if (TransactionType::RECONCILIATION !== $journal->transactionType->type) { - return redirect(route('transactions.edit', [$journal->id])); - } - // view related code - $subTitle = (string)trans('breadcrumbs.edit_journal', ['description' => $journal->description]); - - // journal related code - $pTransaction = $this->repository->getFirstPosTransaction($journal); - $preFilled = [ - 'date' => $this->repository->getJournalDate($journal, null), - 'category' => $this->repository->getJournalCategoryName($journal), - 'tags' => implode(',', $journal->tags->pluck('tag')->toArray()), - 'amount' => $pTransaction->amount, - ]; - - session()->flash('preFilled', $preFilled); - - // put previous url in session if not redirect from store (not "return_to_edit"). - if (true !== session('reconcile.edit.fromUpdate')) { - $this->rememberPreviousUri('reconcile.edit.uri'); - } - session()->forget('reconcile.edit.fromUpdate'); - - return view( - 'accounts.reconcile.edit', - compact('journal', 'subTitle') - )->with('data', $preFilled); - } - /** * Reconciliation overview. * - * @param Account $account + * @param Account $account * @param Carbon|null $start * @param Carbon|null $end * * @return \Illuminate\Contracts\View\Factory|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|\Illuminate\View\View - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + * @throws Exception */ public function reconcile(Account $account, Carbon $start = null, Carbon $end = null) { - if (AccountType::INITIAL_BALANCE === $account->accountType->type) { - return $this->redirectToOriginalAccount($account); - } if (AccountType::ASSET !== $account->accountType->type) { + // @codeCoverageIgnoreStart session()->flash('error', (string)trans('firefly.must_be_asset_account')); - return redirect(route('accounts.index', [config('firefly.shortNamesByFullName.' . $account->accountType->type)])); - } - $currencyId = (int)$this->accountRepos->getMetaValue($account, 'currency_id'); - $currency = $this->currencyRepos->findNull($currencyId); - if (null === $currency) { - $currency = app('amount')->getDefaultCurrency(); // @codeCoverageIgnore + return redirect(route('accounts.index', [config(sprintf('firefly.shortNamesByFullName.%s', $account->accountType->type))])); + // @codeCoverageIgnoreEnd } + $currency = $this->accountRepos->getAccountCurrency($account) ?? app('amount')->getDefaultCurrency(); // no start or end: $range = app('preferences')->get('viewRange', '1M')->data; // get start and end + // @codeCoverageIgnoreStart if (null === $start && null === $end) { + /** @var Carbon $start */ $start = clone session('start', app('navigation')->startOfPeriod(new Carbon, $range)); /** @var Carbon $end */ $end = clone session('end', app('navigation')->endOfPeriod(new Carbon, $range)); + } if (null === $end) { /** @var Carbon $end */ $end = app('navigation')->endOfPeriod($start, $range); } + // @codeCoverageIgnoreEnd $startDate = clone $start; - $startDate->subDays(1); + $startDate->subDay(); $startBalance = round(app('steam')->balance($account, $startDate), $currency->decimal_places); + $endBalance = round(app('steam')->balance($account, $end), $currency->decimal_places); - $subTitleIcon = config('firefly.subIconsByIdentifier.' . $account->accountType->type); + $subTitleIcon = config(sprintf('firefly.subIconsByIdentifier.%s', $account->accountType->type)); $subTitle = (string)trans('firefly.reconcile_account', ['account' => $account->name]); // various links $transactionsUri = route('accounts.reconcile.transactions', [$account->id, '%start%', '%end%']); $overviewUri = route('accounts.reconcile.overview', [$account->id, '%start%', '%end%']); $indexUri = route('accounts.reconcile', [$account->id, '%start%', '%end%']); + $objectType = 'asset'; - return view( - 'accounts.reconcile.index', compact( - 'account', 'currency', 'subTitleIcon', 'start', 'end', 'subTitle', 'startBalance', 'endBalance', 'transactionsUri', - 'overviewUri', 'indexUri' - ) - ); + return view('accounts.reconcile.index', + compact('account', 'currency', 'objectType', + 'subTitleIcon', 'start', 'end', 'subTitle', 'startBalance', 'endBalance', + 'transactionsUri', 'overviewUri', 'indexUri')); } - /** - * Show a single reconciliation. - * - * @param TransactionJournal $journal - * - * @return \Illuminate\Contracts\View\Factory|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|\Illuminate\View\View - * @throws FireflyException - */ - public function show(TransactionJournal $journal) - { - - if (TransactionType::RECONCILIATION !== $journal->transactionType->type) { - return redirect(route('transactions.show', [$journal->id])); - } - $subTitle = trans('firefly.reconciliation') . ' "' . $journal->description . '"'; - - // get main transaction: - $transaction = $this->repository->getAssetTransaction($journal); - if (null === $transaction) { - throw new FireflyException('The transaction data is incomplete. This is probably a bug. Apologies.'); - } - $account = $transaction->account; - - return view('accounts.reconcile.show', compact('journal', 'subTitle', 'transaction', 'account')); - } - - /** @noinspection MoreThanThreeArgumentsInspection */ /** * Submit a new reconciliation. * * @param ReconciliationStoreRequest $request - * @param Account $account - * @param Carbon $start - * @param Carbon $end + * @param Account $account + * @param Carbon $start + * @param Carbon $end * * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector - * @throws FireflyException - * - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ public function submit(ReconciliationStoreRequest $request, Account $account, Carbon $start, Carbon $end) { Log::debug('In ReconcileController::submit()'); $data = $request->getAll(); - /** @var Transaction $transaction */ - foreach ($data['transactions'] as $transactionId) { - $this->repository->reconcileById((int)$transactionId); + /** @var string $journalId */ + foreach ($data['journals'] as $journalId) { + $this->repository->reconcileById((int)$journalId); } Log::debug('Reconciled all transactions.'); // create reconciliation transaction (if necessary): + $result = ''; if ('create' === $data['reconcile']) { - // get "opposing" account. - $reconciliation = $this->accountRepos->getReconciliation($account); - $difference = $data['difference']; - $source = $reconciliation; - $destination = $account; - if (1 === bccomp($difference, '0')) { - // amount is positive. Add it to reconciliation? - $source = $account; - $destination = $reconciliation; - } - - // data for journal - $description = trans( - 'firefly.reconcilliation_transaction_title', - ['from' => $start->formatLocalized($this->monthAndDayFormat), 'to' => $end->formatLocalized($this->monthAndDayFormat)] - ); - $journalData = [ - 'type' => 'Reconciliation', - 'description' => $description, - 'user' => auth()->user()->id, - 'date' => $data['end'], - 'bill_id' => null, - 'bill_name' => null, - 'piggy_bank_id' => null, - 'piggy_bank_name' => null, - 'tags' => null, - 'interest_date' => null, - 'transactions' => [[ - 'currency_id' => (int)$this->accountRepos->getMetaValue($account, 'currency_id'), - 'currency_code' => null, - 'description' => null, - 'amount' => app('steam')->positive($difference), - 'source_id' => $source->id, - 'source_name' => null, - 'destination_id' => $destination->id, - 'destination_name' => null, - 'reconciled' => true, - 'identifier' => 0, - 'foreign_currency_id' => null, - 'foreign_currency_code' => null, - 'foreign_amount' => null, - 'budget_id' => null, - 'budget_name' => null, - 'category_id' => null, - 'category_name' => null, - ], - ], - 'notes' => implode(', ', $data['transactions']), - ]; - - $this->repository->store($journalData); + $result = $this->createReconciliation($account, $start, $end, $data['difference']); } Log::debug('End of routine.'); app('preferences')->mark(); - session()->flash('success', (string)trans('firefly.reconciliation_stored')); + if ('' === $result) { + session()->flash('success', (string)trans('firefly.reconciliation_stored')); + } + if ('' !== $result) { + session()->flash('error', (string)trans('firefly.reconciliation_error', ['error' => $result])); + } return redirect(route('accounts.show', [$account->id])); } - /** - * Update a reconciliation. - * - * @param ReconciliationUpdateRequest $request - * @param TransactionJournal $journal - * - * @return $this|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector - * - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + * Creates a reconciliation group. + * @return string */ - public function update(ReconciliationUpdateRequest $request, TransactionJournal $journal) + private function createReconciliation(Account $account, Carbon $start, Carbon $end, string $difference): string { - if (TransactionType::RECONCILIATION !== $journal->transactionType->type) { - return redirect(route('transactions.show', [$journal->id])); + $reconciliation = $this->accountRepos->getReconciliation($account); + $currency = $this->accountRepos->getAccountCurrency($account) ?? app('amount')->getDefaultCurrency(); + $source = $reconciliation; + $destination = $account; + if (1 === bccomp($difference, '0')) { + $source = $account; + $destination = $reconciliation; } - if (0 === bccomp('0', $request->get('amount'))) { - session()->flash('error', (string)trans('firefly.amount_cannot_be_zero')); - return redirect(route('accounts.reconcile.edit', [$journal->id]))->withInput(); - } - // update journal using account repository. Keep it consistent. - $submitted = $request->getJournalData(); - - // amount pos neg influences the accounts: - $source = $this->repository->getJournalSourceAccounts($journal)->first(); - $destination = $this->repository->getJournalDestinationAccounts($journal)->first(); - if (1 === bccomp($submitted['amount'], '0')) { - // amount is positive, switch accounts: - [$source, $destination] = [$destination, $source]; - - } - // expand data with journal data: - $data = [ - 'type' => $journal->transactionType->type, - 'description' => $journal->description, - 'user' => $journal->user_id, - 'date' => $journal->date, - 'bill_id' => null, - 'bill_name' => null, - 'piggy_bank_id' => null, - 'piggy_bank_name' => null, - 'tags' => $submitted['tags'], - 'interest_date' => null, - 'book_date' => null, - 'transactions' => [[ - 'currency_id' => (int)$journal->transaction_currency_id, - 'currency_code' => null, - 'description' => null, - 'amount' => app('steam')->positive($submitted['amount']), - 'source_id' => $source->id, - 'source_name' => null, - 'destination_id' => $destination->id, - 'destination_name' => null, - 'reconciled' => true, - 'identifier' => 0, - 'foreign_currency_id' => null, - 'foreign_currency_code' => null, - 'foreign_amount' => null, - 'budget_id' => null, - 'budget_name' => null, - 'category_id' => null, - 'category_name' => $submitted['category'], - ], + // title: + $description = trans('firefly.reconciliation_transaction_title', + ['from' => $start->formatLocalized($this->monthAndDayFormat), 'to' => $end->formatLocalized($this->monthAndDayFormat)]); + $submission = [ + 'user' => auth()->user()->id, + 'group_title' => null, + 'transactions' => [ + [ + 'user' => auth()->user()->id, + 'type' => strtolower(TransactionType::RECONCILIATION), + 'date' => $end, + 'order' => 0, + 'currency_id' => $currency->id, + 'foreign_currency_id' => null, + 'amount' => $difference, + 'foreign_amount' => null, + 'description' => $description, + 'source_id' => $source->id, + 'destination_id' => $destination->id, + 'reconciled' => true, + ], ], - 'notes' => $this->repository->getNoteText($journal), ]; - - $this->repository->update($journal, $data); - - - // @codeCoverageIgnoreStart - if (1 === (int)$request->get('return_to_edit')) { - session()->put('reconcile.edit.fromUpdate', true); - - return redirect(route('accounts.reconcile.edit', [$journal->id]))->withInput(['return_to_edit' => 1]); + /** @var TransactionGroupFactory $factory */ + $factory = app(TransactionGroupFactory::class); + /** @var User $user */ + $user = auth()->user(); + $factory->setUser($user); + try { + $factory->create($submission); + } catch (FireflyException $e) { + return $e->getMessage(); } - // @codeCoverageIgnoreEnd - // redirect to previous URL. - return redirect($this->getPreviousUri('reconcile.edit.uri')); + return ''; } } diff --git a/app/Http/Controllers/Account/ShowController.php b/app/Http/Controllers/Account/ShowController.php index db60db7ab0..cd5bee7207 100644 --- a/app/Http/Controllers/Account/ShowController.php +++ b/app/Http/Controllers/Account/ShowController.php @@ -24,8 +24,8 @@ declare(strict_types=1); namespace FireflyIII\Http\Controllers\Account; use Carbon\Carbon; -use FireflyIII\Exceptions\FireflyException; -use FireflyIII\Helpers\Collector\TransactionCollectorInterface; +use Exception; +use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Models\Account; use FireflyIII\Models\AccountType; @@ -53,6 +53,7 @@ class ShowController extends Controller /** * ShowController constructor. + * @codeCoverageIgnore */ public function __construct() { @@ -76,62 +77,57 @@ class ShowController extends Controller /** * Show an account. * - * @param Request $request - * @param Account $account + * @param Request $request + * @param Account $account * @param Carbon|null $start * @param Carbon|null $end * * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View - * - * @throws FireflyException - * + * @throws Exception */ public function show(Request $request, Account $account, Carbon $start = null, Carbon $end = null) { - if (AccountType::INITIAL_BALANCE === $account->accountType->type) { - return $this->redirectToOriginalAccount($account); - } - // a basic thing to determin if this account is a liability: - if ($this->repository->isLiability($account)) { - return redirect(route('accounts.show.all', [$account->id])); + if (in_array($account->accountType->type, [AccountType::INITIAL_BALANCE, AccountType::RECONCILIATION], true)) { + return $this->redirectToOriginalAccount($account); // @codeCoverageIgnore } /** @var Carbon $start */ $start = $start ?? session('start'); /** @var Carbon $end */ $end = $end ?? session('end'); + if ($end < $start) { - throw new FireflyException('End is after start!'); // @codeCoverageIgnore + [$start, $end] = [$end, $start]; // @codeCoverageIgnore } - $what = config(sprintf('firefly.shortNamesByFullName.%s', $account->accountType->type)); // used for menu - $today = new Carbon; - $subTitleIcon = config(sprintf('firefly.subIconsByIdentifier.%s', $account->accountType->type)); - $page = (int)$request->get('page'); - $pageSize = (int)app('preferences')->get('listPageSize', 50)->data; - $currencyId = (int)$this->repository->getMetaValue($account, 'currency_id'); - $currency = $this->currencyRepos->findNull($currencyId); - if (0 === $currencyId) { - $currency = app('amount')->getDefaultCurrency(); // @codeCoverageIgnore - } - $fStart = $start->formatLocalized($this->monthAndDayFormat); - $fEnd = $end->formatLocalized($this->monthAndDayFormat); - $subTitle = (string)trans('firefly.journals_in_period_for_account', ['name' => $account->name, 'start' => $fStart, 'end' => $fEnd]); - $chartUri = route('chart.account.period', [$account->id, $start->format('Y-m-d'), $end->format('Y-m-d')]); - $periods = $this->getAccountPeriodOverview($account, $end); - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setAccounts(new Collection([$account]))->setLimit($pageSize)->setPage($page); - $collector->setRange($start, $end); - $transactions = $collector->getPaginatedTransactions(); - $transactions->setPath(route('accounts.show', [$account->id, $start->format('Y-m-d'), $end->format('Y-m-d')])); + $objectType = config(sprintf('firefly.shortNamesByFullName.%s', $account->accountType->type)); + $today = new Carbon; + $subTitleIcon = config(sprintf('firefly.subIconsByIdentifier.%s', $account->accountType->type)); + $page = (int)$request->get('page'); + $pageSize = (int)app('preferences')->get('listPageSize', 50)->data; + $currency = $this->repository->getAccountCurrency($account) ?? app('amount')->getDefaultCurrency(); + $fStart = $start->formatLocalized($this->monthAndDayFormat); + $fEnd = $end->formatLocalized($this->monthAndDayFormat); + $subTitle = (string)trans('firefly.journals_in_period_for_account', ['name' => $account->name, 'start' => $fStart, 'end' => $fEnd]); + $chartUri = route('chart.account.period', [$account->id, $start->format('Y-m-d'), $end->format('Y-m-d')]); + $firstTransaction = $this->repository->oldestJournalDate($account) ?? $start; + $periods = $this->getAccountPeriodOverview($account, $firstTransaction, $end); + + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector + ->setAccounts(new Collection([$account])) + ->setLimit($pageSize) + ->setPage($page)->withAccountInformation() + ->setRange($start, $end); + $groups = $collector->getPaginatedGroups(); + $groups->setPath(route('accounts.show', [$account->id, $start->format('Y-m-d'), $end->format('Y-m-d')])); $showAll = false; - return view( 'accounts.show', compact( - 'account', 'showAll', 'what', 'currency', 'today', 'periods', 'subTitleIcon', 'transactions', 'subTitle', 'start', 'end', + 'account', 'showAll', 'objectType', 'currency', 'today', 'periods', 'subTitleIcon', 'groups', 'subTitle', 'start', 'end', 'chartUri' ) ); @@ -142,10 +138,10 @@ class ShowController extends Controller * * @param Request $request * @param Account $account - * * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View * * + * @throws Exception */ public function showAll(Request $request, Account $account) { @@ -153,30 +149,28 @@ class ShowController extends Controller return $this->redirectToOriginalAccount($account); // @codeCoverageIgnore } $isLiability = $this->repository->isLiability($account); + $objectType = config(sprintf('firefly.shortNamesByFullName.%s', $account->accountType->type)); $end = new Carbon; $today = new Carbon; $start = $this->repository->oldestJournalDate($account) ?? Carbon::now()->startOfMonth(); $subTitleIcon = config('firefly.subIconsByIdentifier.' . $account->accountType->type); $page = (int)$request->get('page'); $pageSize = (int)app('preferences')->get('listPageSize', 50)->data; - $currencyId = (int)$this->repository->getMetaValue($account, 'currency_id'); - $currency = $this->currencyRepos->findNull($currencyId); - if (0 === $currencyId) { - $currency = app('amount')->getDefaultCurrency(); // @codeCoverageIgnore - } - $subTitle = (string)trans('firefly.all_journals_for_account', ['name' => $account->name]); - $periods = new Collection; - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setAccounts(new Collection([$account]))->setLimit($pageSize)->setPage($page); - $transactions = $collector->getPaginatedTransactions(); - $transactions->setPath(route('accounts.show.all', [$account->id])); + $currency = $this->repository->getAccountCurrency($account) ?? app('amount')->getDefaultCurrency(); + $subTitle = (string)trans('firefly.all_journals_for_account', ['name' => $account->name]); + $periods = new Collection; + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setAccounts(new Collection([$account]))->setLimit($pageSize)->setPage($page)->withAccountInformation(); + $groups = $collector->getPaginatedGroups(); + $groups->setPath(route('accounts.show.all', [$account->id])); $chartUri = route('chart.account.period', [$account->id, $start->format('Y-m-d'), $end->format('Y-m-d')]); $showAll = true; return view( 'accounts.show', - compact('account', 'showAll', 'isLiability', 'currency', 'today', 'chartUri', 'periods', 'subTitleIcon', 'transactions', 'subTitle', 'start', 'end') + compact('account', 'showAll', 'objectType', 'isLiability', 'currency', 'today', + 'chartUri', 'periods', 'subTitleIcon', 'groups', 'subTitle', 'start', 'end') ); } diff --git a/app/Http/Controllers/Admin/ConfigurationController.php b/app/Http/Controllers/Admin/ConfigurationController.php index f9932b5236..f4b7982268 100644 --- a/app/Http/Controllers/Admin/ConfigurationController.php +++ b/app/Http/Controllers/Admin/ConfigurationController.php @@ -37,13 +37,14 @@ class ConfigurationController extends Controller { /** * ConfigurationController constructor. + * @codeCoverageIgnore */ public function __construct() { parent::__construct(); $this->middleware( - function ($request, $next) { + static function ($request, $next) { app('view')->share('title', (string)trans('firefly.administration')); app('view')->share('mainTitleIcon', 'fa-hand-spock-o'); diff --git a/app/Http/Controllers/Admin/HomeController.php b/app/Http/Controllers/Admin/HomeController.php index 16733d64f5..514185b49c 100644 --- a/app/Http/Controllers/Admin/HomeController.php +++ b/app/Http/Controllers/Admin/HomeController.php @@ -37,6 +37,7 @@ class HomeController extends Controller { /** * ConfigurationController constructor. + * @codeCoverageIgnore */ public function __construct() { diff --git a/app/Http/Controllers/Admin/LinkController.php b/app/Http/Controllers/Admin/LinkController.php index 1188f28de3..4df4519bd8 100644 --- a/app/Http/Controllers/Admin/LinkController.php +++ b/app/Http/Controllers/Admin/LinkController.php @@ -36,8 +36,13 @@ use View; */ class LinkController extends Controller { + + /** @var LinkTypeRepositoryInterface */ + private $repository; + /** * LinkController constructor. + * @codeCoverageIgnore */ public function __construct() { @@ -47,6 +52,7 @@ class LinkController extends Controller function ($request, $next) { app('view')->share('title', (string)trans('firefly.administration')); app('view')->share('mainTitleIcon', 'fa-hand-spock-o'); + $this->repository = app(LinkTypeRepositoryInterface::class); return $next($request); } @@ -61,11 +67,11 @@ class LinkController extends Controller */ public function create() { + Log::channel('audit')->info('User visits link index.'); + $subTitle = (string)trans('firefly.create_new_link_type'); $subTitleIcon = 'fa-link'; - Log::channel('audit')->info('User visits link index.'); - // put previous url in session if not redirect from store (not "create another"). if (true !== session('link-types.create.fromStore')) { $this->rememberPreviousUri('link-types.create.uri'); @@ -77,13 +83,12 @@ class LinkController extends Controller /** * Delete a link form. * - * @param Request $request - * @param LinkTypeRepositoryInterface $repository - * @param LinkType $linkType + * @param Request $request + * @param LinkType $linkType * * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View */ - public function delete(Request $request, LinkTypeRepositoryInterface $repository, LinkType $linkType) + public function delete(Request $request, LinkType $linkType) { if (!$linkType->editable) { $request->session()->flash('error', (string)trans('firefly.cannot_edit_link_type', ['name' => e($linkType->name)])); @@ -92,18 +97,19 @@ class LinkController extends Controller } Log::channel('audit')->info(sprintf('User wants to delete link type #%d', $linkType->id)); - $subTitle = (string)trans('firefly.delete_link_type', ['name' => $linkType->name]); - $otherTypes = $repository->get(); - $count = $repository->countJournals($linkType); + $otherTypes = $this->repository->get(); + $count = $this->repository->countJournals($linkType); $moveTo = []; $moveTo[0] = (string)trans('firefly.do_not_save_connection'); + /** @var LinkType $otherType */ foreach ($otherTypes as $otherType) { if ($otherType->id !== $linkType->id) { $moveTo[$otherType->id] = sprintf('%s (%s / %s)', $otherType->name, $otherType->inward, $otherType->outward); } } + // put previous url in session $this->rememberPreviousUri('link-types.delete.uri'); @@ -113,18 +119,17 @@ class LinkController extends Controller /** * Actually destroy the link. * - * @param Request $request - * @param LinkTypeRepositoryInterface $repository - * @param LinkType $linkType + * @param Request $request + * @param LinkType $linkType * * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector */ - public function destroy(Request $request, LinkTypeRepositoryInterface $repository, LinkType $linkType) + public function destroy(Request $request, LinkType $linkType) { Log::channel('audit')->info(sprintf('User destroyed link type #%d', $linkType->id)); $name = $linkType->name; - $moveTo = $repository->findNull((int)$request->get('move_link_type_before_delete')); - $repository->destroy($linkType, $moveTo); + $moveTo = $this->repository->findNull((int)$request->get('move_link_type_before_delete')); + $this->repository->destroy($linkType, $moveTo); $request->session()->flash('success', (string)trans('firefly.deleted_link_type', ['name' => $name])); app('preferences')->mark(); @@ -135,7 +140,7 @@ class LinkController extends Controller /** * Edit a link form. * - * @param Request $request + * @param Request $request * @param LinkType $linkType * * @return \Illuminate\Contracts\View\Factory|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|\Illuminate\View\View @@ -164,20 +169,18 @@ class LinkController extends Controller /** * Show index of all links. * - * @param LinkTypeRepositoryInterface $repository - * * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View */ - public function index(LinkTypeRepositoryInterface $repository) + public function index() { $subTitle = (string)trans('firefly.journal_link_configuration'); $subTitleIcon = 'fa-link'; - $linkTypes = $repository->get(); + $linkTypes = $this->repository->get(); Log::channel('audit')->info('User on index of link types in admin.'); $linkTypes->each( - function (LinkType $linkType) use ($repository) { - $linkType->journalCount = $repository->countJournals($linkType); + function (LinkType $linkType) { + $linkType->journalCount = $this->repository->countJournals($linkType); } ); @@ -195,7 +198,7 @@ class LinkController extends Controller { $subTitle = (string)trans('firefly.overview_for_link', ['name' => $linkType->name]); $subTitleIcon = 'fa-link'; - $links = $linkType->transactionJournalLinks()->get(); + $links = $this->repository->getJournalLinks($linkType); Log::channel('audit')->info(sprintf('User viewing link type #%d', $linkType->id)); @@ -205,19 +208,18 @@ class LinkController extends Controller /** * Store the new link. * - * @param LinkTypeFormRequest $request - * @param LinkTypeRepositoryInterface $repository + * @param LinkTypeFormRequest $request * * @return $this|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector */ - public function store(LinkTypeFormRequest $request, LinkTypeRepositoryInterface $repository) + public function store(LinkTypeFormRequest $request) { $data = [ 'name' => $request->string('name'), 'inward' => $request->string('inward'), 'outward' => $request->string('outward'), ]; - $linkType = $repository->store($data); + $linkType = $this->repository->store($data); Log::channel('audit')->info('User stored new link type.', $linkType->toArray()); @@ -237,13 +239,12 @@ class LinkController extends Controller /** * Update an existing link. * - * @param LinkTypeFormRequest $request - * @param LinkTypeRepositoryInterface $repository - * @param LinkType $linkType + * @param LinkTypeFormRequest $request + * @param LinkType $linkType * * @return $this|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector */ - public function update(LinkTypeFormRequest $request, LinkTypeRepositoryInterface $repository, LinkType $linkType) + public function update(LinkTypeFormRequest $request, LinkType $linkType) { if (!$linkType->editable) { $request->session()->flash('error', (string)trans('firefly.cannot_edit_link_type', ['name' => e($linkType->name)])); @@ -256,7 +257,7 @@ class LinkController extends Controller 'inward' => $request->string('inward'), 'outward' => $request->string('outward'), ]; - $repository->update($linkType, $data); + $this->repository->update($linkType, $data); Log::channel('audit')->info(sprintf('User update link type #%d.', $linkType->id), $data); diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php index 683269c659..001eec6061 100644 --- a/app/Http/Controllers/Admin/UserController.php +++ b/app/Http/Controllers/Admin/UserController.php @@ -35,6 +35,9 @@ use Log; */ class UserController extends Controller { + /** @var UserRepositoryInterface */ + private $repository; + /** * UserController constructor. */ @@ -46,7 +49,7 @@ class UserController extends Controller function ($request, $next) { app('view')->share('title', (string)trans('firefly.administration')); app('view')->share('mainTitleIcon', 'fa-hand-spock-o'); - + $this->repository = app(UserRepositoryInterface::class); return $next($request); } ); @@ -72,13 +75,12 @@ class UserController extends Controller * Destroy a user. * * @param User $user - * @param UserRepositoryInterface $repository * * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector */ - public function destroy(User $user, UserRepositoryInterface $repository) + public function destroy(User $user) { - $repository->destroy($user); + $this->repository->destroy($user); session()->flash('success', (string)trans('firefly.user_deleted')); return redirect(route('admin.users')); @@ -114,26 +116,19 @@ class UserController extends Controller /** * Show index of user manager. * - * @param UserRepositoryInterface $repository - * * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View */ - public function index(UserRepositoryInterface $repository) + public function index() { $subTitle = (string)trans('firefly.user_administration'); $subTitleIcon = 'fa-users'; - $users = $repository->all(); + $users = $this->repository->all(); // add meta stuff. $users->each( - function (User $user) use ($repository) { - $list = ['twoFactorAuthEnabled', 'twoFactorAuthSecret']; - $preferences = app('preferences')->getArrayForUser($user, $list); - $user->isAdmin = $repository->hasRole($user, 'owner'); - $is2faEnabled = 1 === $preferences['twoFactorAuthEnabled']; - $has2faSecret = null !== $preferences['twoFactorAuthSecret']; - $user->has2FA = ($is2faEnabled && $has2faSecret); - $user->prefs = $preferences; + function (User $user) { + $user->isAdmin = $this->repository->hasRole($user, 'owner'); + $user->has2FA = null !== $user->mfa_secret; } ); @@ -143,18 +138,17 @@ class UserController extends Controller /** * Show single user. * - * @param UserRepositoryInterface $repository * @param User $user * * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View */ - public function show(UserRepositoryInterface $repository, User $user) + public function show(User $user) { $title = (string)trans('firefly.administration'); $mainTitleIcon = 'fa-hand-spock-o'; $subTitle = (string)trans('firefly.single_user_administration', ['email' => $user->email]); $subTitleIcon = 'fa-user'; - $information = $repository->getUserData($user); + $information = $this->repository->getUserData($user); return view( 'admin.users.show', compact( @@ -168,22 +162,21 @@ class UserController extends Controller * * @param UserFormRequest $request * @param User $user - * @param UserRepositoryInterface $repository * * @return $this|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector */ - public function update(UserFormRequest $request, User $user, UserRepositoryInterface $repository) + public function update(UserFormRequest $request, User $user) { Log::debug('Actually here'); $data = $request->getUserData(); // update password if ('' !== $data['password']) { - $repository->changePassword($user, $data['password']); + $this->repository->changePassword($user, $data['password']); } - $repository->changeStatus($user, $data['blocked'], $data['blocked_code']); - $repository->updateEmail($user, $data['email']); + $this->repository->changeStatus($user, $data['blocked'], $data['blocked_code']); + $this->repository->updateEmail($user, $data['email']); session()->flash('success', (string)trans('firefly.updated_user', ['email' => $user->email])); app('preferences')->mark(); diff --git a/app/Http/Controllers/AttachmentController.php b/app/Http/Controllers/AttachmentController.php index b0c88f9894..0fc0009e0b 100644 --- a/app/Http/Controllers/AttachmentController.php +++ b/app/Http/Controllers/AttachmentController.php @@ -41,6 +41,7 @@ class AttachmentController extends Controller /** * AttachmentController constructor. + * @codeCoverageIgnore */ public function __construct() { @@ -121,7 +122,7 @@ class AttachmentController extends Controller ->header('Expires', '0') ->header('Cache-Control', 'must-revalidate, post-check=0, pre-check=0') ->header('Pragma', 'public') - ->header('Content-Length', \strlen($content)); + ->header('Content-Length', strlen($content)); return $response; } diff --git a/app/Http/Controllers/Auth/ForgotPasswordController.php b/app/Http/Controllers/Auth/ForgotPasswordController.php index 1ce51d9dab..02e886339b 100644 --- a/app/Http/Controllers/Auth/ForgotPasswordController.php +++ b/app/Http/Controllers/Auth/ForgotPasswordController.php @@ -33,6 +33,7 @@ use Log; /** * Class ForgotPasswordController + * @codeCoverageIgnore */ class ForgotPasswordController extends Controller { @@ -72,6 +73,7 @@ class ForgotPasswordController extends Controller $this->validateEmail($request); // verify if the user is not a demo user. If so, we give him back an error. + /** @var User $user */ $user = User::where('email', $request->get('email'))->first(); if (null !== $user && $repository->hasRole($user, 'demo')) { diff --git a/app/Http/Controllers/Auth/LoginController.php b/app/Http/Controllers/Auth/LoginController.php index 08deaaaddc..ddb872bb16 100644 --- a/app/Http/Controllers/Auth/LoginController.php +++ b/app/Http/Controllers/Auth/LoginController.php @@ -72,7 +72,7 @@ class LoginController extends Controller public function login(Request $request) { Log::channel('audit')->info(sprintf('User is trying to login using "%s"', $request->get('email'))); - + Log::info(sprintf('User is trying to login.')); if ('ldap' === config('auth.providers.users.driver')) { /** * Temporary bug fix for something that doesn't seem to work in @@ -102,9 +102,14 @@ class LoginController extends Controller // user is logged in. Save in session if the user requested session to be remembered: $request->session()->put('remember_login', $request->filled('remember')); + Log::debug(sprintf('Redirect after login is %s.', $this->redirectPath())); + /** @noinspection PhpInconsistentReturnPointsInspection */ /** @noinspection PhpVoidFunctionResultUsedInspection */ - return $this->sendLoginResponse($request); + $response = $this->sendLoginResponse($request); + Log::debug(sprintf('Response Location header: %s', $response->headers->get('location'))); + + return $response; } // If the login attempt was unsuccessful we will increment the number of attempts diff --git a/app/Http/Controllers/Auth/TwoFactorController.php b/app/Http/Controllers/Auth/TwoFactorController.php index d6db6d5e34..b211cb78f3 100644 --- a/app/Http/Controllers/Auth/TwoFactorController.php +++ b/app/Http/Controllers/Auth/TwoFactorController.php @@ -29,6 +29,8 @@ use FireflyIII\User; use Illuminate\Cookie\CookieJar; use Illuminate\Http\Request; use Log; +use PragmaRX\Google2FALaravel\Support\Authenticator; +use Preferences; /** * Class TwoFactorController. @@ -36,36 +38,87 @@ use Log; class TwoFactorController extends Controller { /** - * Show 2FA screen. - * * @param Request $request * - * @return \Illuminate\Contracts\View\Factory|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|\Illuminate\View\View - * - * @throws FireflyException - * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector */ - public function index(Request $request) + public function submitMFA(Request $request) { - $user = auth()->user(); + /** @var array $mfaHistory */ + $mfaHistory = Preferences::get('mfa_history', [])->data; + $mfaCode = $request->get('one_time_password'); - // to make sure the validator in the next step gets the secret, we push it in session - $secretPreference = app('preferences')->get('twoFactorAuthSecret', null); - $secret = null === $secretPreference ? null : $secretPreference->data; - $title = (string)trans('firefly.two_factor_title'); + // is in history? then refuse to use it. + if ($this->inMFAHistory($mfaCode, $mfaHistory)) { + $this->filterMFAHistory(); + session()->flash('error', trans('firefly.wrong_mfa_code')); - // make sure the user has two factor configured: - $has2FA = app('preferences')->get('twoFactorAuthEnabled', false)->data; - if (null === $has2FA || false === $has2FA) { - return redirect(route('index')); + return redirect(route('home')); } - if ('' === (string)$secret) { - throw new FireflyException('Your two factor authentication secret is empty, which it cannot be at this point. Please check the log files.'); - } - $request->session()->flash('two-factor-secret', $secret); + /** @var Authenticator $authenticator */ + $authenticator = app(Authenticator::class)->boot($request); - return view('auth.two-factor', compact('user', 'title')); + if ($authenticator->isAuthenticated()) { + // save MFA in preferences + $this->addToMFAHistory($mfaCode); + + // otp auth success! + return redirect(route('home')); + } + + // could be user has a backup code. + if ($this->isBackupCode($mfaCode)) { + $this->removeFromBackupCodes($mfaCode); + $authenticator->login(); + + session()->flash('info', trans('firefly.mfa_backup_code')); + + return redirect(route('home')); + } + + session()->flash('error', trans('firefly.wrong_mfa_code')); + + return redirect(route('home')); + } + + /** + * @param string $mfaCode + */ + private function addToMFAHistory(string $mfaCode): void + { + /** @var array $mfaHistory */ + $mfaHistory = Preferences::get('mfa_history', [])->data; + $entry = [ + 'time' => time(), + 'code' => $mfaCode, + ]; + $mfaHistory[] = $entry; + + Preferences::set('mfa_history', $mfaHistory); + $this->filterMFAHistory(); + } + + /** + * Remove old entries from the preferences array. + */ + private function filterMFAHistory(): void + { + /** @var array $mfaHistory */ + $mfaHistory = Preferences::get('mfa_history', [])->data; + $newHistory = []; + $now = time(); + foreach ($mfaHistory as $entry) { + $time = $entry['time']; + $code = $entry['code']; + if ($now - $time <= 300) { + $newHistory[] = [ + 'time' => $time, + 'code' => $code, + ]; + } + } + Preferences::set('mfa_history', $newHistory); } /** @@ -90,30 +143,54 @@ class TwoFactorController extends Controller } /** - * Submit 2FA code. + * Each MFA history has a timestamp and a code, saving the MFA entries for 5 minutes. So if the + * submitted MFA code has been submitted in the last 5 minutes, it won't work despite being valid. * - * @param TokenFormRequest $request - * @param CookieJar $cookieJar + * @param string $mfaCode + * @param array $mfaHistory * - * @return mixed + * @return bool */ - public function postIndex(TokenFormRequest $request, CookieJar $cookieJar) + private function inMFAHistory(string $mfaCode, array $mfaHistory): bool { - // wants to remember session? - $remember = $request->session()->get('remember_login') ?? false; - - $minutes = config('session.lifetime'); - if (true === $remember) { - // set cookie with a long lifetime (30 days) - $minutes = 43200; + $now = time(); + foreach ($mfaHistory as $entry) { + $time = $entry['time']; + $code = $entry['code']; + if ($code === $mfaCode && $now - $time <= 300) { + return true; + } } - $cookie = $cookieJar->make( - 'twoFactorAuthenticated', 'true', $minutes, config('session.path'), config('session.domain'), config('session.secure'), config('session.http_only') - ); - // whatever the case, forget about it: - $request->session()->forget('remember_login'); + return false; + } - return redirect(route('home'))->withCookie($cookie); + /** + * Checks if code is in users backup codes. + * + * @param string $mfaCode + * + * @return bool + */ + private function isBackupCode(string $mfaCode): bool + { + $list = Preferences::get('mfa_recovery', [])->data; + if (in_array($mfaCode, $list, true)) { + return true; + } + + return false; + } + + /** + * Remove the used code from the list of backup codes. + * + * @param string $mfaCode + */ + private function removeFromBackupCodes(string $mfaCode): void + { + $list = Preferences::get('mfa_recovery', [])->data; + $newList = array_values(array_diff($list, [$mfaCode])); + Preferences::set('mfa_recovery', $newList); } } diff --git a/app/Http/Controllers/BillController.php b/app/Http/Controllers/BillController.php index 2251706607..7c002a23a3 100644 --- a/app/Http/Controllers/BillController.php +++ b/app/Http/Controllers/BillController.php @@ -24,7 +24,7 @@ namespace FireflyIII\Http\Controllers; use Carbon\Carbon; use FireflyIII\Helpers\Attachments\AttachmentHelperInterface; -use FireflyIII\Helpers\Collector\TransactionCollectorInterface; +use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Http\Requests\BillFormRequest; use FireflyIII\Models\Attachment; use FireflyIII\Models\Bill; @@ -39,7 +39,6 @@ use League\Fractal\Manager; use League\Fractal\Resource\Item; use League\Fractal\Serializer\DataArraySerializer; use Symfony\Component\HttpFoundation\ParameterBag; -use URL; /** * Class BillController. @@ -55,6 +54,7 @@ class BillController extends Controller /** * BillController constructor. + * @codeCoverageIgnore */ public function __construct() { @@ -124,7 +124,7 @@ class BillController extends Controller * Destroy a bill. * * @param Request $request - * @param Bill $bill + * @param Bill $bill * * @return RedirectResponse|\Illuminate\Routing\Redirector */ @@ -143,7 +143,7 @@ class BillController extends Controller * Edit a bill. * * @param Request $request - * @param Bill $bill + * @param Bill $bill * * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View */ @@ -212,11 +212,6 @@ class BillController extends Controller return $return; } ); - $bills = $bills->sortBy( - function (array $bill) { - return (int)!$bill['active'] . strtolower($bill['name']); - } - ); // add info about rules: $rules = $this->billRepository->getRulesForBills($paginator->getCollection()); @@ -237,7 +232,7 @@ class BillController extends Controller * Rescan bills for transactions. * * @param Request $request - * @param Bill $bill + * @param Bill $bill * * @return RedirectResponse|\Illuminate\Routing\Redirector * @throws \FireflyIII\Exceptions\FireflyException @@ -258,7 +253,7 @@ class BillController extends Controller $matcher->setTriggeredLimit(100000); // large upper limit $matcher->setRule($rule); $matchingTransactions = $matcher->findTransactionsByRule(); - $total += $matchingTransactions->count(); + $total += count($matchingTransactions); $this->billRepository->linkCollectionToBill($bill, $matchingTransactions); } @@ -267,14 +262,14 @@ class BillController extends Controller app('preferences')->mark(); } - return redirect(URL::previous()); + return redirect(route('bills.show', [$bill->id])); } /** * Show a bill. * * @param Request $request - * @param Bill $bill + * @param Bill $bill * * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View */ @@ -309,29 +304,31 @@ class BillController extends Controller $object = $manager->createData($resource)->toArray(); $object['data']['currency'] = $bill->transactionCurrency; - // use collector: - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setAllAssetAccounts()->setBills(new Collection([$bill]))->setLimit($pageSize)->setPage($page)->withBudgetInformation() - ->withCategoryInformation(); - $transactions = $collector->getPaginatedTransactions(); - $transactions->setPath(route('bills.show', [$bill->id])); + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setBill($bill)->setLimit($pageSize)->setPage($page)->withBudgetInformation() + ->withCategoryInformation()->withAccountInformation(); + $groups = $collector->getPaginatedGroups(); + $groups->setPath(route('bills.show', [$bill->id])); // transform any attachments as well. $collection = $this->billRepository->getAttachments($bill); $attachments = new Collection; + + // @codeCoverageIgnoreStart if ($collection->count() > 0) { /** @var AttachmentTransformer $transformer */ $transformer = app(AttachmentTransformer::class); $attachments = $collection->each( - function (Attachment $attachment) use ($transformer) { + static function (Attachment $attachment) use ($transformer) { return $transformer->transform($attachment); } ); } + // @codeCoverageIgnoreEnd - return view('bills.show', compact('attachments', 'transactions', 'rules', 'yearAverage', 'overallAverage', 'year', 'object', 'bill', 'subTitle')); + return view('bills.show', compact('attachments', 'groups', 'rules', 'yearAverage', 'overallAverage', 'year', 'object', 'bill', 'subTitle')); } @@ -362,7 +359,7 @@ class BillController extends Controller $files = $request->hasFile('attachments') ? $request->file('attachments') : null; $this->attachments->saveAttachmentsForModel($bill, $files); - if (\count($this->attachments->getMessages()->get('attachments')) > 0) { + if (count($this->attachments->getMessages()->get('attachments')) > 0) { $request->session()->flash('info', $this->attachments->getMessages()->get('attachments')); // @codeCoverageIgnore } @@ -373,7 +370,7 @@ class BillController extends Controller * Update a bill. * * @param BillFormRequest $request - * @param Bill $bill + * @param Bill $bill * * @return RedirectResponse */ @@ -390,7 +387,7 @@ class BillController extends Controller $this->attachments->saveAttachmentsForModel($bill, $files); // flash messages - if (\count($this->attachments->getMessages()->get('attachments')) > 0) { + if (count($this->attachments->getMessages()->get('attachments')) > 0) { $request->session()->flash('info', $this->attachments->getMessages()->get('attachments')); // @codeCoverageIgnore } $redirect = redirect($this->getPreviousUri('bills.edit.uri')); diff --git a/app/Http/Controllers/Budget/AmountController.php b/app/Http/Controllers/Budget/AmountController.php index 1f760649b9..5fc6194a91 100644 --- a/app/Http/Controllers/Budget/AmountController.php +++ b/app/Http/Controllers/Budget/AmountController.php @@ -25,7 +25,7 @@ namespace FireflyIII\Http\Controllers\Budget; use Carbon\Carbon; -use FireflyIII\Helpers\Collector\TransactionCollectorInterface; +use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Http\Requests\BudgetIncomeRequest; use FireflyIII\Models\Budget; @@ -69,22 +69,25 @@ class AmountController extends Controller /** - * Set the amount for a single budget in a specific period. Shows a waring when its a lot. + * Set the amount for a single budget in a specific period. * - * @param Request $request - * @param BudgetRepositoryInterface $repository - * @param Budget $budget + * @param Request $request + * @param Budget $budget * * @return JsonResponse * * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ - public function amount(Request $request, BudgetRepositoryInterface $repository, Budget $budget): JsonResponse + public function amount(Request $request, Budget $budget): JsonResponse { // grab vars from URI $amount = (string)$request->get('amount'); - $start = Carbon::createFromFormat('Y-m-d', $request->get('start')); - $end = Carbon::createFromFormat('Y-m-d', $request->get('end')); + + /** @var Carbon $start */ + $start = Carbon::createFromFormat('Y-m-d', $request->get('start')); + + /** @var Carbon $end */ + $end = Carbon::createFromFormat('Y-m-d', $request->get('end')); // grab other useful vars $currency = app('amount')->getDefaultCurrency(); @@ -95,11 +98,11 @@ class AmountController extends Controller $budgetLimit = $this->repository->updateLimitAmount($budget, $start, $end, $amount); // calculate what the user has spent in current period. - $spent = $repository->spentInPeriod(new Collection([$budget]), new Collection, $start, $end); + $spent = $this->repository->spentInPeriod(new Collection([$budget]), new Collection, $start, $end); // given the new budget, this is what they have left (and left per day?) $left = app('amount')->formatAnything($currency, bcadd($amount, $spent), true); - $leftPerDay = null; // + $leftPerDay = null; // If the user budgets ANY amount per day for this budget (anything but zero) Firefly III calculates how much he could spend per day. if (1 === bccomp(bcadd($amount, $spent), '0')) { @@ -113,7 +116,7 @@ class AmountController extends Controller // If the difference is very large, give the user a notification. $average = $this->repository->budgetedPerDay($budget); $current = bcdiv($amount, (string)$periodLength); - if (bccomp(bcmul('1.1', $average), $current) === -1) { + if (bccomp(bcmul('1.3', $average), $current) === -1) { $largeDiff = true; $warnText = (string)trans( 'firefly.over_budget_warn', @@ -141,67 +144,6 @@ class AmountController extends Controller ); } - - /** - * Shows some basic info about the income and the suggested budget. - * - * @param Carbon $start - * @param Carbon $end - * - * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View - */ - public function infoIncome(Carbon $start, Carbon $end) - { - $range = app('preferences')->get('viewRange', '1M')->data; - /** @var Carbon $searchBegin */ - $searchBegin = app('navigation')->subtractPeriod($start, $range, 3); - $searchEnd = app('navigation')->addPeriod($end, $range, 3); - $daysInPeriod = $start->diffInDays($end); - $daysInSearchPeriod = $searchBegin->diffInDays($searchEnd); - $average = $this->repository->getAverageAvailable($start, $end); - $available = bcmul($average, (string)$daysInPeriod); - - Log::debug(sprintf('Average is %s, so total available is %s because days is %d.', $average, $available, $daysInPeriod)); - - // amount earned in this period: - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setAllAssetAccounts()->setRange($searchBegin, $searchEnd)->setTypes([TransactionType::DEPOSIT])->withOpposingAccount(); - $earned = (string)$collector->getTransactions()->sum('transaction_amount'); - // Total amount earned divided by the number of days in the whole search period is the average amount earned per day. - // This is multiplied by the number of days in the current period, showing you the average. - $earnedAverage = bcmul(bcdiv($earned, (string)$daysInSearchPeriod), (string)$daysInPeriod); - - Log::debug(sprintf('Earned is %s, earned average is %s', $earned, $earnedAverage)); - - // amount spent in period - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setAllAssetAccounts()->setRange($searchBegin, $searchEnd)->setTypes([TransactionType::WITHDRAWAL])->withOpposingAccount(); - $spent = (string)$collector->getTransactions()->sum('transaction_amount'); - $spentAverage = app('steam')->positive(bcmul(bcdiv($spent, (string)$daysInSearchPeriod), (string)$daysInPeriod)); - - Log::debug(sprintf('Spent is %s, spent average is %s', $earned, $earnedAverage)); - - // the default suggestion is the money the user has spent, on average, over this period. - $suggested = $spentAverage; - - Log::debug(sprintf('Suggested is now %s (spent average)', $suggested)); - - // if the user makes less per period, suggest that amount instead. - if (1 === bccomp($spentAverage, $earnedAverage)) { - Log::debug( - sprintf('Because earned average (%s) is less than spent average (%s) will suggest earned average instead.', $earnedAverage, $spentAverage) - ); - $suggested = $earnedAverage; - } - - $result = ['available' => $available, 'earned' => $earnedAverage, 'spent' => $spentAverage, 'suggested' => $suggested,]; - - return view('budgets.info', compact('result', 'searchBegin', 'searchEnd', 'start', 'end')); - } - - /** * Store an available budget for the current period. * @@ -211,7 +153,9 @@ class AmountController extends Controller */ public function postUpdateIncome(BudgetIncomeRequest $request): RedirectResponse { + /** @var Carbon $start */ $start = Carbon::createFromFormat('Y-m-d', $request->string('start')); + /** @var Carbon $end */ $end = Carbon::createFromFormat('Y-m-d', $request->string('end')); $defaultCurrency = app('amount')->getDefaultCurrency(); $amount = $request->get('amount'); @@ -227,8 +171,8 @@ class AmountController extends Controller * Shows the form to update available budget. * * @param Request $request - * @param Carbon $start - * @param Carbon $end + * @param Carbon $start + * @param Carbon $end * * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View */ @@ -239,6 +183,6 @@ class AmountController extends Controller $available = round($available, $defaultCurrency->decimal_places); $page = (int)$request->get('page'); - return view('budgets.income', compact('available', 'start', 'end', 'page')); + return view('budgets.income', compact('available', 'start', 'end', 'page','defaultCurrency')); } } diff --git a/app/Http/Controllers/Budget/CreateController.php b/app/Http/Controllers/Budget/CreateController.php index daa47876d9..eb930fba73 100644 --- a/app/Http/Controllers/Budget/CreateController.php +++ b/app/Http/Controllers/Budget/CreateController.php @@ -40,6 +40,7 @@ class CreateController extends Controller /** * CreateController constructor. + * @codeCoverageIgnore */ public function __construct() { diff --git a/app/Http/Controllers/Budget/DeleteController.php b/app/Http/Controllers/Budget/DeleteController.php index 36d3798390..0b913ed23d 100644 --- a/app/Http/Controllers/Budget/DeleteController.php +++ b/app/Http/Controllers/Budget/DeleteController.php @@ -40,6 +40,7 @@ class DeleteController extends Controller /** * DeleteController constructor. + * @codeCoverageIgnore */ public function __construct() { diff --git a/app/Http/Controllers/Budget/EditController.php b/app/Http/Controllers/Budget/EditController.php index f2d9398399..d74c8b7479 100644 --- a/app/Http/Controllers/Budget/EditController.php +++ b/app/Http/Controllers/Budget/EditController.php @@ -42,6 +42,7 @@ class EditController extends Controller /** * EditController constructor. + * @codeCoverageIgnore */ public function __construct() { diff --git a/app/Http/Controllers/Budget/IndexController.php b/app/Http/Controllers/Budget/IndexController.php index d9aa50ae96..6c52b522bd 100644 --- a/app/Http/Controllers/Budget/IndexController.php +++ b/app/Http/Controllers/Budget/IndexController.php @@ -45,6 +45,7 @@ class IndexController extends Controller /** * IndexController constructor. + * @codeCoverageIgnore */ public function __construct() { diff --git a/app/Http/Controllers/Budget/ShowController.php b/app/Http/Controllers/Budget/ShowController.php index 0c136764f9..1fb7370ac7 100644 --- a/app/Http/Controllers/Budget/ShowController.php +++ b/app/Http/Controllers/Budget/ShowController.php @@ -26,7 +26,7 @@ namespace FireflyIII\Http\Controllers\Budget; use Carbon\Carbon; use FireflyIII\Exceptions\FireflyException; -use FireflyIII\Helpers\Collector\TransactionCollectorInterface; +use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Models\Budget; use FireflyIII\Models\BudgetLimit; @@ -43,20 +43,22 @@ use Illuminate\Http\Request; class ShowController extends Controller { use PeriodOverview, AugumentData; + /** @var JournalRepositoryInterface */ + private $journalRepos; /** * ShowController constructor. + * + * @codeCoverageIgnore */ public function __construct() { parent::__construct(); - - app('view')->share('hideBudgets', true); - $this->middleware( function ($request, $next) { app('view')->share('title', (string)trans('firefly.budgets')); app('view')->share('mainTitleIcon', 'fa-tasks'); + $this->journalRepos = app(JournalRepositoryInterface::class); return $next($request); } @@ -66,7 +68,7 @@ class ShowController extends Controller /** * Show transactions without a budget. * - * @param Request $request + * @param Request $request * @param Carbon|null $start * @param Carbon|null $end * @@ -82,47 +84,48 @@ class ShowController extends Controller 'firefly.without_budget_between', ['start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)] ); - $periods = $this->getNoBudgetPeriodOverview($end); - $page = (int)$request->get('page'); - $pageSize = (int)app('preferences')->get('listPageSize', 50)->data; - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setAllAssetAccounts()->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])->setLimit($pageSize)->setPage($page) - ->withoutBudget()->withOpposingAccount(); - $transactions = $collector->getPaginatedTransactions(); - $transactions->setPath(route('budgets.no-budget')); + // get first journal ever to set off the budget period overview. + $first = $this->journalRepos->firstNull(); + $firstDate = null !== $first ? $first->date : $start; + $periods = $this->getNoBudgetPeriodOverview($firstDate, $end); + $page = (int)$request->get('page'); + $pageSize = (int)app('preferences')->get('listPageSize', 50)->data; - return view('budgets.no-budget', compact('transactions', 'subTitle', 'periods', 'start', 'end')); + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])->setLimit($pageSize)->setPage($page) + ->withoutBudget()->withAccountInformation(); + $groups = $collector->getPaginatedGroups(); + $groups->setPath(route('budgets.no-budget')); + + return view('budgets.no-budget', compact('groups', 'subTitle', 'periods', 'start', 'end')); } /** * Shows ALL transactions without a budget. * - * @param Request $request - * @param JournalRepositoryInterface $repository + * @param Request $request * * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View - * - * @SuppressWarnings(PHPMD.UnusedLocalVariable) */ - public function noBudgetAll(Request $request, JournalRepositoryInterface $repository) + public function noBudgetAll(Request $request) { $subTitle = (string)trans('firefly.all_journals_without_budget'); - $first = $repository->firstNull(); + $first = $this->journalRepos->firstNull(); $start = null === $first ? new Carbon : $first->date; $end = new Carbon; $page = (int)$request->get('page'); $pageSize = (int)app('preferences')->get('listPageSize', 50)->data; - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setAllAssetAccounts()->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])->setLimit($pageSize)->setPage($page) - ->withoutBudget()->withOpposingAccount(); - $transactions = $collector->getPaginatedTransactions(); - $transactions->setPath(route('budgets.no-budget')); + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])->setLimit($pageSize)->setPage($page) + ->withoutBudget()->withAccountInformation(); + $groups = $collector->getPaginatedGroups(); + $groups->setPath(route('budgets.no-budget')); - return view('budgets.no-budget', compact('transactions', 'subTitle', 'start', 'end')); + return view('budgets.no-budget', compact('groups', 'subTitle', 'start', 'end')); } @@ -130,7 +133,7 @@ class ShowController extends Controller * Show a single budget. * * @param Request $request - * @param Budget $budget + * @param Budget $budget * * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View */ @@ -145,22 +148,22 @@ class ShowController extends Controller $repetition = null; // collector: - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setAllAssetAccounts()->setRange($start, $end)->setBudget($budget)->setLimit($pageSize)->setPage($page)->withBudgetInformation(); - $transactions = $collector->getPaginatedTransactions(); - $transactions->setPath(route('budgets.show', [$budget->id])); + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setRange($start, $end)->setBudget($budget)->setLimit($pageSize)->setPage($page)->withBudgetInformation(); + $groups = $collector->getPaginatedGroups(); + $groups->setPath(route('budgets.show', [$budget->id])); $subTitle = (string)trans('firefly.all_journals_for_budget', ['name' => $budget->name]); - return view('budgets.show', compact('limits', 'budget', 'repetition', 'transactions', 'subTitle')); + return view('budgets.show', compact('limits', 'budget', 'repetition', 'groups', 'subTitle')); } /** * Show a single budget by a budget limit. * - * @param Request $request - * @param Budget $budget + * @param Request $request + * @param Budget $budget * @param BudgetLimit $budgetLimit * * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View @@ -169,7 +172,7 @@ class ShowController extends Controller public function showByBudgetLimit(Request $request, Budget $budget, BudgetLimit $budgetLimit) { if ($budgetLimit->budget->id !== $budget->id) { - throw new FireflyException('This budget limit is not part of this budget.'); + throw new FireflyException('This budget limit is not part of this budget.'); // @codeCoverageIgnore } $page = (int)$request->get('page'); @@ -184,17 +187,18 @@ class ShowController extends Controller ); // collector: - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setAllAssetAccounts()->setRange($budgetLimit->start_date, $budgetLimit->end_date) + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + + $collector->setRange($budgetLimit->start_date, $budgetLimit->end_date) ->setBudget($budget)->setLimit($pageSize)->setPage($page)->withBudgetInformation(); - $transactions = $collector->getPaginatedTransactions(); - $transactions->setPath(route('budgets.show', [$budget->id, $budgetLimit->id])); + $groups = $collector->getPaginatedGroups(); + $groups->setPath(route('budgets.show', [$budget->id, $budgetLimit->id])); /** @var Carbon $start */ $start = session('first', Carbon::now()->startOfYear()); $end = new Carbon; $limits = $this->getLimits($budget, $start, $end); - return view('budgets.show', compact('limits', 'budget', 'budgetLimit', 'transactions', 'subTitle')); + return view('budgets.show', compact('limits', 'budget', 'budgetLimit', 'groups', 'subTitle')); } } diff --git a/app/Http/Controllers/Category/CreateController.php b/app/Http/Controllers/Category/CreateController.php new file mode 100644 index 0000000000..f0cfb0a21a --- /dev/null +++ b/app/Http/Controllers/Category/CreateController.php @@ -0,0 +1,103 @@ +. + */ + +namespace FireflyIII\Http\Controllers\Category; + + +use FireflyIII\Http\Controllers\Controller; +use FireflyIII\Http\Requests\CategoryFormRequest; +use FireflyIII\Repositories\Category\CategoryRepositoryInterface; +use Illuminate\Http\Request; + +/** + * Class CreateController + */ +class CreateController extends Controller +{ + /** @var CategoryRepositoryInterface The category repository */ + private $repository; + + /** + * CategoryController constructor. + * @codeCoverageIgnore + */ + public function __construct() + { + parent::__construct(); + + $this->middleware( + function ($request, $next) { + app('view')->share('title', (string)trans('firefly.categories')); + app('view')->share('mainTitleIcon', 'fa-bar-chart'); + $this->repository = app(CategoryRepositoryInterface::class); + + return $next($request); + } + ); + } + + + /** + * Create category. + * + * @param Request $request + * + * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View + */ + public function create(Request $request) + { + if (true !== session('categories.create.fromStore')) { + $this->rememberPreviousUri('categories.create.uri'); + } + $request->session()->forget('categories.create.fromStore'); + $subTitle = (string)trans('firefly.create_new_category'); + + return view('categories.create', compact('subTitle')); + } + + + /** + * Store new category. + * + * @param CategoryFormRequest $request + * + * @return $this|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector + */ + public function store(CategoryFormRequest $request) + { + $data = $request->getCategoryData(); + $category = $this->repository->store($data); + + $request->session()->flash('success', (string)trans('firefly.stored_category', ['name' => $category->name])); + app('preferences')->mark(); + + $redirect = redirect(route('categories.index')); + if (1 === (int)$request->get('create_another')) { + // @codeCoverageIgnoreStart + $request->session()->put('categories.create.fromStore', true); + + $redirect = redirect(route('categories.create'))->withInput(); + // @codeCoverageIgnoreEnd + } + + return $redirect; + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Category/DeleteController.php b/app/Http/Controllers/Category/DeleteController.php new file mode 100644 index 0000000000..44cd8773c6 --- /dev/null +++ b/app/Http/Controllers/Category/DeleteController.php @@ -0,0 +1,92 @@ +. + */ + +namespace FireflyIII\Http\Controllers\Category; + + +use FireflyIII\Http\Controllers\Controller; +use FireflyIII\Models\Category; +use FireflyIII\Repositories\Category\CategoryRepositoryInterface; +use Illuminate\Http\Request; + +/** + * Class DeleteController + */ +class DeleteController extends Controller +{ + /** @var CategoryRepositoryInterface The category repository */ + private $repository; + + /** + * CategoryController constructor. + * @codeCoverageIgnore + */ + public function __construct() + { + parent::__construct(); + + $this->middleware( + function ($request, $next) { + app('view')->share('title', (string)trans('firefly.categories')); + app('view')->share('mainTitleIcon', 'fa-bar-chart'); + $this->repository = app(CategoryRepositoryInterface::class); + + return $next($request); + } + ); + } + + /** + * Delete a category. + * + * @param Category $category + * + * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View + */ + public function delete(Category $category) + { + $subTitle = (string)trans('firefly.delete_category', ['name' => $category->name]); + + // put previous url in session + $this->rememberPreviousUri('categories.delete.uri'); + + return view('categories.delete', compact('category', 'subTitle')); + } + + /** + * Destroy a category. + * + * @param Request $request + * @param Category $category + * + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector + */ + public function destroy(Request $request, Category $category) + { + $name = $category->name; + $this->repository->destroy($category); + + $request->session()->flash('success', (string)trans('firefly.deleted_category', ['name' => $name])); + app('preferences')->mark(); + + return redirect($this->getPreviousUri('categories.delete.uri')); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Category/EditController.php b/app/Http/Controllers/Category/EditController.php new file mode 100644 index 0000000000..76991b0369 --- /dev/null +++ b/app/Http/Controllers/Category/EditController.php @@ -0,0 +1,109 @@ +. + */ + +namespace FireflyIII\Http\Controllers\Category; + + +use FireflyIII\Http\Controllers\Controller; +use FireflyIII\Http\Requests\CategoryFormRequest; +use FireflyIII\Models\Category; +use FireflyIII\Repositories\Category\CategoryRepositoryInterface; +use Illuminate\Http\Request; + +/** + * Class EditController + */ +class EditController extends Controller +{ + + /** @var CategoryRepositoryInterface The category repository */ + private $repository; + + /** + * CategoryController constructor. + * @codeCoverageIgnore + */ + public function __construct() + { + parent::__construct(); + + $this->middleware( + function ($request, $next) { + app('view')->share('title', (string)trans('firefly.categories')); + app('view')->share('mainTitleIcon', 'fa-bar-chart'); + $this->repository = app(CategoryRepositoryInterface::class); + + return $next($request); + } + ); + } + + + /** + * Edit a category. + * + * @param Request $request + * @param Category $category + * + * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View + */ + public function edit(Request $request, Category $category) + { + $subTitle = (string)trans('firefly.edit_category', ['name' => $category->name]); + + // put previous url in session if not redirect from store (not "return_to_edit"). + if (true !== session('categories.edit.fromUpdate')) { + $this->rememberPreviousUri('categories.edit.uri'); + } + $request->session()->forget('categories.edit.fromUpdate'); + + return view('categories.edit', compact('category', 'subTitle')); + } + + /** + * Update category. + * + * @param CategoryFormRequest $request + * @param Category $category + * + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector + */ + public function update(CategoryFormRequest $request, Category $category) + { + $data = $request->getCategoryData(); + $this->repository->update($category, $data); + + $request->session()->flash('success', (string)trans('firefly.updated_category', ['name' => $category->name])); + app('preferences')->mark(); + + $redirect = redirect($this->getPreviousUri('categories.edit.uri')); + + if (1 === (int)$request->get('return_to_edit')) { + // @codeCoverageIgnoreStart + $request->session()->put('categories.edit.fromUpdate', true); + + $redirect = redirect(route('categories.edit', [$category->id])); + // @codeCoverageIgnoreEnd + } + + return $redirect; + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Category/IndexController.php b/app/Http/Controllers/Category/IndexController.php new file mode 100644 index 0000000000..cd39646a2c --- /dev/null +++ b/app/Http/Controllers/Category/IndexController.php @@ -0,0 +1,89 @@ +. + */ + +namespace FireflyIII\Http\Controllers\Category; + + +use FireflyIII\Http\Controllers\Controller; +use FireflyIII\Models\Category; +use FireflyIII\Repositories\Category\CategoryRepositoryInterface; +use Illuminate\Http\Request; +use Illuminate\Pagination\LengthAwarePaginator; +use Illuminate\Support\Collection; + +/** + * Class IndexController + */ +class IndexController extends Controller +{ + /** @var CategoryRepositoryInterface The category repository */ + private $repository; + + /** + * CategoryController constructor. + * @codeCoverageIgnore + */ + public function __construct() + { + parent::__construct(); + + $this->middleware( + function ($request, $next) { + app('view')->share('title', (string)trans('firefly.categories')); + app('view')->share('mainTitleIcon', 'fa-bar-chart'); + $this->repository = app(CategoryRepositoryInterface::class); + + return $next($request); + } + ); + } + + + + /** + * Show all categories. + * + * @param Request $request + * + * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View + */ + public function index(Request $request) + { + $page = 0 === (int)$request->get('page') ? 1 : (int)$request->get('page'); + $pageSize = (int)app('preferences')->get('listPageSize', 50)->data; + $collection = $this->repository->getCategories(); + $total = $collection->count(); + $collection = $collection->slice(($page - 1) * $pageSize, $pageSize); + + $collection->each( + function (Category $category) { + $category->lastActivity = $this->repository->lastUseDate($category, new Collection); + } + ); + + // paginate categories + $categories = new LengthAwarePaginator($collection, $total, $pageSize, $page); + $categories->setPath(route('categories.index')); + + return view('categories.index', compact('categories')); + } + +} \ No newline at end of file diff --git a/app/Http/Controllers/Category/NoCategoryController.php b/app/Http/Controllers/Category/NoCategoryController.php index 8dbc24f45f..aa8df71950 100644 --- a/app/Http/Controllers/Category/NoCategoryController.php +++ b/app/Http/Controllers/Category/NoCategoryController.php @@ -25,8 +25,7 @@ namespace FireflyIII\Http\Controllers\Category; use Carbon\Carbon; -use FireflyIII\Helpers\Collector\TransactionCollectorInterface; -use FireflyIII\Helpers\Filter\InternalTransferFilter; +use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Journal\JournalRepositoryInterface; @@ -47,6 +46,7 @@ class NoCategoryController extends Controller /** * CategoryController constructor. + * @codeCoverageIgnore */ public function __construct() { @@ -66,7 +66,7 @@ class NoCategoryController extends Controller /** * Show transactions without a category. * - * @param Request $request + * @param Request $request * @param Carbon|null $start * @param Carbon|null $end * @@ -90,15 +90,15 @@ class NoCategoryController extends Controller Log::debug(sprintf('Start for noCategory() is %s', $start->format('Y-m-d'))); Log::debug(sprintf('End for noCategory() is %s', $end->format('Y-m-d'))); - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setAllAssetAccounts()->setRange($start, $end)->setLimit($pageSize)->setPage($page)->withoutCategory()->withOpposingAccount() + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setRange($start, $end) + ->setLimit($pageSize)->setPage($page)->withoutCategory() ->setTypes([TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::TRANSFER]); - $collector->removeFilter(InternalTransferFilter::class); - $transactions = $collector->getPaginatedTransactions(); - $transactions->setPath(route('categories.no-category')); + $groups = $collector->getPaginatedGroups(); + $groups->setPath(route('categories.no-category')); - return view('categories.no-category', compact('transactions', 'subTitle', 'periods', 'start', 'end')); + return view('categories.no-category', compact('groups', 'subTitle', 'periods', 'start', 'end')); } @@ -125,14 +125,13 @@ class NoCategoryController extends Controller Log::debug(sprintf('Start for noCategory() is %s', $start->format('Y-m-d'))); Log::debug(sprintf('End for noCategory() is %s', $end->format('Y-m-d'))); - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setAllAssetAccounts()->setRange($start, $end)->setLimit($pageSize)->setPage($page)->withoutCategory()->withOpposingAccount() + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setRange($start, $end)->setLimit($pageSize)->setPage($page)->withoutCategory() ->setTypes([TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::TRANSFER]); - $collector->removeFilter(InternalTransferFilter::class); - $transactions = $collector->getPaginatedTransactions(); - $transactions->setPath(route('categories.no-category.all')); + $groups = $collector->getPaginatedGroups(); + $groups->setPath(route('categories.no-category.all')); - return view('categories.no-category', compact('transactions', 'subTitle', 'periods', 'start', 'end')); + return view('categories.no-category', compact('groups', 'subTitle', 'periods', 'start', 'end')); } } diff --git a/app/Http/Controllers/Category/ShowController.php b/app/Http/Controllers/Category/ShowController.php index 79c552729a..f17e13eab0 100644 --- a/app/Http/Controllers/Category/ShowController.php +++ b/app/Http/Controllers/Category/ShowController.php @@ -24,8 +24,7 @@ declare(strict_types=1); namespace FireflyIII\Http\Controllers\Category; use Carbon\Carbon; -use FireflyIII\Helpers\Collector\TransactionCollectorInterface; -use FireflyIII\Helpers\Filter\InternalTransferFilter; +use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Models\Category; use FireflyIII\Repositories\Category\CategoryRepositoryInterface; @@ -48,6 +47,7 @@ class ShowController extends Controller /** * CategoryController constructor. + * @codeCoverageIgnore */ public function __construct() { @@ -69,8 +69,8 @@ class ShowController extends Controller /** * Show a single category. * - * @param Request $request - * @param Category $category + * @param Request $request + * @param Category $category * @param Carbon|null $start * @param Carbon|null $end * @@ -78,7 +78,7 @@ class ShowController extends Controller */ public function show(Request $request, Category $category, Carbon $start = null, Carbon $end = null) { - Log::debug('Now in show()'); + //Log::debug('Now in show()'); /** @var Carbon $start */ $start = $start ?? session('start', Carbon::now()->startOfMonth()); /** @var Carbon $end */ @@ -86,7 +86,8 @@ class ShowController extends Controller $subTitleIcon = 'fa-bar-chart'; $page = (int)$request->get('page'); $pageSize = (int)app('preferences')->get('listPageSize', 50)->data; - $periods = $this->getCategoryPeriodOverview($category, $end); + $oldest = $this->repository->firstUseDate($category) ?? Carbon::create()->startOfYear(); + $periods = $this->getCategoryPeriodOverview($category, $oldest, $end); $path = route('categories.show', [$category->id, $start->format('Y-m-d'), $end->format('Y-m-d')]); $subTitle = trans( 'firefly.journals_in_period_for_category', @@ -94,23 +95,24 @@ class ShowController extends Controller 'end' => $end->formatLocalized($this->monthAndDayFormat),] ); - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setAllAssetAccounts()->setRange($start, $end)->setLimit($pageSize)->setPage($page)->withOpposingAccount() + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setRange($start, $end)->setLimit($pageSize)->setPage($page) + ->withAccountInformation() ->setCategory($category)->withBudgetInformation()->withCategoryInformation(); - $collector->removeFilter(InternalTransferFilter::class); - $transactions = $collector->getPaginatedTransactions(); - $transactions->setPath($path); - Log::debug('End of show()'); + $groups = $collector->getPaginatedGroups(); + $groups->setPath($path); - return view('categories.show', compact('category', 'transactions', 'periods', 'subTitle', 'subTitleIcon', 'start', 'end')); + //Log::debug('End of show()'); + + return view('categories.show', compact('category', 'groups', 'periods', 'subTitle', 'subTitleIcon', 'start', 'end')); } /** * Show all transactions within a category. * - * @param Request $request + * @param Request $request * @param Category $category * * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View @@ -133,14 +135,15 @@ class ShowController extends Controller $path = route('categories.show.all', [$category->id]); - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setAllAssetAccounts()->setRange($start, $end)->setLimit($pageSize)->setPage($page)->withOpposingAccount() + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setRange($start, $end)->setLimit($pageSize)->setPage($page) + ->withAccountInformation() ->setCategory($category)->withBudgetInformation()->withCategoryInformation(); - $collector->removeFilter(InternalTransferFilter::class); - $transactions = $collector->getPaginatedTransactions(); - $transactions->setPath($path); - return view('categories.show', compact('category', 'transactions', 'periods', 'subTitle', 'subTitleIcon', 'start', 'end')); + $groups = $collector->getPaginatedGroups(); + $groups->setPath($path); + + return view('categories.show', compact('category', 'groups', 'periods', 'subTitle', 'subTitleIcon', 'start', 'end')); } } diff --git a/app/Http/Controllers/CategoryController.php b/app/Http/Controllers/CategoryController.php deleted file mode 100644 index c051b36b1c..0000000000 --- a/app/Http/Controllers/CategoryController.php +++ /dev/null @@ -1,222 +0,0 @@ -. - */ -declare(strict_types=1); - -namespace FireflyIII\Http\Controllers; - -use FireflyIII\Http\Requests\CategoryFormRequest; -use FireflyIII\Models\Category; -use FireflyIII\Repositories\Category\CategoryRepositoryInterface; -use Illuminate\Http\Request; -use Illuminate\Pagination\LengthAwarePaginator; -use Illuminate\Support\Collection; - -/** - * Class CategoryController. - */ -class CategoryController extends Controller -{ - /** @var CategoryRepositoryInterface The category repository */ - private $repository; - - /** - * CategoryController constructor. - */ - public function __construct() - { - parent::__construct(); - - $this->middleware( - function ($request, $next) { - app('view')->share('title', (string)trans('firefly.categories')); - app('view')->share('mainTitleIcon', 'fa-bar-chart'); - $this->repository = app(CategoryRepositoryInterface::class); - - return $next($request); - } - ); - } - - /** - * Create category. - * - * @param Request $request - * - * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View - */ - public function create(Request $request) - { - if (true !== session('categories.create.fromStore')) { - $this->rememberPreviousUri('categories.create.uri'); - } - $request->session()->forget('categories.create.fromStore'); - $subTitle = (string)trans('firefly.create_new_category'); - - return view('categories.create', compact('subTitle')); - } - - /** - * Delete a category. - * - * @param Category $category - * - * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View - */ - public function delete(Category $category) - { - $subTitle = (string)trans('firefly.delete_category', ['name' => $category->name]); - - // put previous url in session - $this->rememberPreviousUri('categories.delete.uri'); - - return view('categories.delete', compact('category', 'subTitle')); - } - - /** - * Destroy a category. - * - * @param Request $request - * @param Category $category - * - * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector - */ - public function destroy(Request $request, Category $category) - { - $name = $category->name; - $this->repository->destroy($category); - - $request->session()->flash('success', (string)trans('firefly.deleted_category', ['name' => $name])); - app('preferences')->mark(); - - return redirect($this->getPreviousUri('categories.delete.uri')); - } - - /** - * Edit a category. - * - * @param Request $request - * @param Category $category - * - * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View - */ - public function edit(Request $request, Category $category) - { - $subTitle = (string)trans('firefly.edit_category', ['name' => $category->name]); - - // put previous url in session if not redirect from store (not "return_to_edit"). - if (true !== session('categories.edit.fromUpdate')) { - $this->rememberPreviousUri('categories.edit.uri'); - } - $request->session()->forget('categories.edit.fromUpdate'); - - return view('categories.edit', compact('category', 'subTitle')); - } - - /** - * Show all categories. - * - * @param Request $request - * - * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View - */ - public function index(Request $request) - { - $page = 0 === (int)$request->get('page') ? 1 : (int)$request->get('page'); - $pageSize = (int)app('preferences')->get('listPageSize', 50)->data; - $collection = $this->repository->getCategories(); - $total = $collection->count(); - $collection = $collection->slice(($page - 1) * $pageSize, $pageSize); - - $collection->each( - function (Category $category) { - $category->lastActivity = $this->repository->lastUseDate($category, new Collection); - } - ); - - // paginate categories - $categories = new LengthAwarePaginator($collection, $total, $pageSize, $page); - $categories->setPath(route('categories.index')); - - return view('categories.index', compact('categories')); - } - - - /** - * Store new category. - * - * @param CategoryFormRequest $request - * @param CategoryRepositoryInterface $repository - * - * @return $this|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector - */ - public function store(CategoryFormRequest $request, CategoryRepositoryInterface $repository) - { - $data = $request->getCategoryData(); - $category = $repository->store($data); - - $request->session()->flash('success', (string)trans('firefly.stored_category', ['name' => $category->name])); - app('preferences')->mark(); - - $redirect = redirect(route('categories.index')); - if (1 === (int)$request->get('create_another')) { - // @codeCoverageIgnoreStart - $request->session()->put('categories.create.fromStore', true); - - $redirect = redirect(route('categories.create'))->withInput(); - // @codeCoverageIgnoreEnd - } - - return $redirect; - } - - - /** - * Update category. - * - * @param CategoryFormRequest $request - * @param CategoryRepositoryInterface $repository - * @param Category $category - * - * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector - */ - public function update(CategoryFormRequest $request, CategoryRepositoryInterface $repository, Category $category) - { - $data = $request->getCategoryData(); - $repository->update($category, $data); - - $request->session()->flash('success', (string)trans('firefly.updated_category', ['name' => $category->name])); - app('preferences')->mark(); - - $redirect = redirect($this->getPreviousUri('categories.edit.uri')); - - if (1 === (int)$request->get('return_to_edit')) { - // @codeCoverageIgnoreStart - $request->session()->put('categories.edit.fromUpdate', true); - - $redirect = redirect(route('categories.edit', [$category->id])); - // @codeCoverageIgnoreEnd - } - - return $redirect; - } - - -} diff --git a/app/Http/Controllers/Chart/AccountController.php b/app/Http/Controllers/Chart/AccountController.php index 24d4af028f..80987ec48f 100644 --- a/app/Http/Controllers/Chart/AccountController.php +++ b/app/Http/Controllers/Chart/AccountController.php @@ -24,11 +24,10 @@ namespace FireflyIII\Http\Controllers\Chart; use Carbon\Carbon; use FireflyIII\Generator\Chart\Basic\GeneratorInterface; -use FireflyIII\Helpers\Collector\TransactionCollectorInterface; +use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Models\Account; use FireflyIII\Models\AccountType; -use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionCurrency; use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Account\AccountRepositoryInterface; @@ -63,6 +62,7 @@ class AccountController extends Controller /** * AccountController constructor. + * @codeCoverageIgnore */ public function __construct() { @@ -147,7 +147,7 @@ class AccountController extends Controller // loop all found currencies and build the data array for the chart. /** - * @var int $currencyId + * @var int $currencyId * @var TransactionCurrency $currency */ foreach ($currencies as $currencyId => $currency) { @@ -174,13 +174,28 @@ class AccountController extends Controller return response()->json($data); } + /** + * Expenses per budget for all time, as shown on account overview. + * + * @param AccountRepositoryInterface $repository + * @param Account $account + * + * @return JsonResponse + */ + public function expenseBudgetAll(AccountRepositoryInterface $repository, Account $account): JsonResponse + { + $start = $repository->oldestJournalDate($account) ?? Carbon::now()->startOfMonth(); + $end = Carbon::now(); + + return $this->expenseBudget($account, $start, $end); + } /** * Expenses per budget, as shown on account overview. * * @param Account $account - * @param Carbon $start - * @param Carbon $end + * @param Carbon $start + * @param Carbon $end * * @return JsonResponse */ @@ -194,30 +209,28 @@ class AccountController extends Controller if ($cache->has()) { return response()->json($cache->get()); // @codeCoverageIgnore } - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); $collector->setAccounts(new Collection([$account]))->setRange($start, $end)->withBudgetInformation()->setTypes([TransactionType::WITHDRAWAL]); - $transactions = $collector->getTransactions(); - $chartData = []; - $result = []; - $budgetIds = []; - /** @var Transaction $transaction */ - foreach ($transactions as $transaction) { - $jrnlBudgetId = (int)$transaction->transaction_journal_budget_id; - $transBudgetId = (int)$transaction->transaction_budget_id; - $currencyName = $transaction->transaction_currency_name; - $budgetId = max($jrnlBudgetId, $transBudgetId); - $combi = $budgetId . $currencyName; - $budgetIds[] = $budgetId; + $journals = $collector->getExtractedJournals(); + $chartData = []; + $result = []; + $budgetIds = []; + /** @var array $journal */ + foreach ($journals as $journal) { + $currencyName = $journal['currency_name']; + $budgetId = (int)$journal['budget_id']; + $combi = $budgetId . $currencyName; + $budgetIds[] = $budgetId; if (!isset($result[$combi])) { $result[$combi] = [ 'total' => '0', 'budget_id' => $budgetId, 'currency' => $currencyName, - 'currency_symbol' => $transaction->transaction_currency_symbol, + 'currency_symbol' => $journal['currency_symbol'], ]; } - $result[$combi]['total'] = bcadd($transaction->transaction_amount, $result[$combi]['total']); + $result[$combi]['total'] = bcadd($journal['amount'], $result[$combi]['total']); } $names = $this->getBudgetNames($budgetIds); @@ -236,28 +249,27 @@ class AccountController extends Controller } /** - * Expenses per budget for all time, as shown on account overview. + * Expenses grouped by category for account. * * @param AccountRepositoryInterface $repository - * @param Account $account + * @param Account $account * * @return JsonResponse */ - public function expenseBudgetAll(AccountRepositoryInterface $repository, Account $account): JsonResponse + public function expenseCategoryAll(AccountRepositoryInterface $repository, Account $account): JsonResponse { $start = $repository->oldestJournalDate($account) ?? Carbon::now()->startOfMonth(); $end = Carbon::now(); - return $this->expenseBudget($account, $start, $end); + return $this->expenseCategory($account, $start, $end); } - /** * Expenses per category for one single account. * * @param Account $account - * @param Carbon $start - * @param Carbon $end + * @param Carbon $start + * @param Carbon $end * * @return JsonResponse */ @@ -272,20 +284,18 @@ class AccountController extends Controller return response()->json($cache->get()); // @codeCoverageIgnore } - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); $collector->setAccounts(new Collection([$account]))->setRange($start, $end)->withCategoryInformation()->setTypes([TransactionType::WITHDRAWAL]); - $transactions = $collector->getTransactions(); - $result = []; - $chartData = []; - $categoryIds = []; + $journals = $collector->getExtractedJournals(); + $result = []; + $chartData = []; + $categoryIds = []; - /** @var Transaction $transaction */ - foreach ($transactions as $transaction) { - $jrnlCatId = (int)$transaction->transaction_journal_category_id; - $transCatId = (int)$transaction->transaction_category_id; - $currencyName = $transaction->transaction_currency_name; - $categoryId = max($jrnlCatId, $transCatId); + /** @var array $journal */ + foreach ($journals as $journal) { + $currencyName = $journal['currency_name']; + $categoryId = $journal['category_id']; $combi = $categoryId . $currencyName; $categoryIds[] = $categoryId; if (!isset($result[$combi])) { @@ -293,10 +303,10 @@ class AccountController extends Controller 'total' => '0', 'category_id' => $categoryId, 'currency' => $currencyName, - 'currency_symbol' => $transaction->transaction_currency_symbol, + 'currency_symbol' => $journal['currency_symbol'], ]; } - $result[$combi]['total'] = bcadd($transaction->transaction_amount, $result[$combi]['total']); + $result[$combi]['total'] = bcadd($journal['amount'], $result[$combi]['total']); } $names = $this->getCategoryNames($categoryIds); @@ -314,23 +324,6 @@ class AccountController extends Controller return response()->json($data); } - /** - * Expenses grouped by category for account. - * - * @param AccountRepositoryInterface $repository - * @param Account $account - * - * @return JsonResponse - */ - public function expenseCategoryAll(AccountRepositoryInterface $repository, Account $account): JsonResponse - { - $start = $repository->oldestJournalDate($account) ?? Carbon::now()->startOfMonth(); - $end = Carbon::now(); - - return $this->expenseCategory($account, $start, $end); - } - - /** * Shows the balances for all the user's frontpage accounts. * @@ -348,23 +341,37 @@ class AccountController extends Controller Log::debug('Frontpage preference set is ', $frontPage->data); - if (0 === \count($frontPage->data)) { - $frontPage->data = $defaultSet; + if (0 === count($frontPage->data)) { + app('preferences')->set('frontPageAccounts', $defaultSet); Log::debug('frontpage set is empty!'); - $frontPage->save(); } $accounts = $repository->getAccountsById($frontPage->data); return response()->json($this->accountBalanceChart($accounts, $start, $end)); } + /** + * Shows the income grouped by category for an account, in all time. + * + * @param AccountRepositoryInterface $repository + * @param Account $account + * + * @return JsonResponse + */ + public function incomeCategoryAll(AccountRepositoryInterface $repository, Account $account): JsonResponse + { + $start = $repository->oldestJournalDate($account) ?? Carbon::now()->startOfMonth(); + $end = Carbon::now(); + + return $this->incomeCategory($account, $start, $end); + } /** * Shows all income per account for each category. * * @param Account $account - * @param Carbon $start - * @param Carbon $end + * @param Carbon $start + * @param Carbon $end * * @return JsonResponse */ @@ -380,19 +387,18 @@ class AccountController extends Controller } // grab all journals: - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setAccounts(new Collection([$account]))->setRange($start, $end)->withCategoryInformation()->setTypes([TransactionType::DEPOSIT]); - $transactions = $collector->getTransactions(); - $result = []; - $chartData = []; - $categoryIds = []; - /** @var Transaction $transaction */ - foreach ($transactions as $transaction) { - $jrnlCatId = (int)$transaction->transaction_journal_category_id; - $transCatId = (int)$transaction->transaction_category_id; - $categoryId = max($jrnlCatId, $transCatId); - $currencyName = $transaction->transaction_currency_name; + $journals = $collector->getExtractedJournals(); + $result = []; + $chartData = []; + $categoryIds = []; + /** @var array $journal */ + foreach ($journals as $journal) { + $categoryId = $journal['category_id']; + $currencyName = $journal['currency_name']; $combi = $categoryId . $currencyName; $categoryIds[] = $categoryId; if (!isset($result[$combi])) { @@ -400,10 +406,10 @@ class AccountController extends Controller 'total' => '0', 'category_id' => $categoryId, 'currency' => $currencyName, - 'currency_symbol' => $transaction->transaction_currency_symbol, + 'currency_symbol' => $journal['currency_symbol'], ]; } - $result[$combi]['total'] = bcadd($transaction->transaction_amount, $result[$combi]['total']); + $result[$combi]['total'] = bcadd($journal['amount'], $result[$combi]['total']); } $names = $this->getCategoryNames($categoryIds); @@ -419,32 +425,15 @@ class AccountController extends Controller return response()->json($data); } - /** - * Shows the income grouped by category for an account, in all time. - * - * @param AccountRepositoryInterface $repository - * @param Account $account - * - * @return JsonResponse - */ - public function incomeCategoryAll(AccountRepositoryInterface $repository, Account $account): JsonResponse - { - $start = $repository->oldestJournalDate($account) ?? Carbon::now()->startOfMonth(); - $end = Carbon::now(); - - return $this->incomeCategory($account, $start, $end); - } - - /** * Shows overview of account during a single period. * * TODO this chart is not multi-currency aware. * * @param Account $account - * @param Carbon $start + * @param Carbon $start * - * @param Carbon $end + * @param Carbon $end * * @return JsonResponse * @SuppressWarnings(PHPMD.CyclomaticComplexity) @@ -502,8 +491,8 @@ class AccountController extends Controller * * TODO this chart is not multi-currency aware. * - * @param Carbon $start - * @param Carbon $end + * @param Carbon $start + * @param Carbon $end * @param Collection $accounts * * @return JsonResponse @@ -579,7 +568,7 @@ class AccountController extends Controller // loop all found currencies and build the data array for the chart. /** - * @var int $currencyId + * @var int $currencyId * @var TransactionCurrency $currency */ foreach ($currencies as $currencyId => $currency) { diff --git a/app/Http/Controllers/Chart/BillController.php b/app/Http/Controllers/Chart/BillController.php index e0af3ad201..bd079cb551 100644 --- a/app/Http/Controllers/Chart/BillController.php +++ b/app/Http/Controllers/Chart/BillController.php @@ -24,15 +24,13 @@ namespace FireflyIII\Http\Controllers\Chart; use Carbon\Carbon; use FireflyIII\Generator\Chart\Basic\GeneratorInterface; -use FireflyIII\Helpers\Collector\TransactionCollectorInterface; +use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Models\Bill; -use FireflyIII\Models\Transaction; use FireflyIII\Repositories\Bill\BillRepositoryInterface; use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; use FireflyIII\Support\CacheProperties; use Illuminate\Http\JsonResponse; -use Illuminate\Support\Collection; /** * Class BillController. @@ -44,6 +42,7 @@ class BillController extends Controller /** * BillController constructor. + * @codeCoverageIgnore */ public function __construct() { @@ -99,12 +98,11 @@ class BillController extends Controller /** * Shows overview for a single bill. * - * @param TransactionCollectorInterface $collector - * @param Bill $bill + * @param Bill $bill * * @return JsonResponse */ - public function single(TransactionCollectorInterface $collector, Bill $bill): JsonResponse + public function single(Bill $bill): JsonResponse { $cache = new CacheProperties; $cache->addProperty('chart.bill.single'); @@ -113,22 +111,18 @@ class BillController extends Controller return response()->json($cache->get()); // @codeCoverageIgnore } - $results = $collector->setAllAssetAccounts()->setBills(new Collection([$bill]))->getTransactions(); - /** @var Collection $results */ - $results = $results->sortBy( - function (Transaction $transaction) { - return $transaction->date->format('U'); - } - ); + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $journals = $collector->setBill($bill)->getExtractedJournals(); + $chartData = [ ['type' => 'bar', 'label' => (string)trans('firefly.min-amount'), 'currency_symbol' => $bill->transactionCurrency->symbol, 'entries' => []], ['type' => 'bar', 'label' => (string)trans('firefly.max-amount'), 'currency_symbol' => $bill->transactionCurrency->symbol, 'entries' => []], ['type' => 'line', 'label' => (string)trans('firefly.journal-amount'), 'currency_symbol' => $bill->transactionCurrency->symbol, 'entries' => []], ]; - /** @var Transaction $entry */ - foreach ($results as $entry) { - $date = $entry->date->formatLocalized((string)trans('config.month_and_day')); + foreach ($journals as $journal) { + $date = $journal['date']->formatLocalized((string)trans('config.month_and_day')); $chartData[0]['entries'][$date] = $bill->amount_min; // minimum amount of bill $chartData[1]['entries'][$date] = $bill->amount_max; // maximum amount of bill @@ -136,7 +130,7 @@ class BillController extends Controller if (!isset($chartData[2]['entries'][$date])) { $chartData[2]['entries'][$date] = '0'; } - $amount = bcmul($entry->transaction_amount, '-1'); + $amount = bcmul($journal['amount'], '-1'); $chartData[2]['entries'][$date] = bcadd($chartData[2]['entries'][$date], $amount); // amount of journal } diff --git a/app/Http/Controllers/Chart/BudgetController.php b/app/Http/Controllers/Chart/BudgetController.php index 8677cbc236..3fe456e5ae 100644 --- a/app/Http/Controllers/Chart/BudgetController.php +++ b/app/Http/Controllers/Chart/BudgetController.php @@ -25,11 +25,10 @@ namespace FireflyIII\Http\Controllers\Chart; use Carbon\Carbon; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Generator\Chart\Basic\GeneratorInterface; -use FireflyIII\Helpers\Collector\TransactionCollectorInterface; +use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Models\Budget; use FireflyIII\Models\BudgetLimit; -use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Support\CacheProperties; @@ -56,6 +55,7 @@ class BudgetController extends Controller /** * BudgetController constructor. + * @codeCoverageIgnore */ public function __construct() { @@ -128,7 +128,7 @@ class BudgetController extends Controller * * TODO this chart is not multi-currency aware. * - * @param Budget $budget + * @param Budget $budget * @param BudgetLimit $budgetLimit * * @return JsonResponse @@ -177,7 +177,7 @@ class BudgetController extends Controller * * TODO this chart is not multi-currency aware. * - * @param Budget $budget + * @param Budget $budget * @param BudgetLimit|null $budgetLimit * * @return JsonResponse @@ -195,21 +195,20 @@ class BudgetController extends Controller return response()->json($cache->get()); // @codeCoverageIgnore } - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setAllAssetAccounts()->setBudget($budget); + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setBudget($budget); if (null !== $budgetLimit) { $collector->setRange($budgetLimit->start_date, $budgetLimit->end_date); } - $transactions = $collector->getTransactions(); - $result = []; - $chartData = []; - /** @var Transaction $transaction */ - foreach ($transactions as $transaction) { - $assetId = (int)$transaction->account_id; + $journals = $collector->getExtractedJournals(); + $result = []; + $chartData = []; + foreach ($journals as $journal) { + $assetId = (int)$journal['destination_account_id']; $result[$assetId] = $result[$assetId] ?? '0'; - $result[$assetId] = bcadd($transaction->transaction_amount, $result[$assetId]); + $result[$assetId] = bcadd($journal['amount'], $result[$assetId]); } $names = $this->getAccountNames(array_keys($result)); @@ -229,7 +228,7 @@ class BudgetController extends Controller * * TODO this chart is not multi-currency aware. * - * @param Budget $budget + * @param Budget $budget * @param BudgetLimit|null $budgetLimit * * @return JsonResponse @@ -247,23 +246,20 @@ class BudgetController extends Controller return response()->json($cache->get()); // @codeCoverageIgnore } - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setAllAssetAccounts()->setBudget($budget)->withCategoryInformation(); + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setBudget($budget)->withCategoryInformation(); if (null !== $budgetLimit) { $collector->setRange($budgetLimit->start_date, $budgetLimit->end_date); } - $transactions = $collector->getTransactions(); - $result = []; - $chartData = []; - /** @var Transaction $transaction */ - foreach ($transactions as $transaction) { - $jrnlCatId = (int)$transaction->transaction_journal_category_id; - $transCatId = (int)$transaction->transaction_category_id; - $categoryId = max($jrnlCatId, $transCatId); + $journals = $collector->getExtractedJournals(); + $result = []; + $chartData = []; + foreach ($journals as $journal) { + $categoryId = (int)$journal['category_id']; $result[$categoryId] = $result[$categoryId] ?? '0'; - $result[$categoryId] = bcadd($transaction->transaction_amount, $result[$categoryId]); + $result[$categoryId] = bcadd($journal['amount'], $result[$categoryId]); } $names = $this->getCategoryNames(array_keys($result)); @@ -282,7 +278,7 @@ class BudgetController extends Controller * * TODO this chart is not multi-currency aware. * - * @param Budget $budget + * @param Budget $budget * @param BudgetLimit|null $budgetLimit * * @return JsonResponse @@ -300,21 +296,21 @@ class BudgetController extends Controller return response()->json($cache->get()); // @codeCoverageIgnore } - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setAllAssetAccounts()->setTypes([TransactionType::WITHDRAWAL])->setBudget($budget)->withOpposingAccount(); + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setTypes([TransactionType::WITHDRAWAL])->setBudget($budget)->withAccountInformation(); if (null !== $budgetLimit) { $collector->setRange($budgetLimit->start_date, $budgetLimit->end_date); } - $transactions = $collector->getTransactions(); - $result = []; - $chartData = []; - /** @var Transaction $transaction */ - foreach ($transactions as $transaction) { - $opposingId = (int)$transaction->opposing_account_id; + $journals = $collector->getExtractedJournals(); + $result = []; + $chartData = []; + /** @var array $journal */ + foreach ($journals as $journal) { + $opposingId = (int)$journal['destination_account_id']; $result[$opposingId] = $result[$opposingId] ?? '0'; - $result[$opposingId] = bcadd($transaction->transaction_amount, $result[$opposingId]); + $result[$opposingId] = bcadd($journal['amount'], $result[$opposingId]); } $names = $this->getAccountNames(array_keys($result)); @@ -392,9 +388,9 @@ class BudgetController extends Controller * * TODO this chart is not multi-currency aware. * - * @param Budget $budget - * @param Carbon $start - * @param Carbon $end + * @param Budget $budget + * @param Carbon $start + * @param Carbon $end * @param Collection $accounts * * @return JsonResponse @@ -441,8 +437,8 @@ class BudgetController extends Controller * TODO this chart is not multi-currency aware. * * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end + * @param Carbon $start + * @param Carbon $end * * @return JsonResponse */ diff --git a/app/Http/Controllers/Chart/BudgetReportController.php b/app/Http/Controllers/Chart/BudgetReportController.php index 3879ff540c..439c8414dd 100644 --- a/app/Http/Controllers/Chart/BudgetReportController.php +++ b/app/Http/Controllers/Chart/BudgetReportController.php @@ -51,6 +51,7 @@ class BudgetReportController extends Controller /** * BudgetReportController constructor. + * @codeCoverageIgnore */ public function __construct() { @@ -201,7 +202,7 @@ class BudgetReportController extends Controller $chartData[$budget->id]['entries'][$label] = bcmul($currentExpenses, '-1'); $chartData[$budget->id . '-sum']['entries'][$label] = bcmul($sumOfExpenses[$budget->id], '-1'); - if (\count($budgetLimits) > 0) { + if (count($budgetLimits) > 0) { $budgetLimitId = $budgetLimits->first()->id; $leftOfLimits[$budgetLimitId] = $leftOfLimits[$budgetLimitId] ?? (string)$budgetLimits->sum('amount'); $leftOfLimits[$budgetLimitId] = bcadd($leftOfLimits[$budgetLimitId], $currentExpenses); diff --git a/app/Http/Controllers/Chart/CategoryController.php b/app/Http/Controllers/Chart/CategoryController.php index b91e690b29..50c6694dbd 100644 --- a/app/Http/Controllers/Chart/CategoryController.php +++ b/app/Http/Controllers/Chart/CategoryController.php @@ -50,6 +50,7 @@ class CategoryController extends Controller /** * CategoryController constructor. + * @codeCoverageIgnore */ public function __construct() { @@ -106,6 +107,7 @@ class CategoryController extends Controller ], ]; $step = $this->calculateStep($start, $end); + /** @var Carbon $current */ $current = clone $start; Log::debug(sprintf('abc Step is %s', $step)); @@ -113,7 +115,7 @@ class CategoryController extends Controller switch ($step) { case '1D': while ($current <= $end) { - Log::debug(sprintf('Current day is %s', $current->format('Y-m-d'))); + //Log::debug(sprintf('Current day is %s', $current->format('Y-m-d'))); $spent = $repository->spentInPeriod(new Collection([$category]), $accounts, $current, $current); $earned = $repository->earnedInPeriod(new Collection([$category]), $accounts, $current, $current); $sum = bcadd($spent, $earned); @@ -124,12 +126,15 @@ class CategoryController extends Controller $current->addDay(); } break; + // @codeCoverageIgnoreStart + // for some reason it doesn't pick up on these case entries. case '1W': case '1M': case '1Y': + // @codeCoverageIgnoreEnd while ($current <= $end) { $currentEnd = app('navigation')->endOfPeriod($current, $step); - Log::debug(sprintf('abc Range is %s to %s', $current->format('Y-m-d'), $currentEnd->format('Y-m-d'))); + //Log::debug(sprintf('abc Range is %s to %s', $current->format('Y-m-d'), $currentEnd->format('Y-m-d'))); $spent = $repository->spentInPeriod(new Collection([$category]), $accounts, $current, $currentEnd); $earned = $repository->earnedInPeriod(new Collection([$category]), $accounts, $current, $currentEnd); @@ -391,7 +396,7 @@ class CategoryController extends Controller $start = app('navigation')->startOfPeriod($date, $range); $end = session()->get('end'); if ($end < $start) { - [$end, $start] = [$start, $end]; + [$end, $start] = [$start, $end]; // @codeCoverageIgnore } $data = $this->makePeriodChart($category, $start, $end); diff --git a/app/Http/Controllers/Chart/CategoryReportController.php b/app/Http/Controllers/Chart/CategoryReportController.php index 4ff0e3b465..6ad3b2d009 100644 --- a/app/Http/Controllers/Chart/CategoryReportController.php +++ b/app/Http/Controllers/Chart/CategoryReportController.php @@ -47,6 +47,7 @@ class CategoryReportController extends Controller /** * CategoryReportController constructor. + * @codeCoverageIgnore */ public function __construct() { @@ -207,7 +208,7 @@ class CategoryReportController extends Controller $cache->addProperty($start); $cache->addProperty($end); if ($cache->has()) { - return response()->json($cache->get()); // @codeCoverageIgnore + //return response()->json($cache->get()); // @codeCoverageIgnore } $format = app('navigation')->preferredCarbonLocalizedFormat($start, $end); @@ -261,7 +262,7 @@ class CategoryReportController extends Controller $labelOut = $category->id . '-out'; $labelSumIn = $category->id . '-total-in'; $labelSumOut = $category->id . '-total-out'; - $currentIncome = $income[$category->id] ?? '0'; + $currentIncome = bcmul($income[$category->id] ?? '0','-1'); $currentExpense = $expenses[$category->id] ?? '0'; // add to sum: @@ -279,15 +280,16 @@ class CategoryReportController extends Controller /** @var Carbon $currentStart */ $currentStart = clone $currentEnd; $currentStart->addDay(); + $currentStart->startOfDay(); } // remove all empty entries to prevent cluttering: $newSet = []; foreach ($chartData as $key => $entry) { if (0 === !array_sum($entry['entries'])) { - $newSet[$key] = $chartData[$key]; + $newSet[$key] = $chartData[$key]; // @codeCoverageIgnore } } - if (0 === \count($newSet)) { + if (0 === count($newSet)) { $newSet = $chartData; } $data = $this->generator->multiSet($newSet); diff --git a/app/Http/Controllers/Chart/ExpenseReportController.php b/app/Http/Controllers/Chart/ExpenseReportController.php index baa6ededed..e03c592552 100644 --- a/app/Http/Controllers/Chart/ExpenseReportController.php +++ b/app/Http/Controllers/Chart/ExpenseReportController.php @@ -49,6 +49,7 @@ class ExpenseReportController extends Controller /** * ExpenseReportController constructor. + * @codeCoverageIgnore */ public function __construct() { @@ -71,8 +72,8 @@ class ExpenseReportController extends Controller * * @param Collection $accounts * @param Collection $expense - * @param Carbon $start - * @param Carbon $end + * @param Carbon $start + * @param Carbon $end * * @return JsonResponse * @@ -89,7 +90,7 @@ class ExpenseReportController extends Controller $cache->addProperty($start); $cache->addProperty($end); if ($cache->has()) { - return response()->json($cache->get()); // @codeCoverageIgnore + return response()->json($cache->get()); // @codeCoverageIgnore } $format = app('navigation')->preferredCarbonLocalizedFormat($start, $end); @@ -97,44 +98,43 @@ class ExpenseReportController extends Controller $chartData = []; $currentStart = clone $start; $combined = $this->combineAccounts($expense); - // make "all" set: $all = new Collection; - foreach ($combined as $name => $combi) { - $all = $all->merge($combi); + foreach ($combined as $name => $combination) { + $all = $all->merge($combination); } // prep chart data: /** - * @var string $name - * @var Collection $combi + * @var string $name + * @var Collection $combination */ - foreach ($combined as $name => $combi) { + foreach ($combined as $name => $combination) { // first is always expense account: /** @var Account $exp */ - $exp = $combi->first(); + $exp = $combination->first(); $chartData[$exp->id . '-in'] = [ - 'label' => $name . ' (' . strtolower((string)trans('firefly.income')) . ')', + 'label' => sprintf('%s (%s)', $name, strtolower((string)trans('firefly.income'))), 'type' => 'bar', 'yAxisID' => 'y-axis-0', 'entries' => [], ]; $chartData[$exp->id . '-out'] = [ - 'label' => $name . ' (' . strtolower((string)trans('firefly.expenses')) . ')', + 'label' => sprintf('%s (%s)', $name, strtolower((string)trans('firefly.expenses'))), 'type' => 'bar', 'yAxisID' => 'y-axis-0', 'entries' => [], ]; // total in, total out: $chartData[$exp->id . '-total-in'] = [ - 'label' => $name . ' (' . strtolower((string)trans('firefly.sum_of_income')) . ')', + 'label' => sprintf('%s (%s)', $name, strtolower((string)trans('firefly.sum_of_income'))), 'type' => 'line', 'fill' => false, 'yAxisID' => 'y-axis-1', 'entries' => [], ]; $chartData[$exp->id . '-total-out'] = [ - 'label' => $name . ' (' . strtolower((string)trans('firefly.sum_of_expenses')) . ')', + 'label' => sprintf('%s (%s)', $name, strtolower((string)trans('firefly.sum_of_expenses'))), 'type' => 'line', 'fill' => false, 'yAxisID' => 'y-axis-1', @@ -154,15 +154,15 @@ class ExpenseReportController extends Controller $income = $this->groupByName($this->getIncomeForOpposing($accounts, $all, $currentStart, $currentEnd)); $label = $currentStart->formatLocalized($format); - foreach ($combined as $name => $combi) { + foreach ($combined as $name => $combination) { // first is always expense account: /** @var Account $exp */ - $exp = $combi->first(); + $exp = $combination->first(); $labelIn = $exp->id . '-in'; $labelOut = $exp->id . '-out'; $labelSumIn = $exp->id . '-total-in'; $labelSumOut = $exp->id . '-total-out'; - $currentIncome = $income[$name] ?? '0'; + $currentIncome = bcmul($income[$name] ?? '0', '-1'); $currentExpense = $expenses[$name] ?? '0'; // add to sum: @@ -180,15 +180,16 @@ class ExpenseReportController extends Controller /** @var Carbon $currentStart */ $currentStart = clone $currentEnd; $currentStart->addDay(); + $currentStart->startOfDay(); } // remove all empty entries to prevent cluttering: $newSet = []; foreach ($chartData as $key => $entry) { if (0 === !array_sum($entry['entries'])) { - $newSet[$key] = $chartData[$key]; + $newSet[$key] = $chartData[$key]; // @codeCoverageIgnore } } - if (0 === \count($newSet)) { + if (0 === count($newSet)) { $newSet = $chartData; // @codeCoverageIgnore } $data = $this->generator->multiSet($newSet); diff --git a/app/Http/Controllers/Chart/PiggyBankController.php b/app/Http/Controllers/Chart/PiggyBankController.php index b67a057b4c..fb94165760 100644 --- a/app/Http/Controllers/Chart/PiggyBankController.php +++ b/app/Http/Controllers/Chart/PiggyBankController.php @@ -44,6 +44,7 @@ class PiggyBankController extends Controller /** * PiggyBankController constructor. + * @codeCoverageIgnore */ public function __construct() { diff --git a/app/Http/Controllers/Chart/ReportController.php b/app/Http/Controllers/Chart/ReportController.php index fec7cd9219..280220fa50 100644 --- a/app/Http/Controllers/Chart/ReportController.php +++ b/app/Http/Controllers/Chart/ReportController.php @@ -46,6 +46,7 @@ class ReportController extends Controller /** * ReportController constructor. + * @codeCoverageIgnore */ public function __construct() { @@ -171,6 +172,7 @@ class ReportController extends Controller $carbon = new Carbon($date); $label = $carbon->formatLocalized($format); $earned = $chartData[0]['entries'][$label] ?? '0'; + $amount = bcmul($amount, '-1'); $chartData[0]['entries'][$label] = bcadd($earned, $amount); } foreach ($source['spent'] as $date => $amount) { @@ -222,6 +224,7 @@ class ReportController extends Controller 'count_spent' => 0, ]; foreach ($source['earned'] as $amount) { + $amount = bcmul($amount,'-1'); $numbers['sum_earned'] = bcadd($amount, $numbers['sum_earned']); ++$numbers['count_earned']; } diff --git a/app/Http/Controllers/Chart/TagReportController.php b/app/Http/Controllers/Chart/TagReportController.php index 6be03027a3..aacf46a64e 100644 --- a/app/Http/Controllers/Chart/TagReportController.php +++ b/app/Http/Controllers/Chart/TagReportController.php @@ -44,6 +44,7 @@ class TagReportController extends Controller /** * TagReportController constructor. + * @codeCoverageIgnore */ public function __construct() { @@ -200,7 +201,7 @@ class TagReportController extends Controller $cache->addProperty($start); $cache->addProperty($end); if ($cache->has()) { - return response()->json($cache->get()); // @codeCoverageIgnore + //return response()->json($cache->get()); // @codeCoverageIgnore } $format = app('navigation')->preferredCarbonLocalizedFormat($start, $end); @@ -254,7 +255,7 @@ class TagReportController extends Controller $labelOut = $tag->id . '-out'; $labelSumIn = $tag->id . '-total-in'; $labelSumOut = $tag->id . '-total-out'; - $currentIncome = $income[$tag->id] ?? '0'; + $currentIncome = bcmul($income[$tag->id] ?? '0','-1'); $currentExpense = $expenses[$tag->id] ?? '0'; // add to sum: @@ -272,15 +273,17 @@ class TagReportController extends Controller /** @var Carbon $currentStart */ $currentStart = clone $currentEnd; $currentStart->addDay(); + $currentStart->startOfDay(); + } // remove all empty entries to prevent cluttering: $newSet = []; foreach ($chartData as $key => $entry) { if (0 === !array_sum($entry['entries'])) { - $newSet[$key] = $chartData[$key]; + $newSet[$key] = $chartData[$key]; // @codeCoverageIgnore } } - if (0 === \count($newSet)) { + if (0 === count($newSet)) { $newSet = $chartData; // @codeCoverageIgnore } $data = $this->generator->multiSet($newSet); diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php index 286fcec349..c5c041a3aa 100644 --- a/app/Http/Controllers/Controller.php +++ b/app/Http/Controllers/Controller.php @@ -50,6 +50,7 @@ class Controller extends BaseController /** * Controller constructor. + * @codeCoverageIgnore */ public function __construct() { diff --git a/app/Http/Controllers/CurrencyController.php b/app/Http/Controllers/CurrencyController.php index ee89e3b4f7..6aa462e6be 100644 --- a/app/Http/Controllers/CurrencyController.php +++ b/app/Http/Controllers/CurrencyController.php @@ -47,6 +47,7 @@ class CurrencyController extends Controller /** * CurrencyController constructor. + * @codeCoverageIgnore */ public function __construct() { @@ -311,13 +312,6 @@ class CurrencyController extends Controller $pageSize = (int)app('preferences')->get('listPageSize', 50)->data; $collection = $this->repository->getAll(); $total = $collection->count(); - $collection = $collection->sortBy( - function (TransactionCurrency $currency) { - $intEnabled = $currency->enabled ? 0 : 1; - - return $intEnabled . $currency->name; - } - ); $collection = $collection->slice(($page - 1) * $pageSize, $pageSize); $currencies = new LengthAwarePaginator($collection, $total, $pageSize, $page); $currencies->setPath(route('currencies.index')); @@ -393,6 +387,10 @@ class CurrencyController extends Controller /** @var User $user */ $user = auth()->user(); $data = $request->getCurrencyData(); + + if (false === $data['enabled'] && $this->repository->currencyInUse($currency)) { + $data['enabled'] = true; + } if (!$this->userRepository->hasRole($user, 'owner')) { // @codeCoverageIgnoreStart $request->session()->flash('error', (string)trans('firefly.ask_site_owner', ['owner' => e(config('firefly.site_owner'))])); diff --git a/app/Http/Controllers/DebugController.php b/app/Http/Controllers/DebugController.php index c0966526cd..8de8555f3d 100644 --- a/app/Http/Controllers/DebugController.php +++ b/app/Http/Controllers/DebugController.php @@ -46,7 +46,8 @@ class DebugController extends Controller use GetConfigurationData; /** - * HomeController constructor. + * DebugController constructor. + * @codeCoverageIgnore */ public function __construct() { @@ -204,7 +205,7 @@ class DebugController extends Controller 'rules.select', 'search.search', 'test-flash', 'transactions.link.delete', 'transactions.link.switch', 'two-factor.lost', 'reports.options', 'debug', 'import.create-job', 'import.download', 'import.start', 'import.status.json', 'preferences.delete-code', 'rules.test-triggers', 'piggy-banks.remove-money', 'piggy-banks.add-money', - 'accounts.reconcile.transactions', 'accounts.reconcile.overview', 'export.download', + 'accounts.reconcile.transactions', 'accounts.reconcile.overview', 'transactions.clone', 'two-factor.index', 'api.v1', 'installer.', 'attachments.view', 'import.create', 'import.job.download', 'import.job.start', 'import.job.status.json', 'import.job.store', 'recurring.events', 'recurring.suggest', @@ -213,7 +214,7 @@ class DebugController extends Controller /** @var Route $route */ foreach ($set as $route) { $name = (string)$route->getName(); - if (\in_array('GET', $route->methods(), true)) { + if (in_array('GET', $route->methods(), true)) { $found = false; foreach ($ignore as $string) { if (!(false === stripos($name, $string))) { diff --git a/app/Http/Controllers/ExportController.php b/app/Http/Controllers/ExportController.php deleted file mode 100644 index 687be4e2b3..0000000000 --- a/app/Http/Controllers/ExportController.php +++ /dev/null @@ -1,198 +0,0 @@ -. - */ -declare(strict_types=1); - -namespace FireflyIII\Http\Controllers; - -use Carbon\Carbon; -use FireflyIII\Exceptions\FireflyException; -use FireflyIII\Export\ProcessorInterface; -use FireflyIII\Http\Middleware\IsDemoUser; -use FireflyIII\Http\Requests\ExportFormRequest; -use FireflyIII\Models\ExportJob; -use FireflyIII\Repositories\Account\AccountRepositoryInterface; -use FireflyIII\Repositories\ExportJob\ExportJobRepositoryInterface; -use Illuminate\Http\JsonResponse; -use Illuminate\Http\Response as LaravelResponse; - -/** - * Class ExportController. - */ -class ExportController extends Controller -{ - /** - * ExportController constructor. - */ - public function __construct() - { - parent::__construct(); - - $this->middleware( - function ($request, $next) { - app('view')->share('mainTitleIcon', 'fa-file-archive-o'); - app('view')->share('title', (string)trans('firefly.export_and_backup_data')); - - return $next($request); - } - ); - $this->middleware(IsDemoUser::class)->except(['index']); - } - - /** - * Download exported file. - * - * @param ExportJobRepositoryInterface $repository - * @param ExportJob $job - * - * @return \Illuminate\Contracts\Routing\ResponseFactory|\Symfony\Component\HttpFoundation\Response - * - * @throws FireflyException - */ - public function download(ExportJobRepositoryInterface $repository, ExportJob $job) - { - $file = $job->key . '.zip'; - $date = date('Y-m-d \a\t H-i-s'); - $name = 'Export job on ' . $date . '.zip'; - $quoted = sprintf('"%s"', addcslashes($name, '"\\')); - - if (!$repository->exists($job)) { - throw new FireflyException('Against all expectations, zip file "' . $file . '" does not exist.'); - } - $content = $repository->getContent($job); - - $repository->changeStatus($job, 'export_downloaded'); - /** @var LaravelResponse $response */ - $response = response($content); - $response - ->header('Content-Description', 'File Transfer') - ->header('Content-Type', 'application/octet-stream') - ->header('Content-Disposition', 'attachment; filename=' . $quoted) - ->header('Content-Transfer-Encoding', 'binary') - ->header('Connection', 'Keep-Alive') - ->header('Expires', '0') - ->header('Cache-Control', 'must-revalidate, post-check=0, pre-check=0') - ->header('Pragma', 'public') - ->header('Content-Length', \strlen($content)); - - return $response; - } - - /** - * Get current export status. - * - * @param ExportJob $job - * - * @return \Illuminate\Http\JsonResponse - */ - public function getStatus(ExportJob $job): JsonResponse - { - return response()->json(['status' => (string)trans('firefly.' . $job->status)]); - } - - /** - * Index of export routine. - * - * @param ExportJobRepositoryInterface $jobs - * - * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View - */ - public function index(ExportJobRepositoryInterface $jobs) - { - // create new export job. - $job = $jobs->create(); - - // does the user have shared accounts? - $formats = array_keys(config('firefly.export_formats')); - $defaultFormat = app('preferences')->get('export_format', config('firefly.default_export_format'))->data; - $first = session('first')->format('Y-m-d'); - $today = Carbon::now()->format('Y-m-d'); - - return view('export.index', compact('job', 'formats', 'defaultFormat', 'first', 'today')); - } - - /** - * Submit the job. - * - * @param ExportFormRequest $request - * @param AccountRepositoryInterface $repository - * @param ExportJobRepositoryInterface $jobs - * - * @return JsonResponse - * - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - */ - public function postIndex(ExportFormRequest $request, AccountRepositoryInterface $repository, ExportJobRepositoryInterface $jobs): JsonResponse - { - $job = $jobs->findByKey($request->get('job')); - $accounts = $request->get('accounts') ?? []; - $settings = [ - 'accounts' => $repository->getAccountsById($accounts), - 'startDate' => new Carbon($request->get('export_start_range')), - 'endDate' => new Carbon($request->get('export_end_range')), - 'exportFormat' => $request->get('exportFormat'), - 'includeAttachments' => $request->boolean('include_attachments'), - 'includeOldUploads' => $request->boolean('include_old_uploads'), - 'job' => $job, - ]; - - $jobs->changeStatus($job, 'export_status_make_exporter'); - - /** @var ProcessorInterface $processor */ - $processor = app(ProcessorInterface::class); - $processor->setSettings($settings); - - // Collect journals: - $jobs->changeStatus($job, 'export_status_collecting_journals'); - $processor->collectJournals(); - $jobs->changeStatus($job, 'export_status_collected_journals'); - - // Transform to exportable entries: - $jobs->changeStatus($job, 'export_status_converting_to_export_format'); - $processor->convertJournals(); - $jobs->changeStatus($job, 'export_status_converted_to_export_format'); - - // Transform to (temporary) file: - $jobs->changeStatus($job, 'export_status_creating_journal_file'); - $processor->exportJournals(); - $jobs->changeStatus($job, 'export_status_created_journal_file'); - // Collect attachments, if applicable. - if ($settings['includeAttachments']) { - $jobs->changeStatus($job, 'export_status_collecting_attachments'); - $processor->collectAttachments(); - $jobs->changeStatus($job, 'export_status_collected_attachments'); - } - - // Collect old uploads - if ($settings['includeOldUploads']) { - $jobs->changeStatus($job, 'export_status_collecting_old_uploads'); - $processor->collectOldUploads(); - $jobs->changeStatus($job, 'export_status_collected_old_uploads'); - } - - // Create ZIP file: - $jobs->changeStatus($job, 'export_status_creating_zip_file'); - $processor->createZipFile(); - $jobs->changeStatus($job, 'export_status_created_zip_file'); - $jobs->changeStatus($job, 'export_status_finished'); - - return response()->json('ok'); - } -} diff --git a/app/Http/Controllers/HomeController.php b/app/Http/Controllers/HomeController.php index 703e53aa53..f1618c2d49 100644 --- a/app/Http/Controllers/HomeController.php +++ b/app/Http/Controllers/HomeController.php @@ -23,8 +23,9 @@ declare(strict_types=1); namespace FireflyIII\Http\Controllers; use Carbon\Carbon; +use Exception; use FireflyIII\Events\RequestedVersionCheckStatus; -use FireflyIII\Helpers\Collector\TransactionCollectorInterface; +use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Http\Middleware\Installer; use FireflyIII\Models\AccountType; use FireflyIII\Repositories\Account\AccountRepositoryInterface; @@ -42,6 +43,7 @@ class HomeController extends Controller { /** * HomeController constructor. + * @codeCoverageIgnore */ public function __construct() { @@ -55,8 +57,8 @@ class HomeController extends Controller * Change index date range. * * @param Request $request - * * @return JsonResponse + * @throws Exception */ public function dateRange(Request $request): JsonResponse { @@ -96,8 +98,8 @@ class HomeController extends Controller * Show index. * * @param AccountRepositoryInterface $repository - * * @return \Illuminate\Contracts\View\Factory|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|\Illuminate\View\View + * @throws Exception */ public function index(AccountRepositoryInterface $repository) { @@ -127,9 +129,10 @@ class HomeController extends Controller $billCount = $billRepository->getBills()->count(); foreach ($accounts as $account) { - $collector = app(TransactionCollectorInterface::class); + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); $collector->setAccounts(new Collection([$account]))->setRange($start, $end)->setLimit(10)->setPage(1); - $set = $collector->getTransactions(); + $set = $collector->getGroups(); $transactions[] = [$set, $account]; } diff --git a/app/Http/Controllers/Import/IndexController.php b/app/Http/Controllers/Import/IndexController.php index 38ffcbf7bb..d448faf17b 100644 --- a/app/Http/Controllers/Import/IndexController.php +++ b/app/Http/Controllers/Import/IndexController.php @@ -32,6 +32,7 @@ use Illuminate\Http\Response as LaravelResponse; use Log; /** + * TODO make sure all import methods work. * * Class IndexController */ @@ -76,8 +77,6 @@ class IndexController extends Controller */ public function create(string $importProvider) { - - $hasPreReq = (bool)config(sprintf('import.has_prereq.%s', $importProvider)); $hasConfig = (bool)config(sprintf('import.has_job_config.%s', $importProvider)); $allowedForDemo = (bool)config(sprintf('import.allowed_for_demo.%s', $importProvider)); @@ -89,12 +88,13 @@ class IndexController extends Controller Log::debug(sprintf('Has prerequisites? %s', var_export($hasPreReq, true))); Log::debug(sprintf('Has config? %s', var_export($hasConfig, true))); - + // @codeCoverageIgnoreStart if ($isDemoUser && !$allowedForDemo) { Log::debug('User is demo and this provider doesnt work for demo users.'); return redirect(route('import.index')); } + // @codeCoverageIgnoreEnd $importJob = $this->repository->create($importProvider); @@ -172,7 +172,7 @@ class IndexController extends Controller ->header('Expires', '0') ->header('Cache-Control', 'must-revalidate, post-check=0, pre-check=0') ->header('Pragma', 'public') - ->header('Content-Length', \strlen($result)); + ->header('Content-Length', strlen($result)); return $response; } diff --git a/app/Http/Controllers/Import/JobConfigurationController.php b/app/Http/Controllers/Import/JobConfigurationController.php index 20a7f0966a..dac7b2136f 100644 --- a/app/Http/Controllers/Import/JobConfigurationController.php +++ b/app/Http/Controllers/Import/JobConfigurationController.php @@ -75,7 +75,7 @@ class JobConfigurationController extends Controller { Log::debug('Now in JobConfigurationController::index()'); $allowed = ['has_prereq', 'need_job_config']; - if (null !== $importJob && !\in_array($importJob->status, $allowed, true)) { + if (null !== $importJob && !in_array($importJob->status, $allowed, true)) { Log::error(sprintf('Job has state "%s", but we only accept %s', $importJob->status, json_encode($allowed))); session()->flash('error', (string)trans('import.bad_job_status', ['status' => e($importJob->status)])); @@ -126,7 +126,7 @@ class JobConfigurationController extends Controller { // catch impossible status: $allowed = ['has_prereq', 'need_job_config']; - if (null !== $importJob && !\in_array($importJob->status, $allowed, true)) { + if (null !== $importJob && !in_array($importJob->status, $allowed, true)) { session()->flash('error', (string)trans('import.bad_job_status', ['status' => e($importJob->status)])); return redirect(route('import.index')); diff --git a/app/Http/Controllers/Import/JobStatusController.php b/app/Http/Controllers/Import/JobStatusController.php index 456fca6f08..abde77bc25 100644 --- a/app/Http/Controllers/Import/JobStatusController.php +++ b/app/Http/Controllers/Import/JobStatusController.php @@ -106,7 +106,7 @@ class JobStatusController extends Controller // if count is zero: if (null !== $importJob->tag_id) { - $count = $importJob->tag->transactionJournals->count(); + $count = $this->repository->countByTag($importJob); } if (0 === $count) { $json['report_txt'] = (string)trans('import.result_no_transactions'); @@ -139,7 +139,7 @@ class JobStatusController extends Controller // catch impossible status: $allowed = ['ready_to_run', 'need_job_config']; - if (null !== $importJob && !\in_array($importJob->status, $allowed, true)) { + if (null !== $importJob && !in_array($importJob->status, $allowed, true)) { Log::error(sprintf('Job is not ready. Status should be in array, but is %s', $importJob->status), $allowed); $this->repository->setStatus($importJob, 'error'); @@ -202,7 +202,7 @@ class JobStatusController extends Controller Log::info('Now in JobStatusController::store'); // catch impossible status: $allowed = ['provider_finished', 'storing_data']; - if (null !== $importJob && !\in_array($importJob->status, $allowed, true)) { + if (null !== $importJob && !in_array($importJob->status, $allowed, true)) { Log::error(sprintf('Job is not ready. Status should be in array, but is %s', $importJob->status), $allowed); return response()->json( diff --git a/app/Http/Controllers/Import/PrerequisitesController.php b/app/Http/Controllers/Import/PrerequisitesController.php index da9c5ba82c..7a08d0519b 100644 --- a/app/Http/Controllers/Import/PrerequisitesController.php +++ b/app/Http/Controllers/Import/PrerequisitesController.php @@ -74,7 +74,7 @@ class PrerequisitesController extends Controller { // catch impossible status: $allowed = ['new']; - if (null !== $importJob && !\in_array($importJob->status, $allowed, true)) { + if (null !== $importJob && !in_array($importJob->status, $allowed, true)) { Log::error(sprintf('Job has state "%s" but this Prerequisites::index() only accepts %s', $importJob->status, json_encode($allowed))); session()->flash('error', (string)trans('import.bad_job_status', ['status' => e($importJob->status)])); @@ -127,7 +127,7 @@ class PrerequisitesController extends Controller // catch impossible status: $allowed = ['new']; - if (null !== $importJob && !\in_array($importJob->status, $allowed, true)) { + if (null !== $importJob && !in_array($importJob->status, $allowed, true)) { Log::error(sprintf('Job has state "%s" but this Prerequisites::post() only accepts %s', $importJob->status, json_encode($allowed))); session()->flash('error', (string)trans('import.bad_job_status', ['status' => e($importJob->status)])); diff --git a/app/Http/Controllers/Json/AutoCompleteController.php b/app/Http/Controllers/Json/AutoCompleteController.php index 6ca6d052a4..26ee4b2b68 100644 --- a/app/Http/Controllers/Json/AutoCompleteController.php +++ b/app/Http/Controllers/Json/AutoCompleteController.php @@ -22,217 +22,328 @@ declare(strict_types=1); namespace FireflyIII\Http\Controllers\Json; -use FireflyIII\Exceptions\FireflyException; -use FireflyIII\Helpers\Collector\TransactionCollectorInterface; use FireflyIII\Http\Controllers\Controller; +use FireflyIII\Models\Account; use FireflyIII\Models\AccountType; -use FireflyIII\Models\TransactionJournal; -use FireflyIII\Support\CacheProperties; -use FireflyIII\Support\Http\Controllers\AutoCompleteCollector; +use FireflyIII\Models\TransactionCurrency; +use FireflyIII\Repositories\Account\AccountRepositoryInterface; +use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; +use FireflyIII\Repositories\Category\CategoryRepositoryInterface; +use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; +use FireflyIII\Repositories\Journal\JournalRepositoryInterface; +use FireflyIII\Repositories\PiggyBank\PiggyBankRepositoryInterface; +use FireflyIII\Repositories\Tag\TagRepositoryInterface; +use FireflyIII\Repositories\TransactionType\TransactionTypeRepositoryInterface; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; +use Log; /** * Class AutoCompleteController. * + * TODO autocomplete for transaction types. + * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class AutoCompleteController extends Controller { - use AutoCompleteCollector; /** - * List of all journals. - * - * @param Request $request - * @param TransactionCollectorInterface $collector + * @param Request $request * * @return JsonResponse */ - public function allTransactionJournals(Request $request, TransactionCollectorInterface $collector): JsonResponse + public function accounts(Request $request): JsonResponse { - $search = (string)$request->get('search'); - $cache = new CacheProperties; - $cache->addProperty('ac-all-journals'); - // very unlikely a user will actually search for this string. - $key = '' === $search ? 'skjf0893j89fj2398hd89dh289h2398hr7isd8900828u209ujnxs88929282u' : $search; - $cache->addProperty($key); - if ($cache->has()) { - return response()->json($cache->get()); // @codeCoverageIgnore - } - // find everything: - $collector->setLimit(250)->setPage(1); - $return = array_values(array_unique($collector->getTransactions()->pluck('description')->toArray())); + $accountTypes = explode(',', $request->get('types') ?? ''); + $search = $request->get('search'); + /** @var AccountRepositoryInterface $repository */ + $repository = app(AccountRepositoryInterface::class); - if ('' !== $search) { - $return = array_values( - array_unique( - array_filter( - $return, function (string $value) use ($search) { - return !(false === stripos($value, $search)); - }, ARRAY_FILTER_USE_BOTH - ) - ) - ); + // filter the account types: + $allowedAccountTypes = [AccountType::ASSET, AccountType::EXPENSE, AccountType::REVENUE, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE,]; + $filteredAccountTypes = []; + foreach ($accountTypes as $type) { + if (in_array($type, $allowedAccountTypes, true)) { + $filteredAccountTypes[] = $type; + } } - $cache->store($return); + Log::debug(sprintf('Now in accounts("%s"). Filtering results.', $search), $filteredAccountTypes); + + $return = []; + $result = $repository->searchAccount((string)$search, $filteredAccountTypes); + $defaultCurrency = app('amount')->getDefaultCurrency(); + + /** @var Account $account */ + foreach ($result as $account) { + $currency = $repository->getAccountCurrency($account); + $currency = $currency ?? $defaultCurrency; + $return[] = [ + 'id' => $account->id, + 'name' => $account->name, + 'type' => $account->accountType->type, + 'currency_id' => $currency->id, + 'currency_name' => $currency->name, + 'currency_code' => $currency->code, + 'currency_decimal_places' => $currency->decimal_places, + ]; + } + return response()->json($return); } + /** + * An auto-complete specifically for revenue accounts, used when converting transactions mostly. + * @param Request $request + * + * @return JsonResponse + */ + public function revenueAccounts(Request $request): JsonResponse + { + $search = $request->get('search'); + /** @var AccountRepositoryInterface $repository */ + $repository = app(AccountRepositoryInterface::class); + + // filter the account types: + $allowedAccountTypes = [AccountType::REVENUE]; + Log::debug('Now in revenueAccounts(). Filtering results.', $allowedAccountTypes); + + $return = []; + $result = $repository->searchAccount((string)$search, $allowedAccountTypes); + + /** @var Account $account */ + foreach ($result as $account) { + $return[] = [ + 'id' => $account->id, + 'name' => $account->name, + 'type' => $account->accountType->type, + ]; + } + + return response()->json($return); + } + + /** + * An auto-complete specifically for expense accounts, used when mass updating mostly. + * @param Request $request + * + * @return JsonResponse + */ + public function expenseAccounts(Request $request): JsonResponse + { + $search = $request->get('search'); + /** @var AccountRepositoryInterface $repository */ + $repository = app(AccountRepositoryInterface::class); + + // filter the account types: + $allowedAccountTypes = [AccountType::EXPENSE]; + Log::debug(sprintf('Now in expenseAccounts(%s). Filtering results.', $search), $allowedAccountTypes); + + $return = []; + $result = $repository->searchAccount((string)$search, $allowedAccountTypes); + + /** @var Account $account */ + foreach ($result as $account) { + $return[] = [ + 'id' => $account->id, + 'name' => $account->name, + 'type' => $account->accountType->type, + ]; + } + + return response()->json($return); + } + + /** + * Searches in the titles of all transaction journals. + * The result is limited to the top 15 unique results. + * + * @param Request $request + * @return JsonResponse + */ + public function allJournals(Request $request): JsonResponse + { + $search = (string)$request->get('search'); + /** @var JournalRepositoryInterface $repository */ + $repository = app(JournalRepositoryInterface::class); + $result = $repository->searchJournalDescriptions($search); + + // limit and unique + $filtered = $result->unique('description'); + $limited = $filtered->slice(0, 15); + $array = $limited->toArray(); + foreach ($array as $index => $item) { + // give another key for consistency + $array[$index]['name'] = $item['description']; + } + + + return response()->json($array); + } + + /** + * Searches in the titles of all transaction journals. + * The result is limited to the top 15 unique results. + * + * If the query is numeric, it will append the journal with that particular ID. + * + * @param Request $request + * @return JsonResponse + */ + public function allJournalsWithID(Request $request): JsonResponse + { + $search = (string)$request->get('search'); + /** @var JournalRepositoryInterface $repository */ + $repository = app(JournalRepositoryInterface::class); + $result = $repository->searchJournalDescriptions($search); + $array = []; + if (is_numeric($search)) { + $firstResult = $repository->findNull((int)$search); + if (null !== $firstResult) { + $array[] = $firstResult->toArray(); + } + } + // if not numeric, search ahead! + + // limit and unique + $limited = $result->slice(0, 15); + $array = array_merge($array, $limited->toArray()); + foreach ($array as $index => $item) { + // give another key for consistency + $array[$index]['name'] = sprintf('#%d: %s', $item['id'], $item['description']); + } + + + return response()->json($array); + } + /** * @param Request $request - * @param string $subject - * - * @throws FireflyException * @return JsonResponse + * @codeCoverageIgnore */ - public function autoComplete(Request $request, string $subject): JsonResponse + public function budgets(Request $request): JsonResponse { - $search = (string)$request->get('search'); - $unfiltered = null; - $filtered = null; + $search = (string)$request->get('search'); + /** @var BudgetRepositoryInterface $repository */ + $repository = app(BudgetRepositoryInterface::class); + $result = $repository->searchBudget($search); - switch ($subject) { - default: - break; - case 'all-accounts': - $unfiltered = $this->getAccounts( - [AccountType::REVENUE, AccountType::EXPENSE, AccountType::BENEFICIARY, AccountType::DEFAULT, AccountType::ASSET, AccountType::LOAN, - AccountType::DEBT, AccountType::MORTGAGE] - ); - break; - case 'expense-accounts': - $unfiltered = $this->getAccounts([AccountType::EXPENSE, AccountType::BENEFICIARY]); - break; - case 'revenue-accounts': - $unfiltered = $this->getAccounts([AccountType::REVENUE]); - break; - case 'asset-accounts': - $unfiltered = $this->getAccounts([AccountType::ASSET, AccountType::DEFAULT]); - break; - case 'categories': - $unfiltered = $this->getCategories(); - break; - case 'budgets': - $unfiltered = $this->getBudgets(); - break; - case 'tags': - $unfiltered = $this->getTags(); - break; - case 'bills': - $unfiltered = $this->getBills(); - break; - case 'currency-names': - $unfiltered = $this->getCurrencyNames(); - break; - case 'transaction-types': - case 'transaction_types': - $unfiltered = $this->getTransactionTypes(); - break; - } - $filtered = $this->filterResult($unfiltered, $search); - - if (null === $filtered) { - throw new FireflyException(sprintf('Auto complete handler cannot handle "%s"', $subject)); // @codeCoverageIgnore - } - - return response()->json($filtered); + return response()->json($result->toArray()); } /** - * List of journals with their ID. - * - * @param Request $request - * @param TransactionCollectorInterface $collector - * @param TransactionJournal $except + * @param Request $request * * @return JsonResponse + * @codeCoverageIgnore */ - public function journalsWithId(Request $request, TransactionCollectorInterface $collector, TransactionJournal $except): JsonResponse + public function categories(Request $request): JsonResponse { - $search = (string)$request->get('search'); - $cache = new CacheProperties; - $cache->addProperty('ac-expense-accounts'); - // very unlikely a user will actually search for this string. - $key = '' === $search ? 'skjf0893j89fj2398hd89dh289h2398hr7isd8900828u209ujnxs88929282u' : $search; - $cache->addProperty($key); - if ($cache->has()) { - return response()->json($cache->get()); // @codeCoverageIgnore - } - // find everything: - $collector->setLimit(400)->setPage(1); - $set = $collector->getTransactions()->pluck('description', 'journal_id')->toArray(); - $return = []; - foreach ($set as $id => $description) { - $id = (int)$id; - if ($id !== $except->id) { - $return[] = [ - 'id' => $id, - 'name' => $id . ': ' . $description, - ]; - } + $query = (string)$request->get('search'); + /** @var CategoryRepositoryInterface $repository */ + $repository = app(CategoryRepositoryInterface::class); + $result = $repository->searchCategory($query); + + return response()->json($result->toArray()); + } + + /** + * @param Request $request + * @return JsonResponse + * @codeCoverageIgnore + */ + public function currencyNames(Request $request): JsonResponse + { + $query = (string)$request->get('search'); + /** @var CurrencyRepositoryInterface $repository */ + $repository = app(CurrencyRepositoryInterface::class); + $result = $repository->searchCurrency($query)->toArray(); + foreach ($result as $index => $item) { + $result[$index]['name'] = sprintf('%s (%s)', $item['name'], $item['code']); } - sort($return); + return response()->json($result); + } - if ('' !== $search) { - $return = array_filter( - $return, function (array $array) use ($search) { - $haystack = $array['name']; - $result = stripos($haystack, $search); - - return !(false === $result); - } - ); + /** + * @param Request $request + * + * @return JsonResponse + * @codeCoverageIgnore + */ + public function transactionTypes(Request $request): JsonResponse + { + $query = (string)$request->get('search'); + /** @var TransactionTypeRepositoryInterface $repository */ + $repository = app(TransactionTypeRepositoryInterface::class); + $array = $repository->searchTypes($query)->toArray(); + foreach ($array as $index => $item) { + // different key for consistency. + $array[$index]['name'] = $item['type']; + } + + return response()->json($array); + } + + /** + * @return JsonResponse + * @codeCoverageIgnore + */ + public function currencies(): JsonResponse + { + /** @var CurrencyRepositoryInterface $repository */ + $repository = app(CurrencyRepositoryInterface::class); + $return = []; + $collection = $repository->getAll(); + + /** @var TransactionCurrency $currency */ + foreach ($collection as $currency) { + $return[] = [ + 'id' => $currency->id, + 'name' => $currency->name, + 'code' => $currency->code, + 'symbol' => $currency->symbol, + 'enabled' => $currency->enabled, + 'decimal_places' => $currency->decimal_places, + ]; } - $cache->store($return); return response()->json($return); } /** - * List of journals by type. - * - * @param Request $request - * @param TransactionCollectorInterface $collector - * @param string $what - * * @return JsonResponse + * @codeCoverageIgnore */ - public function transactionJournals(Request $request, TransactionCollectorInterface $collector, string $what): JsonResponse + public function piggyBanks(): JsonResponse + { + /** @var PiggyBankRepositoryInterface $repository */ + $repository = app(PiggyBankRepositoryInterface::class); + + return response()->json($repository->getPiggyBanks()->toArray()); + } + + /** + * @param Request $request + * @return JsonResponse + * @codeCoverageIgnore + */ + public function tags(Request $request): JsonResponse { $search = (string)$request->get('search'); - $cache = new CacheProperties; - $cache->addProperty('ac-journals'); - // very unlikely a user will actually search for this string. - $key = '' === $search ? 'skjf0893j89fj2398hd89dh289h2398hr7isd8900828u209ujnxs88929282u' : $search; - $cache->addProperty($key); - if ($cache->has()) { - return response()->json($cache->get()); // @codeCoverageIgnore + /** @var TagRepositoryInterface $repository */ + $repository = app(TagRepositoryInterface::class); + $result = $repository->searchTags($search); + $array = $result->toArray(); + foreach ($array as $index => $item) { + // rename field for consistency. + $array[$index]['name'] = $item['tag']; } - // find everything: - $type = config('firefly.transactionTypesByWhat.' . $what); - $types = [$type]; - $collector->setTypes($types)->setLimit(250)->setPage(1); - $return = array_unique($collector->getTransactions()->pluck('description')->toArray()); - sort($return); - - if ('' !== $search) { - $return = array_values( - array_unique( - array_filter( - $return, function (string $value) use ($search) { - return !(false === stripos($value, $search)); - }, ARRAY_FILTER_USE_BOTH - ) - ) - ); - } - $cache->store($return); - - return response()->json($return); + return response()->json($array); } + } diff --git a/app/Http/Controllers/Json/BoxController.php b/app/Http/Controllers/Json/BoxController.php index 77a452d9a3..4bca8b1585 100644 --- a/app/Http/Controllers/Json/BoxController.php +++ b/app/Http/Controllers/Json/BoxController.php @@ -23,12 +23,11 @@ declare(strict_types=1); namespace FireflyIII\Http\Controllers\Json; use Carbon\Carbon; -use FireflyIII\Helpers\Collector\TransactionCollectorInterface; +use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Helpers\Report\NetWorthInterface; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Models\Account; use FireflyIII\Models\AccountType; -use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionCurrency; use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Account\AccountRepositoryInterface; @@ -126,43 +125,42 @@ class BoxController extends Controller $cache->addProperty($end); $cache->addProperty('box-balance'); if ($cache->has()) { - return response()->json($cache->get()); // @codeCoverageIgnore + return response()->json($cache->get()); // @codeCoverageIgnore } // prep some arrays: $incomes = []; $expenses = []; $sums = []; + $currency = app('amount')->getDefaultCurrency(); // collect income of user: - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setAllAssetAccounts()->setRange($start, $end) - ->setTypes([TransactionType::DEPOSIT]) - ->withOpposingAccount(); - $set = $collector->getTransactions(); - /** @var Transaction $transaction */ - foreach ($set as $transaction) { - $currencyId = (int)$transaction->transaction_currency_id; + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setRange($start, $end) + ->setTypes([TransactionType::DEPOSIT]); + $set = $collector->getExtractedJournals(); + /** @var array $journal */ + foreach ($set as $journal) { + $currencyId = (int)$journal['currency_id']; $incomes[$currencyId] = $incomes[$currencyId] ?? '0'; - $incomes[$currencyId] = bcadd($incomes[$currencyId], $transaction->transaction_amount); + $incomes[$currencyId] = bcadd($incomes[$currencyId], $journal['amount']); $sums[$currencyId] = $sums[$currencyId] ?? '0'; - $sums[$currencyId] = bcadd($sums[$currencyId], $transaction->transaction_amount); + $sums[$currencyId] = bcadd($sums[$currencyId], $journal['amount']); } // collect expenses - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setAllAssetAccounts()->setRange($start, $end) - ->setTypes([TransactionType::WITHDRAWAL]) - ->withOpposingAccount(); - $set = $collector->getTransactions(); - /** @var Transaction $transaction */ - foreach ($set as $transaction) { - $currencyId = (int)$transaction->transaction_currency_id; + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setRange($start, $end) + ->setTypes([TransactionType::WITHDRAWAL]); + $set = $collector->getExtractedJournals(); + /** @var array $journal */ + foreach ($set as $journal) { + $currencyId = (int)$journal['currency_id']; $expenses[$currencyId] = $expenses[$currencyId] ?? '0'; - $expenses[$currencyId] = bcadd($expenses[$currencyId], $transaction->transaction_amount); + $expenses[$currencyId] = bcadd($expenses[$currencyId], $journal['amount']); $sums[$currencyId] = $sums[$currencyId] ?? '0'; - $sums[$currencyId] = bcadd($sums[$currencyId], $transaction->transaction_amount); + $sums[$currencyId] = bcadd($sums[$currencyId], $journal['amount']); } // format amounts: @@ -173,7 +171,7 @@ class BoxController extends Controller $incomes[$currencyId] = app('amount')->formatAnything($currency, $incomes[$currencyId] ?? '0', false); $expenses[$currencyId] = app('amount')->formatAnything($currency, $expenses[$currencyId] ?? '0', false); } - if (0 === \count($sums)) { + if (0 === count($sums)) { $currency = app('amount')->getDefaultCurrency(); $sums[$currency->id] = app('amount')->formatAnything($currency, '0', false); $incomes[$currency->id] = app('amount')->formatAnything($currency, '0', false); @@ -181,10 +179,11 @@ class BoxController extends Controller } $response = [ - 'incomes' => $incomes, - 'expenses' => $expenses, - 'sums' => $sums, - 'size' => \count($sums), + 'incomes' => $incomes, + 'expenses' => $expenses, + 'sums' => $sums, + 'size' => count($sums), + 'preferred' => $currency->id, ]; diff --git a/app/Http/Controllers/Json/FrontpageController.php b/app/Http/Controllers/Json/FrontpageController.php index 1303ba1109..b193aad99f 100644 --- a/app/Http/Controllers/Json/FrontpageController.php +++ b/app/Http/Controllers/Json/FrontpageController.php @@ -64,7 +64,7 @@ class FrontpageController extends Controller } } $html = ''; - if (\count($info) > 0) { + if (count($info) > 0) { try { $html = view('json.piggy-banks', compact('info'))->render(); // @codeCoverageIgnoreStart diff --git a/app/Http/Controllers/Json/IntroController.php b/app/Http/Controllers/Json/IntroController.php index 9ad233c2b1..3d43810b4d 100644 --- a/app/Http/Controllers/Json/IntroController.php +++ b/app/Http/Controllers/Json/IntroController.php @@ -47,7 +47,7 @@ class IntroController $specificPage = $specificPage ?? ''; $steps = $this->getBasicSteps($route); $specificSteps = $this->getSpecificSteps($route, $specificPage); - if (0 === \count($specificSteps)) { + if (0 === count($specificSteps)) { Log::debug(sprintf('No specific steps for route "%s" and page "%s"', $route, $specificPage)); return response()->json($steps); @@ -55,7 +55,7 @@ class IntroController if ($this->hasOutroStep($route)) { // @codeCoverageIgnoreStart // save last step: - $lastStep = $steps[\count($steps) - 1]; + $lastStep = $steps[count($steps) - 1]; // remove last step: array_pop($steps); // merge arrays and add last step again @@ -82,7 +82,7 @@ class IntroController $routeKey = str_replace('.', '_', $route); Log::debug(sprintf('Has outro step for route %s', $routeKey)); $elements = config(sprintf('intro.%s', $routeKey)); - if (!\is_array($elements)) { + if (!is_array($elements)) { return false; } @@ -135,7 +135,7 @@ class IntroController Log::debug(sprintf('Going to mark the following route as done: %s with special "%s" (%s)', $route, $specialPage, $key)); app('preferences')->set($key, true); - return response()->json(['result' => sprintf('Reported demo watched for route "%s".', $route)]); + return response()->json(['result' => sprintf('Reported demo watched for route "%s" (%s): %s.', $route, $specialPage, $key)]); } } diff --git a/app/Http/Controllers/Json/ReconcileController.php b/app/Http/Controllers/Json/ReconcileController.php index 3e0f407f18..28d8f3f425 100644 --- a/app/Http/Controllers/Json/ReconcileController.php +++ b/app/Http/Controllers/Json/ReconcileController.php @@ -25,12 +25,11 @@ namespace FireflyIII\Http\Controllers\Json; use Carbon\Carbon; -use FireflyIII\Exceptions\FireflyException; -use FireflyIII\Helpers\Collector\TransactionCollectorInterface; +use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Models\Account; -use FireflyIII\Models\AccountType; -use FireflyIII\Models\Transaction; +use FireflyIII\Models\TransactionCurrency; +use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; use FireflyIII\Repositories\Journal\JournalRepositoryInterface; @@ -44,8 +43,6 @@ use Throwable; /** * * Class ReconcileController - * - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) */ class ReconcileController extends Controller { @@ -59,6 +56,7 @@ class ReconcileController extends Controller /** * ReconcileController constructor. + * @codeCoverageIgnore */ public function __construct() { @@ -78,100 +76,76 @@ class ReconcileController extends Controller ); } - /** @noinspection MoreThanThreeArgumentsInspection */ /** * Overview of reconciliation. * * @param Request $request * @param Account $account - * @param Carbon $start - * @param Carbon $end + * @param Carbon $start + * @param Carbon $end * * @return JsonResponse - * - * @throws FireflyException - * - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ public function overview(Request $request, Account $account, Carbon $start, Carbon $end): JsonResponse { - if (AccountType::ASSET !== $account->accountType->type) { - throw new FireflyException(sprintf('Account %s is not an asset account.', $account->name)); + $startBalance = $request->get('startBalance'); + $endBalance = $request->get('endBalance'); + $accountCurrency = $this->accountRepos->getAccountCurrency($account) ?? app('amount')->getDefaultCurrency(); + $amount = '0'; + $clearedAmount = '0'; + $route = route('accounts.reconcile.submit', [$account->id, $start->format('Ymd'), $end->format('Ymd')]); + $selectedIds = $request->get('journals') ?? []; + $clearedJournals = []; + $clearedIds = $request->get('cleared') ?? []; + $journals = []; + /* Collect all submitted journals */ + if (count($selectedIds) > 0) { + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setJournalIds($selectedIds); + $journals = $collector->getExtractedJournals(); + } + + /* Collect all journals already reconciled */ + if (count($clearedIds) > 0) { + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setJournalIds($clearedIds); + $clearedJournals = $collector->getExtractedJournals(); } - $startBalance = $request->get('startBalance'); - $endBalance = $request->get('endBalance'); - $transactionIds = $request->get('transactions') ?? []; - $clearedIds = $request->get('cleared') ?? []; - $amount = '0'; - $clearedAmount = '0'; - $route = route('accounts.reconcile.submit', [$account->id, $start->format('Ymd'), $end->format('Ymd')]); - // get sum of transaction amounts: - $transactions = $this->repository->getTransactionsById($transactionIds); - $cleared = $this->repository->getTransactionsById($clearedIds); - $countCleared = 0; Log::debug('Start transaction loop'); - /** @var Transaction $transaction */ - foreach ($transactions as $transaction) { - // find the account and opposing account for this transaction - Log::debug(sprintf('Now at transaction #%d: %s', $transaction->journal_id, $transaction->description)); - $srcAccount = $this->accountRepos->findNull((int)$transaction->account_id); - $dstAccount = $this->accountRepos->findNull((int)$transaction->opposing_account_id); - $srcCurrency = (int)$this->accountRepos->getMetaValue($srcAccount, 'currency_id'); - $dstCurrency = (int)$this->accountRepos->getMetaValue($dstAccount, 'currency_id'); - - // is $account source or destination? - if ($account->id === $srcAccount->id) { - // source, and it matches the currency id or is 0 - if ($srcCurrency === $transaction->transaction_currency_id || 0 === $srcCurrency) { - Log::debug(sprintf('Source matches currency: %s', $transaction->transaction_amount)); - $amount = bcadd($amount, $transaction->transaction_amount); - } - // destination, and it matches the foreign currency ID. - if ($srcCurrency === $transaction->foreign_currency_id) { - Log::debug(sprintf('Source matches foreign currency: %s', $transaction->transaction_foreign_amount)); - $amount = bcadd($amount, $transaction->transaction_foreign_amount); - } - } - - if ($account->id === $dstAccount->id) { - // destination, and it matches the currency id or is 0 - if ($dstCurrency === $transaction->transaction_currency_id || 0 === $dstCurrency) { - Log::debug(sprintf('Destination matches currency: %s', app('steam')->negative($transaction->transaction_amount))); - $amount = bcadd($amount, app('steam')->negative($transaction->transaction_amount)); - } - // destination, and it matches the foreign currency ID. - if ($dstCurrency === $transaction->foreign_currency_id) { - Log::debug(sprintf('Destination matches foreign currency: %s', $transaction->transaction_foreign_amount)); - $amount = bcadd($amount, $transaction->transaction_foreign_amount); - } - } - Log::debug(sprintf('Amount is now %s', $amount)); + /** @var array $journal */ + foreach ($journals as $journal) { + $amount = $this->processJournal($account, $accountCurrency, $journal, $amount); } + Log::debug(sprintf('Final amount is %s', $amount)); Log::debug('End transaction loop'); - /** @var Transaction $transaction */ - foreach ($cleared as $transaction) { - if ($transaction->date <= $end) { - $clearedAmount = bcadd($clearedAmount, $transaction->transaction_amount); // @codeCoverageIgnore - ++$countCleared; + + /** @var array $journal */ + foreach ($clearedJournals as $journal) { + if ($journal['date'] <= $end) { + $clearedAmount = $this->processJournal($account, $accountCurrency, $journal, $clearedAmount); } } - $difference = bcadd(bcadd(bcsub($startBalance, $endBalance), $clearedAmount), $amount); - $diffCompare = bccomp($difference, '0'); + $difference = bcadd(bcadd(bcsub($startBalance, $endBalance), $clearedAmount), $amount); + $diffCompare = bccomp($difference, '0'); + $countCleared = count($clearedJournals); + + $reconSum = bcadd(bcadd($startBalance, $amount), $clearedAmount); try { $view = view( 'accounts.reconcile.overview', compact( - 'account', 'start', 'diffCompare', 'difference', 'end', 'clearedIds', 'transactionIds', 'clearedAmount', + 'account', 'start', 'diffCompare', 'difference', 'end', 'clearedAmount', 'startBalance', 'endBalance', 'amount', - 'route', 'countCleared' + 'route', 'countCleared', 'reconSum', 'selectedIds' ) )->render(); // @codeCoverageIgnoreStart } catch (Throwable $e) { Log::debug(sprintf('View error: %s', $e->getMessage())); - $view = 'Could not render accounts.reconcile.overview'; + $view = sprintf('Could not render accounts.reconcile.overview: %s', $e->getMessage()); } // @codeCoverageIgnoreEnd @@ -189,30 +163,20 @@ class ReconcileController extends Controller * Returns a list of transactions in a modal. * * @param Account $account - * @param Carbon $start - * @param Carbon $end + * @param Carbon $start + * @param Carbon $end * * @return mixed * */ public function transactions(Account $account, Carbon $start, Carbon $end) { - if (AccountType::INITIAL_BALANCE === $account->accountType->type) { - return $this->redirectToOriginalAccount($account); - } - $startDate = clone $start; - $startDate->subDays(1); - - $currencyId = (int)$this->accountRepos->getMetaValue($account, 'currency_id'); - $currency = $this->currencyRepos->findNull($currencyId); - if (0 === $currencyId) { - $currency = app('amount')->getDefaultCurrency(); // @codeCoverageIgnore - } + $startDate->subDay(); + $currency = $this->accountRepos->getAccountCurrency($account) ?? app('amount')->getDefaultCurrency(); $startBalance = round(app('steam')->balance($account, $startDate), $currency->decimal_places); $endBalance = round(app('steam')->balance($account, $end), $currency->decimal_places); - // get the transactions $selectionStart = clone $start; $selectionStart->subDays(3); @@ -220,23 +184,94 @@ class ReconcileController extends Controller $selectionEnd->addDays(3); // grab transactions: - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setAccounts(new Collection([$account])) - ->setRange($selectionStart, $selectionEnd)->withBudgetInformation()->withOpposingAccount()->withCategoryInformation(); - $transactions = $collector->getTransactions(); + ->setRange($selectionStart, $selectionEnd) + ->withBudgetInformation()->withCategoryInformation()->withAccountInformation(); + $array = $collector->getExtractedJournals(); + $journals = []; + // "fix" amounts to make it easier on the reconciliation overview: + /** @var array $journal */ + foreach ($array as $journal) { + $inverse = false; + // @codeCoverageIgnoreStart + if (TransactionType::DEPOSIT === $journal['transaction_type_type']) { + $inverse = true; + } + // transfer to this account? then positive amount: + if (TransactionType::TRANSFER === $journal['transaction_type_type'] && $account->id === $journal['destination_account_id']) { + $inverse = true; + } + + // opening balance into account? then positive amount: + if (TransactionType::OPENING_BALANCE === $journal['transaction_type_type'] + && $account->id === $journal['destination_account_id']) { + $inverse = true; + } + + if (true === $inverse) { + $journal['amount'] = app('steam')->positive($journal['amount']); + if (null !== $journal['foreign_amount']) { + $journal['foreign_amount'] = app('steam')->positive($journal['foreign_amount']); + } + } + // @codeCoverageIgnoreEnd + + $journals[] = $journal; + } + try { - $html = view( - 'accounts.reconcile.transactions', compact('account', 'transactions', 'currency', 'start', 'end', 'selectionStart', 'selectionEnd') - )->render(); + $html = view('accounts.reconcile.transactions', + compact('account', 'journals', 'currency', 'start', 'end', 'selectionStart', 'selectionEnd'))->render(); // @codeCoverageIgnoreStart } catch (Throwable $e) { Log::debug(sprintf('Could not render: %s', $e->getMessage())); - $html = 'Could not render accounts.reconcile.transactions'; + $html = sprintf('Could not render accounts.reconcile.transactions: %s', $e->getMessage()); } // @codeCoverageIgnoreEnd return response()->json(['html' => $html, 'startBalance' => $startBalance, 'endBalance' => $endBalance]); } + + /** + * @param Account $account + * @param TransactionCurrency $currency + * @param array $journal + * @param string $amount + * @return string + */ + private function processJournal(Account $account, TransactionCurrency $currency, array $journal, string $amount): string + { + $toAdd = '0'; + Log::debug(sprintf('User submitted %s #%d: "%s"', $journal['transaction_type_type'], $journal['transaction_journal_id'], $journal['description'])); + + // not much magic below we need to cover using tests. + // @codeCoverageIgnoreStart + if ($account->id === $journal['source_account_id']) { + if ($currency->id === $journal['currency_id']) { + $toAdd = $journal['amount']; + } + if (null !== $journal['foreign_currency_id'] && $journal['foreign_currency_id'] === $currency->id) { + $toAdd = $journal['foreign_amount']; + } + } + if ($account->id === $journal['destination_account_id']) { + if ($currency->id === $journal['currency_id']) { + $toAdd = bcmul($journal['amount'], '-1'); + } + if (null !== $journal['foreign_currency_id'] && $journal['foreign_currency_id'] === $currency->id) { + $toAdd = bcmul($journal['foreign_amount'], '-1'); + } + } + // @codeCoverageIgnoreEnd + + Log::debug(sprintf('Going to add %s to %s', $toAdd, $amount)); + $amount = bcadd($amount, $toAdd); + Log::debug(sprintf('Result is %s', $amount)); + + return $amount; + } } diff --git a/app/Http/Controllers/Json/RecurrenceController.php b/app/Http/Controllers/Json/RecurrenceController.php index d672b01311..77ef359ae4 100644 --- a/app/Http/Controllers/Json/RecurrenceController.php +++ b/app/Http/Controllers/Json/RecurrenceController.php @@ -42,6 +42,7 @@ class RecurrenceController extends Controller /** * RecurrenceController constructor. + * @codeCoverageIgnore */ public function __construct() { diff --git a/app/Http/Controllers/JsonController.php b/app/Http/Controllers/Json/RuleController.php similarity index 92% rename from app/Http/Controllers/JsonController.php rename to app/Http/Controllers/Json/RuleController.php index ee325185c9..1b6ebdd80b 100644 --- a/app/Http/Controllers/JsonController.php +++ b/app/Http/Controllers/Json/RuleController.php @@ -1,7 +1,7 @@ . */ -declare(strict_types=1); -namespace FireflyIII\Http\Controllers; +namespace FireflyIII\Http\Controllers\Json; + +use FireflyIII\Http\Controllers\Controller; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; use Log; use Throwable; /** - * Class JsonController. + * Class RuleController */ -class JsonController extends Controller +class RuleController extends Controller { /** * Render HTML form for rule action. @@ -91,4 +92,5 @@ class JsonController extends Controller return response()->json(['html' => $view]); } -} + +} \ No newline at end of file diff --git a/app/Http/Controllers/PiggyBankController.php b/app/Http/Controllers/PiggyBankController.php index 412d6a0f88..4f51a874d9 100644 --- a/app/Http/Controllers/PiggyBankController.php +++ b/app/Http/Controllers/PiggyBankController.php @@ -56,6 +56,7 @@ class PiggyBankController extends Controller /** * PiggyBankController constructor. + * @codeCoverageIgnore */ public function __construct() { diff --git a/app/Http/Controllers/PreferencesController.php b/app/Http/Controllers/PreferencesController.php index 439fd8f4ae..eddb76fb25 100644 --- a/app/Http/Controllers/PreferencesController.php +++ b/app/Http/Controllers/PreferencesController.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace FireflyIII\Http\Controllers; use FireflyIII\Models\AccountType; +use FireflyIII\Models\Preference; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use Illuminate\Http\Request; @@ -33,6 +34,7 @@ class PreferencesController extends Controller { /** * PreferencesController constructor. + * @codeCoverageIgnore */ public function __construct() { @@ -72,7 +74,7 @@ class PreferencesController extends Controller // an important fallback is that the frontPageAccount array gets refilled automatically // when it turns up empty. - if (0 === \count($frontPageAccounts->data)) { + if (0 === count($frontPageAccounts->data)) { $frontPageAccounts = $accountIds; } @@ -105,7 +107,7 @@ class PreferencesController extends Controller { // front page accounts $frontPageAccounts = []; - if (\is_array($request->get('frontPageAccounts')) && \count($request->get('frontPageAccounts')) > 0) { + if (is_array($request->get('frontPageAccounts')) && count($request->get('frontPageAccounts')) > 0) { foreach ($request->get('frontPageAccounts') as $id) { $frontPageAccounts[] = (int)$id; } @@ -133,10 +135,15 @@ class PreferencesController extends Controller } // language: + /** @var Preference $currentLang */ + $currentLang = app('preferences')->get('language', 'en_US'); $lang = $request->get('language'); if (array_key_exists($lang, config('firefly.languages'))) { app('preferences')->set('language', $lang); } + if ($currentLang->data !== $lang) { + session()->flash('info', 'All translations are supplied by volunteers. There might be errors and mistakes. I appreciate your feedback.'); + } // optional fields for transactions: $setOptions = $request->get('tj'); diff --git a/app/Http/Controllers/ProfileController.php b/app/Http/Controllers/ProfileController.php index 2adbaf40c3..5a37aed511 100644 --- a/app/Http/Controllers/ProfileController.php +++ b/app/Http/Controllers/ProfileController.php @@ -45,6 +45,7 @@ use Illuminate\Http\Request; use Illuminate\Support\Collection; use Laravel\Passport\ClientRepository; use Log; +use PragmaRX\Recovery\Recovery; /** * Class ProfileController. @@ -59,13 +60,14 @@ class ProfileController extends Controller /** * ProfileController constructor. + * @codeCoverageIgnore */ public function __construct() { parent::__construct(); $this->middleware( - function ($request, $next) { + static function ($request, $next) { app('view')->share('title', (string)trans('firefly.profile')); app('view')->share('mainTitleIcon', 'fa-user'); @@ -136,12 +138,42 @@ class ProfileController extends Controller public function code() { $domain = $this->getDomain(); - $secret = Google2FA::generateSecretKey(); - session()->flash('two-factor-secret', $secret); + $secret = null; + + // generate secret if not in session + if (!session()->has('temp-mfa-secret')) { + // generate secret + store + flash + $secret = Google2FA::generateSecretKey(); + session()->put('temp-mfa-secret', $secret); + session()->flash('two-factor-secret', $secret); + } + // re-use secret if in session + if (session()->has('temp-mfa-secret')) { + // get secret from session and flash + $secret = session()->get('temp-mfa-secret'); + session()->flash('two-factor-secret', $secret); + } + + // generate codes if not in session: + if (!session()->has('temp-mfa-codes')) { + // generate codes + store + flash: + $recovery = app(Recovery::class); + $recoveryCodes = $recovery->lowercase()->setCount(8)->setBlocks(2)->setChars(6)->toArray(); + session()->put('temp-mfa-codes', $recoveryCodes); + session()->flash('two-factor-codes', $recoveryCodes); + } + + // get codes from session if there already: + if (session()->has('temp-mfa-codes')) { + $recoveryCodes = session()->get('temp-mfa-codes'); + session()->flash('two-factor-codes', $recoveryCodes); + } + + $codes = implode("\r\n", $recoveryCodes); $image = Google2FA::getQRCodeInline($domain, auth()->user()->email, $secret); - return view('profile.code', compact('image', 'secret')); + return view('profile.code', compact('image', 'secret','codes')); } /** @@ -166,20 +198,20 @@ class ProfileController extends Controller /** @var Collection $set */ $set = app('preferences')->findByName('email_change_confirm_token'); $user = null; - Log::debug(sprintf('Found %d preferences', $set->count())); + //Log::debug(sprintf('Found %d preferences', $set->count())); /** @var Preference $preference */ foreach ($set as $preference) { if ($preference->data === $token) { - Log::debug('Found user'); + //Log::debug('Found user'); $user = $preference->user; } } // update user to clear blocked and blocked_code. if (null === $user) { - Log::debug('Found no user'); + //Log::debug('Found no user'); throw new FireflyException('Invalid token.'); } - Log::debug('Will unblock user.'); + //Log::debug('Will unblock user.'); $repository->unblockUser($user); // return to login. @@ -217,8 +249,13 @@ class ProfileController extends Controller */ public function deleteCode() { - app('preferences')->delete('twoFactorAuthEnabled'); - app('preferences')->delete('twoFactorAuthSecret'); + /** @var UserRepositoryInterface $repository */ + $repository = app(UserRepositoryInterface::class); + + /** @var User $user */ + $user = auth()->user(); + + $repository->setMFACode($user, null); session()->flash('success', (string)trans('firefly.pref_two_factor_auth_disabled')); session()->flash('info', (string)trans('firefly.pref_two_factor_auth_remove_it')); @@ -232,17 +269,18 @@ class ProfileController extends Controller */ public function enable2FA() { - $hasSecret = (null !== app('preferences')->get('twoFactorAuthSecret')); + /** @var User $user */ + $user = auth()->user(); + $enabledMFA = null !== $user->mfa_secret; // if we don't have a valid secret yet, redirect to the code page to get one. - if (!$hasSecret) { + if (!$enabledMFA) { return redirect(route('profile.code')); } // If FF3 already has a secret, just set the two factor auth enabled to 1, // and let the user continue with the existing secret. - - app('preferences')->set('twoFactorAuthEnabled', 1); + session()->flash('info', (string)trans('firefly.2fa_already_enabled')); return redirect(route('profile.index')); } @@ -254,11 +292,11 @@ class ProfileController extends Controller */ public function index() { + /** @var User $user */ + $user = auth()->user(); $loginProvider = config('firefly.login_provider'); // check if client token thing exists (default one) - $count = DB::table('oauth_clients') - ->where('personal_access_client', 1) - ->whereNull('user_id')->count(); + $count = DB::table('oauth_clients')->where('personal_access_client', 1)->whereNull('user_id')->count(); $this->createOAuthKeys(); @@ -267,11 +305,10 @@ class ProfileController extends Controller $repository = app(ClientRepository::class); $repository->createPersonalAccessClient(null, config('app.name') . ' Personal Access Client', 'http://localhost'); } - $subTitle = auth()->user()->email; - $userId = auth()->user()->id; - $enabled2FA = 1 === (int)app('preferences')->get('twoFactorAuthEnabled', 0)->data; - /** @var User $user */ - $user = auth()->user(); + $subTitle = $user->email; + $userId = $user->id; + $enabled2FA = null !== $user->mfa_secret; + $mfaBackupCount = count(app('preferences')->get('mfa_recovery', [])->data); // get access token or create one. $accessToken = app('preferences')->get('access_token', null); @@ -280,7 +317,26 @@ class ProfileController extends Controller $accessToken = app('preferences')->set('access_token', $token); } - return view('profile.index', compact('subTitle', 'userId', 'accessToken', 'enabled2FA', 'loginProvider')); + return view('profile.index', compact('subTitle', 'mfaBackupCount', 'userId', 'accessToken', 'enabled2FA', 'loginProvider')); + } + + /** + * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View + */ + public function newBackupCodes() + { + // generate recovery codes: + $recovery = app(Recovery::class); + $recoveryCodes = $recovery->lowercase() + ->setCount(8) // Generate 8 codes + ->setBlocks(2) // Every code must have 7 blocks + ->setChars(6) // Each block must have 16 chars + ->toArray(); + $codes = implode("\r\n", $recoveryCodes); + + app('preferences')->set('mfa_recovery', $recoveryCodes); + app('preferences')->mark(); + return view('profile.new-backup-codes', compact('codes')); } /** @@ -387,12 +443,32 @@ class ProfileController extends Controller */ public function postCode(TokenFormRequest $request) { - app('preferences')->set('twoFactorAuthEnabled', 1); - app('preferences')->set('twoFactorAuthSecret', session()->get('two-factor-secret')); + /** @var User $user */ + $user = auth()->user(); + /** @var UserRepositoryInterface $repository */ + $repository = app(UserRepositoryInterface::class); + /** @var string $secret */ + $secret = session()->get('two-factor-secret'); + $repository->setMFACode($user, $secret); session()->flash('success', (string)trans('firefly.saved_preferences')); app('preferences')->mark(); + // also save the code so replay attack is prevented. + $mfaCode = $request->get('code'); + $this->addToMFAHistory($mfaCode); + + // save backup codes in preferences: + app('preferences')->set('mfa_recovery', session()->get('temp-mfa-codes')); + + // make sure MFA is logged out. + if ('testing' !== config('app.env')) { + Google2FA::logout(); + } + + // drop all info from session: + session()->forget(['temp-mfa-secret', 'two-factor-secret', 'temp-mfa-codes', 'two-factor-codes']); + return redirect(route('profile.index')); } @@ -498,5 +574,44 @@ class ProfileController extends Controller return redirect(route('login')); } + /** + * TODO duplicate code. + * + * @param string $mfaCode + */ + private function addToMFAHistory(string $mfaCode): void + { + /** @var array $mfaHistory */ + $mfaHistory = app('preferences')->get('mfa_history', [])->data; + $entry = [ + 'time' => time(), + 'code' => $mfaCode, + ]; + $mfaHistory[] = $entry; + app('preferences')->set('mfa_history', $mfaHistory); + $this->filterMFAHistory(); + } + + /** + * Remove old entries from the preferences array. + */ + private function filterMFAHistory(): void + { + /** @var array $mfaHistory */ + $mfaHistory = app('preferences')->get('mfa_history', [])->data; + $newHistory = []; + $now = time(); + foreach ($mfaHistory as $entry) { + $time = $entry['time']; + $code = $entry['code']; + if ($now - $time <= 300) { + $newHistory[] = [ + 'time' => $time, + 'code' => $code, + ]; + } + } + app('preferences')->set('mfa_history', $newHistory); + } } diff --git a/app/Http/Controllers/Recurring/CreateController.php b/app/Http/Controllers/Recurring/CreateController.php index 88e5abd5a4..4606a6d6ac 100644 --- a/app/Http/Controllers/Recurring/CreateController.php +++ b/app/Http/Controllers/Recurring/CreateController.php @@ -25,6 +25,7 @@ namespace FireflyIII\Http\Controllers\Recurring; use Carbon\Carbon; +use FireflyIII\Exceptions\FireflyException; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Http\Requests\RecurrenceFormRequest; use FireflyIII\Models\RecurrenceRepetition; @@ -45,6 +46,7 @@ class CreateController extends Controller /** * CreateController constructor. + * @codeCoverageIgnore */ public function __construct() { @@ -71,7 +73,6 @@ class CreateController extends Controller * @param Request $request * * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View - * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ public function create(Request $request) { @@ -120,12 +121,16 @@ class CreateController extends Controller * @param RecurrenceFormRequest $request * * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector - * @throws \FireflyIII\Exceptions\FireflyException */ public function store(RecurrenceFormRequest $request) { - $data = $request->getAll(); - $recurrence = $this->recurring->store($data); + $data = $request->getAll(); + try { + $recurrence = $this->recurring->store($data); + } catch (FireflyException $e) { + session()->flash('error', $e->getMessage()); + return redirect(route('recurring.create'))->withInput(); + } $request->session()->flash('success', (string)trans('firefly.stored_new_recurrence', ['title' => $recurrence->title])); app('preferences')->mark(); diff --git a/app/Http/Controllers/Recurring/DeleteController.php b/app/Http/Controllers/Recurring/DeleteController.php index 8b7b548541..5ad24fce6c 100644 --- a/app/Http/Controllers/Recurring/DeleteController.php +++ b/app/Http/Controllers/Recurring/DeleteController.php @@ -39,6 +39,7 @@ class DeleteController extends Controller /** * DeleteController constructor. + * @codeCoverageIgnore */ public function __construct() { @@ -79,8 +80,8 @@ class DeleteController extends Controller * Destroy the recurring transaction. * * @param RecurringRepositoryInterface $repository - * @param Request $request - * @param Recurrence $recurrence + * @param Request $request + * @param Recurrence $recurrence * * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector */ diff --git a/app/Http/Controllers/Recurring/EditController.php b/app/Http/Controllers/Recurring/EditController.php index 02ce761a85..e99b900814 100644 --- a/app/Http/Controllers/Recurring/EditController.php +++ b/app/Http/Controllers/Recurring/EditController.php @@ -47,6 +47,7 @@ class EditController extends Controller /** * EditController constructor. + * @codeCoverageIgnore */ public function __construct() { @@ -70,7 +71,7 @@ class EditController extends Controller /** * Edit a recurring transaction. * - * @param Request $request + * @param Request $request * @param Recurrence $recurrence * * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View @@ -92,7 +93,7 @@ class EditController extends Controller $repetition = $recurrence->recurrenceRepetitions()->first(); $currentRepType = $repetition->repetition_type; if ('' !== $repetition->repetition_moment) { - $currentRepType .= ',' . $repetition->repetition_moment; + $currentRepType .= ',' . $repetition->repetition_moment; // @codeCoverageIgnore } // put previous url in session if not redirect from store (not "return_to_edit"). @@ -123,9 +124,12 @@ class EditController extends Controller $hasOldInput = null !== $request->old('_token'); $preFilled = [ - 'transaction_type' => strtolower($recurrence->transactionType->type), - 'active' => $hasOldInput ? (bool)$request->old('active') : $recurrence->active, - 'apply_rules' => $hasOldInput ? (bool)$request->old('apply_rules') : $recurrence->apply_rules, + 'transaction_type' => strtolower($recurrence->transactionType->type), + 'active' => $hasOldInput ? (bool)$request->old('active') : $recurrence->active, + 'apply_rules' => $hasOldInput ? (bool)$request->old('apply_rules') : $recurrence->apply_rules, + 'deposit_source_id' => $array['transactions'][0]['source_id'], + 'withdrawal_destination_id' => $array['transactions'][0]['destination_id'], + ]; return view( @@ -138,7 +142,7 @@ class EditController extends Controller * Update the recurring transaction. * * @param RecurrenceFormRequest $request - * @param Recurrence $recurrence + * @param Recurrence $recurrence * * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector * @throws \FireflyIII\Exceptions\FireflyException diff --git a/app/Http/Controllers/Recurring/IndexController.php b/app/Http/Controllers/Recurring/IndexController.php index a4f66c5fa1..b5c7af9859 100644 --- a/app/Http/Controllers/Recurring/IndexController.php +++ b/app/Http/Controllers/Recurring/IndexController.php @@ -48,6 +48,7 @@ class IndexController extends Controller /** * IndexController constructor. + * @codeCoverageIgnore */ public function __construct() { @@ -121,8 +122,8 @@ class IndexController extends Controller $transformer = app(RecurrenceTransformer::class); $transformer->setParameters(new ParameterBag); - $array = $transformer->transform($recurrence); - $transactions = $this->recurring->getTransactions($recurrence); + $array = $transformer->transform($recurrence); + $groups = $this->recurring->getTransactions($recurrence); // transform dates back to Carbon objects: foreach ($array['recurrence_repetitions'] as $index => $repetition) { @@ -133,7 +134,7 @@ class IndexController extends Controller $subTitle = (string)trans('firefly.overview_for_recurrence', ['title' => $recurrence->title]); - return view('recurring.show', compact('recurrence', 'subTitle', 'array', 'transactions')); + return view('recurring.show', compact('recurrence', 'subTitle', 'array', 'groups')); } } diff --git a/app/Http/Controllers/Report/BudgetController.php b/app/Http/Controllers/Report/BudgetController.php index d16ba0c81e..4cc0ac2cda 100644 --- a/app/Http/Controllers/Report/BudgetController.php +++ b/app/Http/Controllers/Report/BudgetController.php @@ -102,6 +102,18 @@ class BudgetController extends Controller $data = $repository->getBudgetPeriodReport($budgets, $accounts, $start, $end); $data[0] = $repository->getNoBudgetPeriodReport($accounts, $start, $end); // append report data for "no budget" $report = $this->filterPeriodReport($data); + + // depending on the carbon format (a reliable way to determine the general date difference) + // change the "listOfPeriods" call so the entire period gets included correctly. + $range = app('navigation')->preferredCarbonFormat($start, $end); + + if ('Y' === $range) { + $start->startOfYear(); + } + if ('Y-m' === $range) { + $start->startOfMonth(); + } + $periods = app('navigation')->listOfPeriods($start, $end); try { $result = view('reports.partials.budget-period', compact('report', 'periods'))->render(); diff --git a/app/Http/Controllers/Report/CategoryController.php b/app/Http/Controllers/Report/CategoryController.php index b7efe0e0e2..b8bc618745 100644 --- a/app/Http/Controllers/Report/CategoryController.php +++ b/app/Http/Controllers/Report/CategoryController.php @@ -64,13 +64,25 @@ class CategoryController extends Controller $data = $repository->periodExpenses($categories, $accounts, $start, $end); $data[0] = $repository->periodExpensesNoCategory($accounts, $start, $end); $report = $this->filterPeriodReport($data); + + // depending on the carbon format (a reliable way to determine the general date difference) + // change the "listOfPeriods" call so the entire period gets included correctly. + $range = app('navigation')->preferredCarbonFormat($start, $end); + + if ('Y' === $range) { + $start->startOfYear(); + } + if ('Y-m' === $range) { + $start->startOfMonth(); + } + $periods = app('navigation')->listOfPeriods($start, $end); try { $result = view('reports.partials.category-period', compact('report', 'periods'))->render(); // @codeCoverageIgnoreStart } catch (Throwable $e) { Log::error(sprintf('Could not render category::expenses: %s', $e->getMessage())); - $result = 'An error prevented Firefly III from rendering. Apologies.'; + $result = sprintf('An error prevented Firefly III from rendering: %s. Apologies.', $e->getMessage()); } // @codeCoverageIgnoreEnd @@ -106,13 +118,25 @@ class CategoryController extends Controller $data = $repository->periodIncome($categories, $accounts, $start, $end); $data[0] = $repository->periodIncomeNoCategory($accounts, $start, $end); $report = $this->filterPeriodReport($data); + + // depending on the carbon format (a reliable way to determine the general date difference) + // change the "listOfPeriods" call so the entire period gets included correctly. + $range = app('navigation')->preferredCarbonFormat($start, $end); + + if ('Y' === $range) { + $start->startOfYear(); + } + if ('Y-m' === $range) { + $start->startOfMonth(); + } + $periods = app('navigation')->listOfPeriods($start, $end); try { $result = view('reports.partials.category-period', compact('report', 'periods'))->render(); // @codeCoverageIgnoreStart } catch (Throwable $e) { Log::error(sprintf('Could not render category::expenses: %s', $e->getMessage())); - $result = 'An error prevented Firefly III from rendering. Apologies.'; + $result = sprintf('An error prevented Firefly III from rendering: %s. Apologies.', $e->getMessage()); } // @codeCoverageIgnoreEnd $cache->store($result); @@ -161,13 +185,13 @@ class CategoryController extends Controller $sum[$categoryId] = (float)$row['spent']; } array_multisort($sum, SORT_ASC, $report); + // @codeCoverageIgnoreStart try { $result = view('reports.partials.categories', compact('report'))->render(); $cache->store($result); - // @codeCoverageIgnoreStart } catch (Throwable $e) { Log::error(sprintf('Could not render category::expenses: %s', $e->getMessage())); - $result = 'An error prevented Firefly III from rendering. Apologies.'; + $result = sprintf('An error prevented Firefly III from rendering: %s. Apologies.', $e->getMessage()); } // @codeCoverageIgnoreEnd diff --git a/app/Http/Controllers/Report/ExpenseController.php b/app/Http/Controllers/Report/ExpenseController.php index d54486624b..ef8e3d4d2a 100644 --- a/app/Http/Controllers/Report/ExpenseController.php +++ b/app/Http/Controllers/Report/ExpenseController.php @@ -23,9 +23,8 @@ declare(strict_types=1); namespace FireflyIII\Http\Controllers\Report; use Carbon\Carbon; -use FireflyIII\Helpers\Collector\TransactionCollectorInterface; +use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Http\Controllers\Controller; -use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Support\CacheProperties; @@ -48,6 +47,7 @@ class ExpenseController extends Controller /** * Constructor for ExpenseController + * @codeCoverageIgnore */ public function __construct() { @@ -70,8 +70,8 @@ class ExpenseController extends Controller * * @param Collection $accounts * @param Collection $expense - * @param Carbon $start - * @param Carbon $end + * @param Carbon $start + * @param Carbon $end * * @return string * @SuppressWarnings(PHPMD.CyclomaticComplexity) @@ -110,7 +110,7 @@ class ExpenseController extends Controller // @codeCoverageIgnoreStart } catch (Throwable $e) { Log::error(sprintf('Could not render category::budget: %s', $e->getMessage())); - $result = 'An error prevented Firefly III from rendering. Apologies.'; + $result = sprintf('An error prevented Firefly III from rendering: %s. Apologies.', $e->getMessage()); } // @codeCoverageIgnoreEnd $cache->store($result); @@ -125,8 +125,8 @@ class ExpenseController extends Controller * * @param Collection $accounts * @param Collection $expense - * @param Carbon $start - * @param Carbon $end + * @param Carbon $start + * @param Carbon $end * * @return string * @SuppressWarnings(PHPMD.CyclomaticComplexity) @@ -175,7 +175,7 @@ class ExpenseController extends Controller // @codeCoverageIgnoreStart } catch (Throwable $e) { Log::error(sprintf('Could not render category::expenses: %s', $e->getMessage())); - $result = 'An error prevented Firefly III from rendering. Apologies.'; + $result = sprintf('An error prevented Firefly III from rendering: %s. Apologies.', $e->getMessage()); } // @codeCoverageIgnoreEnd $cache->store($result); @@ -189,8 +189,8 @@ class ExpenseController extends Controller * * @param Collection $accounts * @param Collection $expense - * @param Carbon $start - * @param Carbon $end + * @param Carbon $start + * @param Carbon $end * * @return array|mixed|string */ @@ -227,7 +227,7 @@ class ExpenseController extends Controller // @codeCoverageIgnoreStart } catch (Throwable $e) { Log::error(sprintf('Could not render category::expenses: %s', $e->getMessage())); - $result = 'An error prevented Firefly III from rendering. Apologies.'; + $result = sprintf('An error prevented Firefly III from rendering: %s. Apologies.', $e->getMessage()); } // @codeCoverageIgnoreEnd $cache->store($result); @@ -242,8 +242,8 @@ class ExpenseController extends Controller * * @param Collection $accounts * @param Collection $expense - * @param Carbon $start - * @param Carbon $end + * @param Carbon $start + * @param Carbon $end * * @return string */ @@ -253,11 +253,11 @@ class ExpenseController extends Controller $cache = new CacheProperties; $cache->addProperty($start); $cache->addProperty($end); - $cache->addProperty('expense-budget'); + $cache->addProperty('top-expense'); $cache->addProperty($accounts->pluck('id')->toArray()); $cache->addProperty($expense->pluck('id')->toArray()); if ($cache->has()) { - return $cache->get(); // @codeCoverageIgnore + //return $cache->get(); // @codeCoverageIgnore } $combined = $this->combineAccounts($expense); $all = new Collection; @@ -265,22 +265,23 @@ class ExpenseController extends Controller $all = $all->merge($combi); } // get all expenses in period: - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])->setAccounts($accounts); - $collector->setOpposingAccounts($all); - $set = $collector->getTransactions(); - $sorted = $set->sortBy( - function (Transaction $transaction) { - return (float)$transaction->transaction_amount; - } - ); + $collector->setAccounts($all)->withAccountInformation(); + $sorted = $collector->getExtractedJournals(); + + usort($sorted, function ($a, $b) { + return $a['amount'] <=> $b['amount']; // @codeCoverageIgnore + }); + try { $result = view('reports.partials.top-transactions', compact('sorted'))->render(); // @codeCoverageIgnoreStart } catch (Throwable $e) { Log::error(sprintf('Could not render category::topExpense: %s', $e->getMessage())); - $result = 'An error prevented Firefly III from rendering. Apologies.'; + $result = sprintf('An error prevented Firefly III from rendering: %s. Apologies.', $e->getMessage()); } // @codeCoverageIgnoreEnd $cache->store($result); @@ -293,8 +294,8 @@ class ExpenseController extends Controller * * @param Collection $accounts * @param Collection $expense - * @param Carbon $start - * @param Carbon $end + * @param Carbon $start + * @param Carbon $end * * @return mixed|string */ @@ -304,11 +305,11 @@ class ExpenseController extends Controller $cache = new CacheProperties; $cache->addProperty($start); $cache->addProperty($end); - $cache->addProperty('expense-budget'); + $cache->addProperty('top-income'); $cache->addProperty($accounts->pluck('id')->toArray()); $cache->addProperty($expense->pluck('id')->toArray()); if ($cache->has()) { - return $cache->get(); // @codeCoverageIgnore + //return $cache->get(); // @codeCoverageIgnore } $combined = $this->combineAccounts($expense); $all = new Collection; @@ -316,22 +317,28 @@ class ExpenseController extends Controller $all = $all->merge($combi); } // get all expenses in period: - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setRange($start, $end)->setTypes([TransactionType::DEPOSIT])->setAccounts($accounts); - $collector->setOpposingAccounts($all); - $set = $collector->getTransactions(); - $sorted = $set->sortByDesc( - function (Transaction $transaction) { - return (float)$transaction->transaction_amount; - } - ); + + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + + $total = $accounts->merge($all); + $collector->setRange($start, $end)->setTypes([TransactionType::DEPOSIT])->setAccounts($total)->withAccountInformation(); + $sorted = $collector->getExtractedJournals(); + + foreach (array_keys($sorted) as $key) { + $sorted[$key]['amount'] = bcmul($sorted[$key]['amount'], '-1'); + } + + usort($sorted, function ($a, $b) { + return $a['amount'] <=> $b['amount']; // @codeCoverageIgnore + }); + try { $result = view('reports.partials.top-transactions', compact('sorted'))->render(); // @codeCoverageIgnoreStart } catch (Throwable $e) { Log::error(sprintf('Could not render category::topIncome: %s', $e->getMessage())); - $result = 'An error prevented Firefly III from rendering. Apologies.'; + $result = sprintf('An error prevented Firefly III from rendering: %s. Apologies.', $e->getMessage()); } // @codeCoverageIgnoreEnd $cache->store($result); diff --git a/app/Http/Controllers/Report/OperationsController.php b/app/Http/Controllers/Report/OperationsController.php index 8b8eca74c1..c5bffbba86 100644 --- a/app/Http/Controllers/Report/OperationsController.php +++ b/app/Http/Controllers/Report/OperationsController.php @@ -41,6 +41,7 @@ class OperationsController extends Controller /** * OperationsController constructor. + * @codeCoverageIgnore */ public function __construct() { diff --git a/app/Http/Controllers/Rule/CreateController.php b/app/Http/Controllers/Rule/CreateController.php index 464f5d4608..af9e56b033 100644 --- a/app/Http/Controllers/Rule/CreateController.php +++ b/app/Http/Controllers/Rule/CreateController.php @@ -45,6 +45,7 @@ class CreateController extends Controller /** * RuleController constructor. + * @codeCoverageIgnore */ public function __construct() { @@ -88,8 +89,8 @@ class CreateController extends Controller $oldActions = $this->getPreviousActions($request); } - $triggerCount = \count($oldTriggers); - $actionCount = \count($oldActions); + $triggerCount = count($oldTriggers); + $actionCount = count($oldActions); $subTitleIcon = 'fa-clone'; // title depends on whether or not there is a rule group: @@ -140,8 +141,8 @@ class CreateController extends Controller $oldTriggers = $this->getTriggersForBill($bill); $oldActions = $this->getActionsForBill($bill); - $triggerCount = \count($oldTriggers); - $actionCount = \count($oldActions); + $triggerCount = count($oldTriggers); + $actionCount = count($oldActions); $subTitleIcon = 'fa-clone'; // title depends on whether or not there is a rule group: diff --git a/app/Http/Controllers/Rule/DeleteController.php b/app/Http/Controllers/Rule/DeleteController.php index a568f2dd91..61f605ef2f 100644 --- a/app/Http/Controllers/Rule/DeleteController.php +++ b/app/Http/Controllers/Rule/DeleteController.php @@ -39,6 +39,7 @@ class DeleteController extends Controller /** * RuleController constructor. + * @codeCoverageIgnore */ public function __construct() { diff --git a/app/Http/Controllers/Rule/EditController.php b/app/Http/Controllers/Rule/EditController.php index 299c613d37..d136c175ee 100644 --- a/app/Http/Controllers/Rule/EditController.php +++ b/app/Http/Controllers/Rule/EditController.php @@ -45,6 +45,7 @@ class EditController extends Controller /** * RuleController constructor. + * @codeCoverageIgnore */ public function __construct() { @@ -79,19 +80,19 @@ class EditController extends Controller $oldActions = []; $oldTriggers = []; // has old input? - if (\count($request->old()) > 0) { + if (count($request->old()) > 0) { $oldTriggers = $this->getPreviousTriggers($request); - $triggerCount = \count($oldTriggers); + $triggerCount = count($oldTriggers); $oldActions = $this->getPreviousActions($request); - $actionCount = \count($oldActions); + $actionCount = count($oldActions); } // overrule old input when it has no rule data: if (0 === $triggerCount && 0 === $actionCount) { $oldTriggers = $this->getCurrentTriggers($rule); - $triggerCount = \count($oldTriggers); + $triggerCount = count($oldTriggers); $oldActions = $this->getCurrentActions($rule); - $actionCount = \count($oldActions); + $actionCount = count($oldActions); } $hasOldInput = null !== $request->old('_token'); diff --git a/app/Http/Controllers/Rule/IndexController.php b/app/Http/Controllers/Rule/IndexController.php index 13a6d21978..4593b67de6 100644 --- a/app/Http/Controllers/Rule/IndexController.php +++ b/app/Http/Controllers/Rule/IndexController.php @@ -45,6 +45,7 @@ class IndexController extends Controller /** * RuleController constructor. + * @codeCoverageIgnore */ public function __construct() { @@ -102,7 +103,7 @@ class IndexController extends Controller public function reorderRuleActions(Request $request, Rule $rule): JsonResponse { $ids = $request->get('actions'); - if (\is_array($ids)) { + if (is_array($ids)) { $this->ruleRepos->reorderRuleActions($rule, $ids); } @@ -120,7 +121,7 @@ class IndexController extends Controller public function reorderRuleTriggers(Request $request, Rule $rule): JsonResponse { $ids = $request->get('triggers'); - if (\is_array($ids)) { + if (is_array($ids)) { $this->ruleRepos->reorderRuleTriggers($rule, $ids); } diff --git a/app/Http/Controllers/Rule/SelectController.php b/app/Http/Controllers/Rule/SelectController.php index 1f3d54ec14..b5a20f49b7 100644 --- a/app/Http/Controllers/Rule/SelectController.php +++ b/app/Http/Controllers/Rule/SelectController.php @@ -26,6 +26,7 @@ namespace FireflyIII\Http\Controllers\Rule; use Carbon\Carbon; use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Http\Requests\SelectTransactionsRequest; use FireflyIII\Http\Requests\TestRuleFormRequest; @@ -34,6 +35,7 @@ use FireflyIII\Models\Rule; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Support\Http\Controllers\RequestInformation; use FireflyIII\Support\Http\Controllers\RuleManagement; +use FireflyIII\TransactionRules\Engine\RuleEngine; use FireflyIII\TransactionRules\TransactionMatcher; use FireflyIII\User; use Illuminate\Http\JsonResponse; @@ -76,7 +78,7 @@ class SelectController extends Controller * Execute the given rule on a set of existing transactions. * * @param SelectTransactionsRequest $request - * @param Rule $rule + * @param Rule $rule * * @return RedirectResponse */ @@ -88,18 +90,26 @@ class SelectController extends Controller $accounts = $this->accountRepos->getAccountsById($request->get('accounts')); $startDate = new Carbon($request->get('start_date')); $endDate = new Carbon($request->get('end_date')); + $rules = [$rule->id]; - // Create a job to do the work asynchronously - $job = new ExecuteRuleOnExistingTransactions($rule); + /** @var RuleEngine $ruleEngine */ + $ruleEngine = app(RuleEngine::class); + $ruleEngine->setUser(auth()->user()); + $ruleEngine->setRulesToApply($rules); + $ruleEngine->setTriggerMode(RuleEngine::TRIGGER_STORE); - // Apply parameters to the job - $job->setUser($user); - $job->setAccounts($accounts); - $job->setStartDate($startDate); - $job->setEndDate($endDate); + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setAccounts($accounts); + $collector->setRange($startDate, $endDate); + $journals = $collector->getExtractedJournals(); - // Dispatch a new job to execute it in a queue - $this->dispatch($job); + /** @var array $journal */ + foreach ($journals as $journal) { + Log::debug('Start of new journal.'); + $ruleEngine->processJournalArray($journal); + Log::debug('Done with all rules for this group + done with journal.'); + } // Tell the user that the job is queued session()->flash('success', (string)trans('firefly.applied_rule_selection', ['title' => $rule->title])); @@ -145,7 +155,7 @@ class SelectController extends Controller // build trigger array from response $triggers = $this->getValidTriggerList($request); - if (0 === \count($triggers)) { + if (0 === count($triggers)) { return response()->json(['html' => '', 'warning' => (string)trans('firefly.warning_no_valid_triggers')]); // @codeCoverageIgnore } @@ -181,11 +191,12 @@ class SelectController extends Controller // Return json response $view = 'ERROR, see logs.'; try { - $view = view('list.journals-tiny', ['transactions' => $matchingTransactions])->render(); + $view = view('list.journals-array-tiny', ['journals' => $matchingTransactions])->render(); // @codeCoverageIgnoreStart } catch (Throwable $exception) { Log::error(sprintf('Could not render view in testTriggers(): %s', $exception->getMessage())); Log::error($exception->getTraceAsString()); + $view = sprintf('Could not render list.journals-tiny: %s', $exception->getMessage()); } // @codeCoverageIgnoreEnd @@ -212,7 +223,7 @@ class SelectController extends Controller { $triggers = $rule->ruleTriggers; - if (0 === \count($triggers)) { + if (0 === count($triggers)) { return response()->json(['html' => '', 'warning' => (string)trans('firefly.warning_no_valid_triggers')]); // @codeCoverageIgnore } @@ -236,17 +247,17 @@ class SelectController extends Controller // Warn the user if only a subset of transactions is returned $warning = ''; - if ($matchingTransactions->count() === $limit) { + if (count($matchingTransactions) === $limit) { $warning = (string)trans('firefly.warning_transaction_subset', ['max_num_transactions' => $limit]); // @codeCoverageIgnore } - if (0 === $matchingTransactions->count()) { + if (0 === count($matchingTransactions)) { $warning = (string)trans('firefly.warning_no_matching_transactions', ['num_transactions' => $range]); // @codeCoverageIgnore } // Return json response $view = 'ERROR, see logs.'; try { - $view = view('list.journals-tiny', ['transactions' => $matchingTransactions])->render(); + $view = view('list.journals-array-tiny', ['journals' => $matchingTransactions])->render(); // @codeCoverageIgnoreStart } catch (Throwable $exception) { Log::error(sprintf('Could not render view in testTriggersByRule(): %s', $exception->getMessage())); diff --git a/app/Http/Controllers/RuleGroup/CreateController.php b/app/Http/Controllers/RuleGroup/CreateController.php new file mode 100644 index 0000000000..ddf41a733e --- /dev/null +++ b/app/Http/Controllers/RuleGroup/CreateController.php @@ -0,0 +1,102 @@ +. + */ + +namespace FireflyIII\Http\Controllers\RuleGroup; + + +use FireflyIII\Http\Controllers\Controller; +use FireflyIII\Http\Requests\RuleGroupFormRequest; +use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface; + +/** + * Class CreateController + */ +class CreateController extends Controller +{ + /** @var RuleGroupRepositoryInterface */ + private $repository; + + /** + * CreateController constructor. + * @codeCoverageIgnore + */ + public function __construct() + { + parent::__construct(); + + $this->middleware( + function ($request, $next) { + app('view')->share('title', (string)trans('firefly.rules')); + app('view')->share('mainTitleIcon', 'fa-random'); + + $this->repository = app(RuleGroupRepositoryInterface::class); + + return $next($request); + } + ); + } + + /** + * Create a new rule group. + * + * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View + */ + public function create() + { + $subTitleIcon = 'fa-clone'; + $subTitle = (string)trans('firefly.make_new_rule_group'); + + // put previous url in session if not redirect from store (not "create another"). + if (true !== session('rule-groups.create.fromStore')) { + $this->rememberPreviousUri('rule-groups.create.uri'); + } + session()->forget('rule-groups.create.fromStore'); + + return view('rules.rule-group.create', compact('subTitleIcon', 'subTitle')); + } + + /** + * Store the rule group. + * + * @param RuleGroupFormRequest $request + * + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector + */ + public function store(RuleGroupFormRequest $request) + { + $data = $request->getRuleGroupData(); + $ruleGroup = $this->repository->store($data); + + session()->flash('success', (string)trans('firefly.created_new_rule_group', ['title' => $ruleGroup->title])); + app('preferences')->mark(); + + $redirect = redirect($this->getPreviousUri('rule-groups.create.uri')); + if (1 === (int)$request->get('create_another')) { + // @codeCoverageIgnoreStart + session()->put('rule-groups.create.fromStore', true); + + $redirect = redirect(route('rule-groups.create'))->withInput(); + // @codeCoverageIgnoreEnd + } + + return $redirect; + } +} \ No newline at end of file diff --git a/app/Http/Controllers/RuleGroup/DeleteController.php b/app/Http/Controllers/RuleGroup/DeleteController.php new file mode 100644 index 0000000000..1c0afeb52b --- /dev/null +++ b/app/Http/Controllers/RuleGroup/DeleteController.php @@ -0,0 +1,98 @@ +. + */ + +namespace FireflyIII\Http\Controllers\RuleGroup; + + +use FireflyIII\Http\Controllers\Controller; +use FireflyIII\Models\RuleGroup; +use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface; +use Illuminate\Http\Request; + +/** + * Class DeleteController + */ +class DeleteController extends Controller +{ + /** @var RuleGroupRepositoryInterface */ + private $repository; + + /** + * DeleteController constructor. + * @codeCoverageIgnore + */ + public function __construct() + { + parent::__construct(); + + $this->middleware( + function ($request, $next) { + app('view')->share('title', (string)trans('firefly.rules')); + app('view')->share('mainTitleIcon', 'fa-random'); + + $this->repository = app(RuleGroupRepositoryInterface::class); + + return $next($request); + } + ); + } + + + /** + * Delete a rule group. + * + * @param RuleGroup $ruleGroup + * + * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View + */ + public function delete(RuleGroup $ruleGroup) + { + $subTitle = (string)trans('firefly.delete_rule_group', ['title' => $ruleGroup->title]); + + // put previous url in session + $this->rememberPreviousUri('rule-groups.delete.uri'); + + return view('rules.rule-group.delete', compact('ruleGroup', 'subTitle')); + } + + /** + * Actually destroy the rule group. + * + * @param Request $request + * @param RuleGroup $ruleGroup + * + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector + */ + public function destroy(Request $request, RuleGroup $ruleGroup) + { + $title = $ruleGroup->title; + + /** @var RuleGroup $moveTo */ + $moveTo = $this->repository->find((int)$request->get('move_rules_before_delete')); + $this->repository->destroy($ruleGroup, $moveTo); + + session()->flash('success', (string)trans('firefly.deleted_rule_group', ['title' => $title])); + app('preferences')->mark(); + + return redirect($this->getPreviousUri('rule-groups.delete.uri')); + } + +} \ No newline at end of file diff --git a/app/Http/Controllers/RuleGroup/EditController.php b/app/Http/Controllers/RuleGroup/EditController.php new file mode 100644 index 0000000000..e6ab8aacf7 --- /dev/null +++ b/app/Http/Controllers/RuleGroup/EditController.php @@ -0,0 +1,150 @@ +. + */ + +namespace FireflyIII\Http\Controllers\RuleGroup; + +use FireflyIII\Http\Controllers\Controller; +use FireflyIII\Http\Requests\RuleGroupFormRequest; +use FireflyIII\Models\RuleGroup; +use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface; +use Illuminate\Http\Request; + +/** + * Class EditController + */ +class EditController extends Controller +{ + /** @var RuleGroupRepositoryInterface */ + private $repository; + + /** + * EditController constructor. + * @codeCoverageIgnore + */ + public function __construct() + { + parent::__construct(); + + $this->middleware( + function ($request, $next) { + app('view')->share('title', (string)trans('firefly.rules')); + app('view')->share('mainTitleIcon', 'fa-random'); + + $this->repository = app(RuleGroupRepositoryInterface::class); + + return $next($request); + } + ); + } + + /** + * Move a rule group down. + * + * @param RuleGroup $ruleGroup + * + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector + */ + public function down(RuleGroup $ruleGroup) + { + $this->repository->moveDown($ruleGroup); + + return redirect(route('rules.index')); + } + + + /** + * Edit a rule group. + * + * @param Request $request + * @param RuleGroup $ruleGroup + * + * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View + */ + public function edit(Request $request, RuleGroup $ruleGroup) + { + $subTitle = (string)trans('firefly.edit_rule_group', ['title' => $ruleGroup->title]); + + $hasOldInput = null !== $request->old('_token'); + $preFilled = [ + 'active' => $hasOldInput ? (bool)$request->old('active') : $ruleGroup->active, + ]; + + + // put previous url in session if not redirect from store (not "return_to_edit"). + if (true !== session('rule-groups.edit.fromUpdate')) { + $this->rememberPreviousUri('rule-groups.edit.uri'); + } + session()->forget('rule-groups.edit.fromUpdate'); + session()->flash('preFilled', $preFilled); + + return view('rules.rule-group.edit', compact('ruleGroup', 'subTitle')); + } + + /** + * Move the rule group up. + * + * @param RuleGroup $ruleGroup + * + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector + * + * @SuppressWarnings(PHPMD.ShortMethodName) + */ + public function up(RuleGroup $ruleGroup) + { + $this->repository->moveUp($ruleGroup); + + return redirect(route('rules.index')); + } + + /** + * Update the rule group. + * + * @param RuleGroupFormRequest $request + * @param RuleGroup $ruleGroup + * + * @return $this|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector + */ + public function update(RuleGroupFormRequest $request, RuleGroup $ruleGroup) + { + $data = [ + 'title' => $request->input('title'), + 'description' => $request->input('description'), + 'active' => 1 === (int)$request->input('active'), + ]; + + $this->repository->update($ruleGroup, $data); + + session()->flash('success', (string)trans('firefly.updated_rule_group', ['title' => $ruleGroup->title])); + app('preferences')->mark(); + $redirect = redirect($this->getPreviousUri('rule-groups.edit.uri')); + if (1 === (int)$request->get('return_to_edit')) { + // @codeCoverageIgnoreStart + session()->put('rule-groups.edit.fromUpdate', true); + + $redirect = redirect(route('rule-groups.edit', [$ruleGroup->id]))->withInput(['return_to_edit' => 1]); + // @codeCoverageIgnoreEnd + } + + // redirect to previous URL. + return $redirect; + } + +} \ No newline at end of file diff --git a/app/Http/Controllers/RuleGroup/ExecutionController.php b/app/Http/Controllers/RuleGroup/ExecutionController.php new file mode 100644 index 0000000000..747a7328d9 --- /dev/null +++ b/app/Http/Controllers/RuleGroup/ExecutionController.php @@ -0,0 +1,136 @@ +. + */ + +namespace FireflyIII\Http\Controllers\RuleGroup; + + +use Carbon\Carbon; +use FireflyIII\Helpers\Collector\GroupCollectorInterface; +use FireflyIII\Http\Controllers\Controller; +use FireflyIII\Http\Requests\SelectTransactionsRequest; +use FireflyIII\Models\Rule; +use FireflyIII\Models\RuleGroup; +use FireflyIII\Repositories\Account\AccountRepositoryInterface; +use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface; +use FireflyIII\TransactionRules\Engine\RuleEngine; +use Illuminate\Http\RedirectResponse; +use Log; + +/** + * Class ExecutionController + */ +class ExecutionController extends Controller +{ + /** @var AccountRepositoryInterface */ + private $repository; + + /** @var RuleGroupRepositoryInterface */ + private $ruleGroupRepository; + + /** + * ExecutionController constructor. + * @codeCoverageIgnore + */ + public function __construct() + { + parent::__construct(); + + $this->middleware( + function ($request, $next) { + app('view')->share('title', (string)trans('firefly.rules')); + app('view')->share('mainTitleIcon', 'fa-random'); + + $this->repository = app(AccountRepositoryInterface::class); + $this->ruleGroupRepository = app(RuleGroupRepositoryInterface::class); + + return $next($request); + } + ); + } + + + /** + * Execute the given rulegroup on a set of existing transactions. + * + * @param SelectTransactionsRequest $request + * @param RuleGroup $ruleGroup + * + * @return RedirectResponse + * @throws \Exception + */ + public function execute(SelectTransactionsRequest $request, RuleGroup $ruleGroup): RedirectResponse + { + // Get parameters specified by the user + $accounts = $this->repository->getAccountsById($request->get('accounts')); + $startDate = new Carbon($request->get('start_date')); + $endDate = new Carbon($request->get('end_date')); + + // start looping. + /** @var RuleEngine $ruleEngine */ + $ruleEngine = app(RuleEngine::class); + $ruleEngine->setUser(auth()->user()); + + $rules = []; + /** @var Rule $rule */ + foreach ($this->ruleGroupRepository->getActiveRules($ruleGroup) as $rule) { + $rules[] = $rule->id; + } + + $ruleEngine->setRulesToApply($rules); + $ruleEngine->setTriggerMode(RuleEngine::TRIGGER_STORE); + + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setAccounts($accounts); + $collector->setRange($startDate, $endDate); + $journals = $collector->getExtractedJournals(); + + /** @var array $journal */ + foreach ($journals as $journal) { + Log::debug('Start of new journal.'); + $ruleEngine->processJournalArray($journal); + Log::debug('Done with all rules for this group + done with journal.'); + } + + // Tell the user that the job is queued + session()->flash('success', (string)trans('firefly.applied_rule_group_selection', ['title' => $ruleGroup->title])); + + return redirect()->route('rules.index'); + } + + /** + * Select transactions to apply the group on. + * + * @param RuleGroup $ruleGroup + * + * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View + */ + public function selectTransactions(RuleGroup $ruleGroup) + { + $first = session('first')->format('Y-m-d'); + $today = Carbon::now()->format('Y-m-d'); + $subTitle = (string)trans('firefly.apply_rule_group_selection', ['title' => $ruleGroup->title]); + + return view('rules.rule-group.select-transactions', compact('first', 'today', 'ruleGroup', 'subTitle')); + } + + +} \ No newline at end of file diff --git a/app/Http/Controllers/RuleGroupController.php b/app/Http/Controllers/RuleGroupController.php deleted file mode 100644 index 8a99cd2ab7..0000000000 --- a/app/Http/Controllers/RuleGroupController.php +++ /dev/null @@ -1,295 +0,0 @@ -. - */ -declare(strict_types=1); - -namespace FireflyIII\Http\Controllers; - -use Carbon\Carbon; -use FireflyIII\Http\Requests\RuleGroupFormRequest; -use FireflyIII\Http\Requests\SelectTransactionsRequest; -use FireflyIII\Jobs\ExecuteRuleGroupOnExistingTransactions; -use FireflyIII\Models\RuleGroup; -use FireflyIII\Repositories\Account\AccountRepositoryInterface; -use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface; -use FireflyIII\User; -use Illuminate\Http\RedirectResponse; -use Illuminate\Http\Request; - -/** - * Class RuleGroupController. - * - * @SuppressWarnings(PHPMD.TooManyPublicMethods) - */ -class RuleGroupController extends Controller -{ - /** - * RuleGroupController constructor. - */ - public function __construct() - { - parent::__construct(); - - $this->middleware( - function ($request, $next) { - app('view')->share('title', (string)trans('firefly.rules')); - app('view')->share('mainTitleIcon', 'fa-random'); - - return $next($request); - } - ); - } - - /** - * Create a new rule group. - * - * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View - */ - public function create() - { - $subTitleIcon = 'fa-clone'; - $subTitle = (string)trans('firefly.make_new_rule_group'); - - // put previous url in session if not redirect from store (not "create another"). - if (true !== session('rule-groups.create.fromStore')) { - $this->rememberPreviousUri('rule-groups.create.uri'); - } - session()->forget('rule-groups.create.fromStore'); - - return view('rules.rule-group.create', compact('subTitleIcon', 'subTitle')); - } - - /** - * Delege a rule group. - * - * @param RuleGroup $ruleGroup - * - * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View - */ - public function delete(RuleGroup $ruleGroup) - { - $subTitle = (string)trans('firefly.delete_rule_group', ['title' => $ruleGroup->title]); - - // put previous url in session - $this->rememberPreviousUri('rule-groups.delete.uri'); - - return view('rules.rule-group.delete', compact('ruleGroup', 'subTitle')); - } - - /** - * Actually destroy the rule group. - * - * @param Request $request - * @param RuleGroupRepositoryInterface $repository - * @param RuleGroup $ruleGroup - * - * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector - */ - public function destroy(Request $request, RuleGroupRepositoryInterface $repository, RuleGroup $ruleGroup) - { - /** @var User $user */ - $user = auth()->user(); - $title = $ruleGroup->title; - - /** @var RuleGroup $moveTo */ - $moveTo = $user->ruleGroups()->find((int)$request->get('move_rules_before_delete')); - - $repository->destroy($ruleGroup, $moveTo); - - session()->flash('success', (string)trans('firefly.deleted_rule_group', ['title' => $title])); - app('preferences')->mark(); - - return redirect($this->getPreviousUri('rule-groups.delete.uri')); - } - - /** - * Move a rule group down. - * - * @param RuleGroupRepositoryInterface $repository - * @param RuleGroup $ruleGroup - * - * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector - */ - public function down(RuleGroupRepositoryInterface $repository, RuleGroup $ruleGroup) - { - $repository->moveDown($ruleGroup); - - return redirect(route('rules.index')); - } - - /** - * Edit a rule group. - * - * @param Request $request - * @param RuleGroup $ruleGroup - * - * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View - */ - public function edit(Request $request, RuleGroup $ruleGroup) - { - $subTitle = (string)trans('firefly.edit_rule_group', ['title' => $ruleGroup->title]); - - $hasOldInput = null !== $request->old('_token'); - $preFilled = [ - 'active' => $hasOldInput ? (bool)$request->old('active') : $ruleGroup->active, - ]; - - - // put previous url in session if not redirect from store (not "return_to_edit"). - if (true !== session('rule-groups.edit.fromUpdate')) { - $this->rememberPreviousUri('rule-groups.edit.uri'); - } - session()->forget('rule-groups.edit.fromUpdate'); - session()->flash('preFilled', $preFilled); - - return view('rules.rule-group.edit', compact('ruleGroup', 'subTitle')); - } - - /** - * Execute the given rulegroup on a set of existing transactions. - * - * @param SelectTransactionsRequest $request - * @param AccountRepositoryInterface $repository - * @param RuleGroup $ruleGroup - * - * @return RedirectResponse - */ - public function execute(SelectTransactionsRequest $request, AccountRepositoryInterface $repository, RuleGroup $ruleGroup): RedirectResponse - { - // Get parameters specified by the user - /** @var User $user */ - $user = auth()->user(); - $accounts = $repository->getAccountsById($request->get('accounts')); - $startDate = new Carbon($request->get('start_date')); - $endDate = new Carbon($request->get('end_date')); - - // Create a job to do the work asynchronously - $job = new ExecuteRuleGroupOnExistingTransactions($ruleGroup); - - // Apply parameters to the job - $job->setUser($user); - $job->setAccounts($accounts); - $job->setStartDate($startDate); - $job->setEndDate($endDate); - - // Dispatch a new job to execute it in a queue - $this->dispatch($job); - - // Tell the user that the job is queued - session()->flash('success', (string)trans('firefly.applied_rule_group_selection', ['title' => $ruleGroup->title])); - - return redirect()->route('rules.index'); - } - - /** - * Select transactions to apply the group on. - * - * @param RuleGroup $ruleGroup - * - * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View - */ - public function selectTransactions(RuleGroup $ruleGroup) - { - $first = session('first')->format('Y-m-d'); - $today = Carbon::now()->format('Y-m-d'); - $subTitle = (string)trans('firefly.apply_rule_group_selection', ['title' => $ruleGroup->title]); - - return view('rules.rule-group.select-transactions', compact('first', 'today', 'ruleGroup', 'subTitle')); - } - - /** - * Store the rule group. - * - * @param RuleGroupFormRequest $request - * @param RuleGroupRepositoryInterface $repository - * - * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector - */ - public function store(RuleGroupFormRequest $request, RuleGroupRepositoryInterface $repository) - { - $data = $request->getRuleGroupData(); - $ruleGroup = $repository->store($data); - - session()->flash('success', (string)trans('firefly.created_new_rule_group', ['title' => $ruleGroup->title])); - app('preferences')->mark(); - - $redirect = redirect($this->getPreviousUri('rule-groups.create.uri')); - if (1 === (int)$request->get('create_another')) { - // @codeCoverageIgnoreStart - session()->put('rule-groups.create.fromStore', true); - - $redirect = redirect(route('rule-groups.create'))->withInput(); - // @codeCoverageIgnoreEnd - } - - return $redirect; - } - - /** - * Move the rule group up. - * - * @param RuleGroupRepositoryInterface $repository - * @param RuleGroup $ruleGroup - * - * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector - * - * @SuppressWarnings(PHPMD.ShortMethodName) - */ - public function up(RuleGroupRepositoryInterface $repository, RuleGroup $ruleGroup) - { - $repository->moveUp($ruleGroup); - - return redirect(route('rules.index')); - } - - /** - * Update the rule group. - * - * @param RuleGroupFormRequest $request - * @param RuleGroupRepositoryInterface $repository - * @param RuleGroup $ruleGroup - * - * @return $this|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector - */ - public function update(RuleGroupFormRequest $request, RuleGroupRepositoryInterface $repository, RuleGroup $ruleGroup) - { - $data = [ - 'title' => $request->input('title'), - 'description' => $request->input('description'), - 'active' => 1 === (int)$request->input('active'), - ]; - - $repository->update($ruleGroup, $data); - - session()->flash('success', (string)trans('firefly.updated_rule_group', ['title' => $ruleGroup->title])); - app('preferences')->mark(); - $redirect = redirect($this->getPreviousUri('rule-groups.edit.uri')); - if (1 === (int)$request->get('return_to_edit')) { - // @codeCoverageIgnoreStart - session()->put('rule-groups.edit.fromUpdate', true); - - $redirect = redirect(route('rule-groups.edit', [$ruleGroup->id]))->withInput(['return_to_edit' => 1]); - // @codeCoverageIgnoreEnd - } - - // redirect to previous URL. - return $redirect; - } -} diff --git a/app/Http/Controllers/SearchController.php b/app/Http/Controllers/SearchController.php index b186248d12..62d917de03 100644 --- a/app/Http/Controllers/SearchController.php +++ b/app/Http/Controllers/SearchController.php @@ -22,11 +22,9 @@ declare(strict_types=1); namespace FireflyIII\Http\Controllers; -use FireflyIII\Support\CacheProperties; use FireflyIII\Support\Search\SearchInterface; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; -use Illuminate\Support\Collection; use Log; use Throwable; @@ -62,15 +60,18 @@ class SearchController extends Controller */ public function index(Request $request, SearchInterface $searcher) { - $fullQuery = (string)$request->get('q'); + $fullQuery = (string)$request->get('search'); // parse search terms: $searcher->parseQuery($fullQuery); - $query = $searcher->getWordsAsString(); + $query = $searcher->getWordsAsString(); $modifiers = $searcher->getModifiers(); - $subTitle = (string)trans('breadcrumbs.search_result', ['query' => $query]); + $subTitle = (string)trans('breadcrumbs.search_result', ['query' => $query]); - return view('search.index', compact('query','modifiers', 'fullQuery', 'subTitle')); + return view( + 'search.index', + compact('query', 'modifiers', 'fullQuery', 'subTitle') + ); } /** @@ -87,11 +88,11 @@ class SearchController extends Controller $searcher->parseQuery($fullQuery); $searcher->setLimit((int)config('firefly.search_result_limit')); - $transactions = $searcher->searchTransactions(); + $groups = $searcher->searchTransactions(); $searchTime = $searcher->searchTime(); // in seconds try { - $html = view('search.search', compact('transactions','searchTime'))->render(); + $html = view('search.search', compact('groups','searchTime'))->render(); // @codeCoverageIgnoreStart } catch (Throwable $e) { Log::error(sprintf('Cannot render search.search: %s', $e->getMessage())); @@ -100,6 +101,6 @@ class SearchController extends Controller // @codeCoverageIgnoreEnd - return response()->json(['count' => $transactions->count(), 'html' => $html]); + return response()->json(['count' => $groups->count(), 'html' => $html]); } } diff --git a/app/Http/Controllers/System/InstallController.php b/app/Http/Controllers/System/InstallController.php index 81a26e19bf..7f6388ecef 100644 --- a/app/Http/Controllers/System/InstallController.php +++ b/app/Http/Controllers/System/InstallController.php @@ -31,6 +31,7 @@ use FireflyIII\Http\Controllers\Controller; use FireflyIII\Support\Facades\Preferences; use FireflyIII\Support\Http\Controllers\GetConfigurationData; use Illuminate\Http\JsonResponse; +use Illuminate\Http\Request; use Illuminate\Support\Arr; use Laravel\Passport\Passport; use Log; @@ -50,6 +51,11 @@ class InstallController extends Controller public const BASEDIR_ERROR = 'Firefly III cannot execute the upgrade commands. It is not allowed to because of an open_basedir restriction.'; /** @var string Other errors */ public const OTHER_ERROR = 'An unknown error prevented Firefly III from executing the upgrade commands. Sorry.'; + + /** @var array All upgrade commands. */ + private $upgradeCommands; + + /** @noinspection MagicMethodsValidityInspection */ /** @noinspection PhpMissingParentConstructorInspection */ /** @@ -58,39 +64,41 @@ class InstallController extends Controller public function __construct() { // empty on purpose. - } + $this->upgradeCommands = [ + // there are 3 initial commands + 'migrate' => ['--seed' => true, '--force' => true], + 'firefly-iii:decrypt-all' => [], + 'generate-keys' => [], // an exception :( - /** - * Do database decrypt. - * - * @return \Illuminate\Http\JsonResponse - */ - public function decrypt(): JsonResponse - { - if ($this->hasForbiddenFunctions()) { - return response()->json(['error' => true, 'message' => self::FORBIDDEN_ERROR]); - } - try { - Log::debug('Am now calling decrypt database routine...'); - Artisan::call('firefly:decrypt-all'); - Log::debug(Artisan::output()); - } catch (Exception $e) { - Log::error($e->getMessage()); - Log::error($e->getTraceAsString()); - if (strpos($e->getMessage(), 'open_basedir restriction in effect')) { - Cache::clear(); + // there are 12 upgrade commands. + 'firefly-iii:transaction-identifiers' => [], + 'firefly-iii:migrate-to-groups' => [], + 'firefly-iii:account-currencies' => [], + 'firefly-iii:transfer-currencies' => [], + 'firefly-iii:other-currencies' => [], + 'firefly-iii:migrate-notes' => [], + 'firefly-iii:migrate-attachments' => [], + 'firefly-iii:bills-to-rules' => [], + 'firefly-iii:bl-currency' => [], + 'firefly-iii:cc-liabilities' => [], + 'firefly-iii:back-to-journals' => [], + 'firefly-iii:rename-account-meta' => [], - return response()->json(['error' => true, 'message' => self::BASEDIR_ERROR]); - } - - return response()->json(['error' => true, 'message' => self::OTHER_ERROR . ' ' . $e->getMessage()]); - } - // clear cache as well. - Cache::clear(); - Preferences::mark(); - - - return response()->json(['error' => false, 'message' => 'OK']); + // there are 13 verify commands. + 'firefly-iii:fix-piggies' => [], + 'firefly-iii:create-link-types' => [], + 'firefly-iii:create-access-tokens' => [], + 'firefly-iii:remove-bills' => [], + 'firefly-iii:enable-currencies' => [], + 'firefly-iii:fix-transfer-budgets' => [], + 'firefly-iii:fix-uneven-amount' => [], + 'firefly-iii:delete-zero-amount' => [], + 'firefly-iii:delete-orphaned-transactions' => [], + 'firefly-iii:delete-empty-journals' => [], + 'firefly-iii:delete-empty-groups' => [], + 'firefly-iii:fix-account-types' => [], + 'firefly-iii:rename-meta-fields' => [], + ]; } /** @@ -108,16 +116,9 @@ class InstallController extends Controller /** * Create specific RSA keys. - * - * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector */ - public function keys() + public function keys(): void { - if ($this->hasForbiddenFunctions()) { - return response()->json(['error' => true, 'message' => self::FORBIDDEN_ERROR]); - } - // create keys manually because for some reason the passport namespace - // does not exist $rsa = new RSA(); $keys = $rsa->createKey(4096); @@ -127,113 +128,77 @@ class InstallController extends Controller ]; if (file_exists($publicKey) || file_exists($privateKey)) { - return response()->json(['error' => false, 'message' => 'OK']); + return; } file_put_contents($publicKey, Arr::get($keys, 'publickey')); file_put_contents($privateKey, Arr::get($keys, 'privatekey')); - - // clear cache as well. - Cache::clear(); - Preferences::mark(); - - return response()->json(['error' => false, 'message' => 'OK']); } /** - * Run migration commands. + * @param Request $request * * @return JsonResponse */ - public function migrate(): JsonResponse + public function runCommand(Request $request): JsonResponse { - if ($this->hasForbiddenFunctions()) { - return response()->json(['error' => true, 'message' => self::FORBIDDEN_ERROR]); - } + $requestIndex = (int)$request->get('index'); + $response = [ + 'hasNextCommand' => false, + 'done' => true, + 'next' => 0, + 'previous' => null, + 'error' => false, + 'errorMessage' => null, + ]; - try { - Log::debug('Am now calling migrate routine...'); - Artisan::call('migrate', ['--seed' => true, '--force' => true]); - Log::debug(Artisan::output()); - } catch (Exception $e) { - Log::error($e->getMessage()); - Log::error($e->getTraceAsString()); - if (strpos($e->getMessage(), 'open_basedir restriction in effect')) { - return response()->json(['error' => true, 'message' => self::BASEDIR_ERROR]); + Log::debug(sprintf('Will now run commands. Request index is %d', $requestIndex)); + $index = 0; + foreach ($this->upgradeCommands as $command => $args) { + Log::debug(sprintf('Current command is "%s", index is %d', $command, $index)); + if ($index < $requestIndex) { + Log::debug('Will not execute.'); + $index++; + continue; } + if ($index >= $requestIndex) { + Log::debug(sprintf('%d >= %d, will execute the command.', $index, $requestIndex)); + Log::debug(sprintf('Will now call command %s with args.', $command), $args); + try { + if ('generate-keys' === $command) { + $this->keys(); + } + if ('generate-keys' !== $command) { + Artisan::call($command, $args); + Log::debug(Artisan::output()); + } + } catch (Exception $e) { + Log::error($e->getMessage()); + Log::error($e->getTraceAsString()); + if (strpos($e->getMessage(), 'open_basedir restriction in effect')) { + $response['error'] = true; + $response['errorMessage'] = self::BASEDIR_ERROR; - return response()->json(['error' => true, 'message' => self::OTHER_ERROR]); - } - // clear cache as well. - Cache::clear(); - Preferences::mark(); + return response()->json($response); + } + $response['error'] = true; + $response['errorMessage'] = self::OTHER_ERROR . ' ' . $e->getMessage(); - return response()->json(['error' => false, 'message' => 'OK']); - } + return response()->json($response); + } + // clear cache as well. + Cache::clear(); + Preferences::mark(); - /** - * Do database upgrade. - * - * @return \Illuminate\Http\JsonResponse - */ - public function upgrade(): JsonResponse - { - if ($this->hasForbiddenFunctions()) { - return response()->json(['error' => true, 'message' => self::FORBIDDEN_ERROR]); - } - try { - Log::debug('Am now calling upgrade database routine...'); - Artisan::call('firefly:upgrade-database'); - Log::debug(Artisan::output()); - } catch (Exception $e) { - Log::error($e->getMessage()); - Log::error($e->getTraceAsString()); - if (strpos($e->getMessage(), 'open_basedir restriction in effect')) { - return response()->json(['error' => true, 'message' => self::BASEDIR_ERROR]); + $index++; + $response['hasNextCommand'] = true; + $response['previous'] = $command; + break; } - - return response()->json(['error' => true, 'message' => self::OTHER_ERROR . ' ' . $e->getMessage()]); } - // clear cache as well. - Cache::clear(); - Preferences::mark(); + $response['next'] = $index; - - return response()->json(['error' => false, 'message' => 'OK']); + return response()->json($response); } - - /** - * Do database verification. - * - * @return \Illuminate\Http\JsonResponse - */ - public function verify(): JsonResponse - { - if ($this->hasForbiddenFunctions()) { - return response()->json(['error' => true, 'message' => self::FORBIDDEN_ERROR]); - } - try { - Log::debug('Am now calling verify database routine...'); - Artisan::call('firefly:verify'); - Log::debug(Artisan::output()); - } catch (Exception $e) { - Log::error($e->getMessage()); - Log::error($e->getTraceAsString()); - if (strpos($e->getMessage(), 'open_basedir restriction in effect')) { - return response()->json(['error' => true, 'message' => self::BASEDIR_ERROR]); - } - - return response()->json(['error' => true, 'message' => self::OTHER_ERROR . ' ' . $e->getMessage()]); - } - - - // clear cache as well. - Cache::clear(); - Preferences::mark(); - - return response()->json(['error' => false, 'message' => 'OK']); - } - - } diff --git a/app/Http/Controllers/TagController.php b/app/Http/Controllers/TagController.php index 040a9a89dd..0a1ad7a732 100644 --- a/app/Http/Controllers/TagController.php +++ b/app/Http/Controllers/TagController.php @@ -24,8 +24,7 @@ declare(strict_types=1); namespace FireflyIII\Http\Controllers; use Carbon\Carbon; -use FireflyIII\Helpers\Collector\TransactionCollectorInterface; -use FireflyIII\Helpers\Filter\InternalTransferFilter; +use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Http\Requests\TagFormRequest; use FireflyIII\Models\Tag; use FireflyIII\Repositories\Tag\TagRepositoryInterface; @@ -171,8 +170,8 @@ class TagController extends Controller /** * Show a single tag. * - * @param Request $request - * @param Tag $tag + * @param Request $request + * @param Tag $tag * @param Carbon|null $start * @param Carbon|null $end * @@ -193,26 +192,30 @@ class TagController extends Controller 'firefly.journals_in_period_for_tag', ['tag' => $tag->tag, 'start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat),] ); - $periods = $this->getTagPeriodOverview($tag, $start); + + $startPeriod = $this->repository->firstUseDate($tag); + $startPeriod = $startPeriod ?? new Carbon; + $endPeriod = clone $end; + $periods = $this->getTagPeriodOverview($tag, $startPeriod, $endPeriod); $path = route('tags.show', [$tag->id, $start->format('Y-m-d'), $end->format('Y-m-d')]); - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setAllAssetAccounts()->setRange($start, $end)->setLimit($pageSize)->setPage($page)->withOpposingAccount() - ->setTag($tag)->withBudgetInformation()->withCategoryInformation()->removeFilter(InternalTransferFilter::class); - $transactions = $collector->getPaginatedTransactions(); - $transactions->setPath($path); + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setRange($start, $end)->setLimit($pageSize)->setPage($page)->withAccountInformation() + ->setTag($tag)->withBudgetInformation()->withCategoryInformation(); + $groups = $collector->getPaginatedGroups(); + $groups->setPath($path); $sums = $this->repository->sumsOfTag($tag, $start, $end); - return view('tags.show', compact('tag', 'sums', 'periods', 'subTitle', 'subTitleIcon', 'transactions', 'start', 'end')); + return view('tags.show', compact('tag', 'sums', 'periods', 'subTitle', 'subTitleIcon', 'groups', 'start', 'end')); } /** * Show a single tag over all time. * * @param Request $request - * @param Tag $tag + * @param Tag $tag * * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View * @@ -225,20 +228,20 @@ class TagController extends Controller $subTitleIcon = 'fa-tag'; $page = (int)$request->get('page'); $pageSize = (int)app('preferences')->get('listPageSize', 50)->data; - $periods = new Collection; + $periods = []; $subTitle = (string)trans('firefly.all_journals_for_tag', ['tag' => $tag->tag]); $start = $this->repository->firstUseDate($tag) ?? new Carbon; $end = new Carbon; $path = route('tags.show', [$tag->id, 'all']); - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setAllAssetAccounts()->setRange($start, $end)->setLimit($pageSize)->setPage($page)->withOpposingAccount() - ->setTag($tag)->withBudgetInformation()->withCategoryInformation()->removeFilter(InternalTransferFilter::class); - $transactions = $collector->getPaginatedTransactions(); - $transactions->setPath($path); + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setRange($start, $end)->setLimit($pageSize)->setPage($page)->withAccountInformation() + ->setTag($tag)->withBudgetInformation()->withCategoryInformation(); + $groups = $collector->getPaginatedGroups(); + $groups->setPath($path); $sums = $this->repository->sumsOfTag($tag, $start, $end); - return view('tags.show', compact('tag', 'sums', 'periods', 'subTitle', 'subTitleIcon', 'transactions', 'start', 'end')); + return view('tags.show', compact('tag', 'sums', 'periods', 'subTitle', 'subTitleIcon', 'groups', 'start', 'end')); } /** @@ -276,7 +279,7 @@ class TagController extends Controller * Update a tag. * * @param TagFormRequest $request - * @param Tag $tag + * @param Tag $tag * * @return RedirectResponse */ diff --git a/app/Http/Controllers/Transaction/BulkController.php b/app/Http/Controllers/Transaction/BulkController.php index c4c28cdc83..83b1d1c9b8 100644 --- a/app/Http/Controllers/Transaction/BulkController.php +++ b/app/Http/Controllers/Transaction/BulkController.php @@ -43,6 +43,7 @@ class BulkController extends Controller /** * BulkController constructor. + * @codeCoverageIgnore */ public function __construct() { @@ -62,24 +63,24 @@ class BulkController extends Controller /** * Edit a set of journals in bulk. * + * TODO user wont be able to tell if journal is part of split. + * * @param Collection $journals * * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View */ - public function edit(Collection $journals) + public function edit(array $journals) { $subTitle = (string)trans('firefly.mass_bulk_journals'); + $this->rememberPreviousUri('transactions.bulk-edit.uri'); + + // make amounts positive. + // get list of budgets: /** @var BudgetRepositoryInterface $repository */ $repository = app(BudgetRepositoryInterface::class); $budgetList = app('expandedform')->makeSelectListWithEmpty($repository->getActiveBudgets()); - // collect some useful meta data for the mass edit: - $journals->each( - function (TransactionJournal $journal) { - $journal->transaction_count = $journal->transactions()->count(); - } - ); return view('transactions.bulk.edit', compact('journals', 'subTitle', 'budgetList')); } @@ -97,45 +98,80 @@ class BulkController extends Controller public function update(BulkEditJournalRequest $request) { $journalIds = $request->get('journals'); - $journalIds = \is_array($journalIds) ? $journalIds : []; + $journalIds = is_array($journalIds) ? $journalIds : []; $ignoreCategory = 1 === (int)$request->get('ignore_category'); $ignoreBudget = 1 === (int)$request->get('ignore_budget'); $ignoreTags = 1 === (int)$request->get('ignore_tags'); $count = 0; foreach ($journalIds as $journalId) { - $journal = $this->repository->findNull((int)$journalId); - if (null === $journal) { - continue; - } - - $count++; - Log::debug(sprintf('Found journal #%d', $journal->id)); - - // update category if not told to ignore - if (false === $ignoreCategory) { - Log::debug(sprintf('Set category to %s', $request->string('category'))); - - $this->repository->updateCategory($journal, $request->string('category')); - } - - // update budget if not told to ignore (and is withdrawal) - if (false === $ignoreBudget) { - Log::debug(sprintf('Set budget to %d', $request->integer('budget_id'))); - $this->repository->updateBudget($journal, $request->integer('budget_id')); - } - - // update tags: - if (false === $ignoreTags) { - Log::debug(sprintf('Set tags to %s', $request->string('budget_id'))); - $this->repository->updateTags($journal, ['tags' => explode(',', $request->string('tags'))]); + $journalId = (int)$journalId; + $journal = $this->repository->findNull($journalId); + if (null !== $journal) { + $resultA = $this->updateJournalBudget($journal, $ignoreBudget, $request->integer('budget_id')); + $resultB = $this->updateJournalTags($journal, $ignoreTags, explode(',', $request->string('tags'))); + $resultC = $this->updateJournalCategory($journal, $ignoreCategory, $request->string('category')); + if ($resultA || $resultB || $resultC) { + $count++; + } } } - app('preferences')->mark(); $request->session()->flash('success', (string)trans('firefly.mass_edited_transactions_success', ['amount' => $count])); // redirect to previous URL: return redirect($this->getPreviousUri('transactions.bulk-edit.uri')); } + + /** + * @param TransactionJournal $journal + * @param bool $ignoreUpdate + * @param array $tags + * @return bool + */ + private function updateJournalTags(TransactionJournal $journal, bool $ignoreUpdate, array $tags): bool + { + + if (true === $ignoreUpdate) { + return false; + } + Log::debug(sprintf('Set tags to %s', implode(',', $tags))); + $this->repository->updateTags($journal, $tags); + + return true; + } + + /** + * @param TransactionJournal $journal + * @param bool $ignoreUpdate + * @param string $category + * @return bool + */ + private function updateJournalCategory(TransactionJournal $journal, bool $ignoreUpdate, string $category): bool + { + if (true === $ignoreUpdate) { + return false; + } + Log::debug(sprintf('Set budget to %s', $category)); + $this->repository->updateCategory($journal, $category); + + return true; + } + + /** + * @param TransactionJournal $journal + * @param bool $ignoreUpdate + * @param int $budgetId + * @return bool + */ + private function updateJournalBudget(TransactionJournal $journal, bool $ignoreUpdate, int $budgetId): bool + { + if (true === $ignoreUpdate) { + return false; + } + Log::debug(sprintf('Set budget to %d', $budgetId)); + $this->repository->updateBudget($journal, $budgetId); + + return true; + } } diff --git a/app/Http/Controllers/Transaction/ConvertController.php b/app/Http/Controllers/Transaction/ConvertController.php index c21be8db8c..1d488e1767 100644 --- a/app/Http/Controllers/Transaction/ConvertController.php +++ b/app/Http/Controllers/Transaction/ConvertController.php @@ -22,19 +22,30 @@ declare(strict_types=1); namespace FireflyIII\Http\Controllers\Transaction; -use FireflyIII\Events\UpdatedTransactionJournal; +use Carbon\Carbon; +use FireflyIII\Events\UpdatedTransactionGroup; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Http\Controllers\Controller; +use FireflyIII\Models\Account; +use FireflyIII\Models\AccountType; +use FireflyIII\Models\TransactionGroup; use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionType; +use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Journal\JournalRepositoryInterface; +use FireflyIII\Services\Internal\Update\JournalUpdateService; use FireflyIII\Support\Http\Controllers\ModelInformation; +use FireflyIII\Transformers\TransactionGroupTransformer; +use FireflyIII\Validation\AccountValidator; use Illuminate\Http\Request; use Log; use View; + /** * Class ConvertController. + * + * TODO when converting to a split transfer, all sources and destinations must be the same. */ class ConvertController extends Controller { @@ -45,6 +56,7 @@ class ConvertController extends Controller /** * ConvertController constructor. + * @codeCoverageIgnore */ public function __construct() { @@ -67,110 +79,286 @@ class ConvertController extends Controller /** * Show overview of a to be converted transaction. * - * @param TransactionType $destinationType - * @param TransactionJournal $journal + * @param TransactionType $destinationType + * @param TransactionGroup $group * * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View + * @throws \Exception */ - public function index(TransactionType $destinationType, TransactionJournal $journal) + public function index(TransactionType $destinationType, TransactionGroup $group) { - // @codeCoverageIgnoreStart - if ($this->isOpeningBalance($journal)) { - Log::debug('This is an opening balance.'); + /** @var TransactionGroupTransformer $transformer */ + $transformer = app(TransactionGroupTransformer::class); - return $this->redirectToAccount($journal); + /** @var TransactionJournal $first */ + $first = $group->transactionJournals()->first(); + $sourceType = $first->transactionType; + // return to account. + if (!in_array($sourceType->type, [TransactionType::WITHDRAWAL, TransactionType::TRANSFER, TransactionType::DEPOSIT], true)) { + return $this->redirectToAccount($first); // @codeCoverageIgnore } - // @codeCoverageIgnoreEnd - $positiveAmount = $this->repository->getJournalTotal($journal); - $sourceType = $journal->transactionType; - $subTitle = (string)trans('firefly.convert_to_' . $destinationType->type, ['description' => $journal->description]); - $subTitleIcon = 'fa-exchange'; + + $groupTitle = $group->title ?? $first->description; + $groupArray = $transformer->transformObject($group); + $subTitle = (string)trans('firefly.convert_to_' . $destinationType->type, ['description' => $groupTitle]); + $subTitleIcon = 'fa-exchange'; + + // get a list of asset accounts and liabilities and stuff, in various combinations: + $validDepositSources = $this->getValidDepositSources(); + $validWithdrawalDests = $this->getValidWithdrawalDests(); + $liabilities = $this->getLiabilities(); + $assets = $this->getAssetAccounts(); + + // old input variables: + $preFilled = [ + 'source_name' => old('source_name'), + ]; if ($sourceType->type === $destinationType->type) { // cannot convert to its own type. Log::debug('This is already a transaction of the expected type..'); session()->flash('info', (string)trans('firefly.convert_is_already_type_' . $destinationType->type)); - return redirect(route('transactions.show', [$journal->id])); + return redirect(route('transactions.show', [$group->id])); } - if ($journal->transactions()->count() > 2) { // cannot convert split. - Log::info('This journal has more than two transactions.'); - session()->flash('error', (string)trans('firefly.cannot_convert_split_journal')); - - return redirect(route('transactions.show', [$journal->id])); - } - - // get source and destination account: - $sourceAccount = $this->repository->getJournalSourceAccounts($journal)->first(); - $destinationAccount = $this->repository->getJournalDestinationAccounts($journal)->first(); - return view( 'transactions.convert', compact( - 'sourceType', 'destinationType', 'journal', 'positiveAmount', 'sourceAccount', 'destinationAccount', 'sourceType', + 'sourceType', 'destinationType', + 'group', 'groupTitle', 'groupArray', 'assets', 'validDepositSources', 'liabilities', + 'validWithdrawalDests', 'preFilled', 'subTitle', 'subTitleIcon' ) ); } - /** * Do the conversion. * - * @param Request $request - * @param TransactionType $destinationType - * @param TransactionJournal $journal + * @param Request $request + * @param TransactionType $destinationType + * @param TransactionGroup $group * * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector * * @throws FireflyException - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ - public function postIndex(Request $request, TransactionType $destinationType, TransactionJournal $journal) + public function postIndex(Request $request, TransactionType $destinationType, TransactionGroup $group) { - // @codeCoverageIgnoreStart - if ($this->isOpeningBalance($journal)) { - Log::debug('Journal is opening balance, return to account.'); + /** @var TransactionJournal $journal */ + foreach ($group->transactionJournals as $journal) { + // catch FF exception. + try { + $this->convertJournal($journal, $destinationType, $request->all()); + } catch (FireflyException $e) { + session()->flash('error', $e->getMessage()); - return $this->redirectToAccount($journal); - } - // @codeCoverageIgnoreEnd - - $data = $request->all(); - - if ($journal->transactionType->type === $destinationType->type) { - Log::info('Journal is already of the desired type.'); - session()->flash('error', (string)trans('firefly.convert_is_already_type_' . $destinationType->type)); - - return redirect(route('transactions.show', [$journal->id])); + return redirect()->route('transactions.convert.index', [strtolower($destinationType->type), $group->id])->withInput(); + } } - if ($journal->transactions()->count() > 2) { - Log::info('Journal has more than two transactions.'); - session()->flash('error', (string)trans('firefly.cannot_convert_split_journal')); - - return redirect(route('transactions.show', [$journal->id])); - } - - // get the new source and destination account: - $source = $this->getSourceAccount($journal, $destinationType, $data); - $destination = $this->getDestinationAccount($journal, $destinationType, $data); - - // update the journal: - $errors = $this->repository->convert($journal, $destinationType, $source, $destination); - - if ($errors->count() > 0) { - Log::error('Errors while converting: ', $errors->toArray()); - - return redirect(route('transactions.convert.index', [strtolower($destinationType->type), $journal->id]))->withErrors($errors)->withInput(); - } - - // Success? Fire rules! - event(new UpdatedTransactionJournal($journal)); - + // correct transfers: + $group->refresh(); + $this->correctTransfer($group); session()->flash('success', (string)trans('firefly.converted_to_' . $destinationType->type)); + event(new UpdatedTransactionGroup($group)); - return redirect(route('transactions.show', [$journal->id])); + return redirect(route('transactions.show', [$group->id])); + } + + /** + * @return array + * @throws \Exception + */ + private function getAssetAccounts(): array + { + // make repositories + /** @var AccountRepositoryInterface $repository */ + $repository = app(AccountRepositoryInterface::class); + $accountList = $repository->getActiveAccountsByType([AccountType::ASSET]); + $defaultCurrency = app('amount')->getDefaultCurrency(); + $grouped = []; + // group accounts: + /** @var Account $account */ + foreach ($accountList as $account) { + $balance = app('steam')->balance($account, new Carbon); + $currency = $repository->getAccountCurrency($account) ?? $defaultCurrency; + $role = (string)$repository->getMetaValue($account, 'account_role'); + if ('' === $role) { + $role = 'no_account_type'; // @codeCoverageIgnore + } + + $key = (string)trans('firefly.opt_group_' . $role); + $grouped[$key][$account->id] = $account->name . ' (' . app('amount')->formatAnything($currency, $balance, false) . ')'; + } + + return $grouped; + } + + /** + * @return array + * @throws \Exception + */ + private function getLiabilities(): array + { + // make repositories + /** @var AccountRepositoryInterface $repository */ + $repository = app(AccountRepositoryInterface::class); + $accountList = $repository->getActiveAccountsByType([AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE]); + $defaultCurrency = app('amount')->getDefaultCurrency(); + $grouped = []; + // group accounts: + /** @var Account $account */ + foreach ($accountList as $account) { + $balance = app('steam')->balance($account, new Carbon); + $currency = $repository->getAccountCurrency($account) ?? $defaultCurrency; + $role = 'l_' . $account->accountType->type; + $key = (string)trans('firefly.opt_group_' . $role); + $grouped[$key][$account->id] = $account->name . ' (' . app('amount')->formatAnything($currency, $balance, false) . ')'; + } + + return $grouped; + } + + /** + * @return array + */ + private function getValidDepositSources(): array + { + // make repositories + /** @var AccountRepositoryInterface $repository */ + $repository = app(AccountRepositoryInterface::class); + $liabilityTypes = [AccountType::MORTGAGE, AccountType::DEBT, AccountType::CREDITCARD, AccountType::LOAN]; + $accountList = $repository + ->getActiveAccountsByType([AccountType::REVENUE, AccountType::CASH, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE]); + $grouped = []; + // group accounts: + /** @var Account $account */ + foreach ($accountList as $account) { + $role = (string)$repository->getMetaValue($account, 'account_role'); + $name = $account->name; + if ('' === $role) { + $role = 'no_account_type'; // @codeCoverageIgnore + } + + // maybe it's a liability thing: + if (in_array($account->accountType->type, $liabilityTypes, true)) { + $role = 'l_' . $account->accountType->type; // @codeCoverageIgnore + } + if (AccountType::CASH === $account->accountType->type) { + // @codeCoverageIgnoreStart + $role = 'cash_account'; + $name = sprintf('(%s)', trans('firefly.cash')); + // @codeCoverageIgnoreEnd + } + if (AccountType::REVENUE === $account->accountType->type) { + $role = 'revenue_account'; // @codeCoverageIgnore + } + + $key = (string)trans('firefly.opt_group_' . $role); + $grouped[$key][$account->id] = $name; + } + + return $grouped; + } + + /** + * @return array + */ + private function getValidWithdrawalDests(): array + { + // make repositories + /** @var AccountRepositoryInterface $repository */ + $repository = app(AccountRepositoryInterface::class); + $liabilityTypes = [AccountType::MORTGAGE, AccountType::DEBT, AccountType::CREDITCARD, AccountType::LOAN]; + $accountList = $repository + ->getActiveAccountsByType([AccountType::EXPENSE, AccountType::CASH, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE]); + $grouped = []; + // group accounts: + /** @var Account $account */ + foreach ($accountList as $account) { + $role = (string)$repository->getMetaValue($account, 'account_role'); + $name = $account->name; + if ('' === $role) { + $role = 'no_account_type'; // @codeCoverageIgnore + } + + // maybe it's a liability thing: + if (in_array($account->accountType->type, $liabilityTypes, true)) { + $role = 'l_' . $account->accountType->type; // @codeCoverageIgnore + } + if (AccountType::CASH === $account->accountType->type) { + // @codeCoverageIgnoreStart + $role = 'cash_account'; + $name = sprintf('(%s)', trans('firefly.cash')); + // @codeCoverageIgnoreEnd + } + if (AccountType::EXPENSE === $account->accountType->type) { + $role = 'expense_account'; // @codeCoverageIgnore + } + + $key = (string)trans('firefly.opt_group_' . $role); + $grouped[$key][$account->id] = $name; + } + + return $grouped; + } + + /** + * @param TransactionJournal $journal + * @param TransactionType $transactionType + * @param array $data + * @return TransactionJournal + * @throws FireflyException + */ + private function convertJournal(TransactionJournal $journal, TransactionType $transactionType, array $data): TransactionJournal + { + /** @var AccountValidator $validator */ + $validator = app(AccountValidator::class); + $validator->setUser(auth()->user()); + $validator->setTransactionType($transactionType->type); + + $sourceId = $data['source_id'][$journal->id] ?? null; + $sourceName = $data['source_name'][$journal->id] ?? null; + $destinationId = $data['destination_id'][$journal->id] ?? null; + $destinationName = $data['destination_name'][$journal->id] ?? null; + + // double check its not an empty string. + $sourceId = '' === $sourceId || null === $sourceId ? null : (int)$sourceId; + $sourceName = '' === $sourceName ? null : $sourceName; + $destinationId = '' === $destinationId || null === $destinationId ? null : (int)$destinationId; + $destinationName = '' === $destinationName ? null : $destinationName; + $validSource = $validator->validateSource($sourceId, $sourceName); + $validDestination = $validator->validateDestination($destinationId, $destinationName); + + if (false === $validSource) { + throw new FireflyException(sprintf(trans('firefly.convert_invalid_source'), $journal->id)); + } + if (false === $validDestination) { + throw new FireflyException(sprintf(trans('firefly.convert_invalid_destination'), $journal->id)); + } + + $update = [ + 'source_id' => $sourceId, + 'source_name' => $sourceName, + 'destination_id' => $destinationId, + 'destination_name' => $destinationName, + 'type' => $transactionType->type, + ]; + /** @var JournalUpdateService $service */ + $service = app(JournalUpdateService::class); + $service->setTransactionJournal($journal); + $service->setData($update); + $service->update(); + $journal->refresh(); + + return $journal; + } + + /** + * @param TransactionGroup $group + */ + private function correctTransfer(TransactionGroup $group): void + { } } diff --git a/app/Http/Controllers/Transaction/CreateController.php b/app/Http/Controllers/Transaction/CreateController.php new file mode 100644 index 0000000000..c91a82d9d8 --- /dev/null +++ b/app/Http/Controllers/Transaction/CreateController.php @@ -0,0 +1,91 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Http\Controllers\Transaction; + + +use FireflyIII\Http\Controllers\Controller; +use FireflyIII\Models\TransactionType; +use FireflyIII\Repositories\Account\AccountRepositoryInterface; + +/** + * Class CreateController + */ +class CreateController extends Controller +{ + /** + * CreateController constructor. + * @codeCoverageIgnore + */ + public function __construct() + { + parent::__construct(); + + $maxFileSize = app('steam')->phpBytes(ini_get('upload_max_filesize')); + $maxPostSize = app('steam')->phpBytes(ini_get('post_max_size')); + $uploadSize = min($maxFileSize, $maxPostSize); + app('view')->share('uploadSize', $uploadSize); + $this->middleware( + static function ($request, $next) { + + app('view')->share('title', (string)trans('firefly.transactions')); + app('view')->share('mainTitleIcon', 'fa-repeat'); + + return $next($request); + } + ); + } + + /** + * Create a new transaction group. + * + * @param string|null objectType + * + * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View + */ + public function create(?string $objectType) + { + /** @var AccountRepositoryInterface $repository */ + $repository = app(AccountRepositoryInterface::class); + $cash = $repository->getCashAccount(); + $preFilled = session()->has('preFilled') ? session('preFilled') : []; + $subTitle = (string)trans('breadcrumbs.create_new_transaction'); + $subTitleIcon = 'fa-plus'; + $optionalFields = app('preferences')->get('transaction_journal_optional_fields', [])->data; + $allowedOpposingTypes = config('firefly.allowed_opposing_types'); + $accountToTypes = config('firefly.account_to_transaction'); + $defaultCurrency = app('amount')->getDefaultCurrency(); + $previousUri = $this->rememberPreviousUri('transactions.create.uri'); + + session()->put('preFilled', $preFilled); + + + return view( + 'transactions.create', compact( + 'subTitleIcon', 'cash', 'objectType', 'subTitle', 'defaultCurrency', 'previousUri', 'optionalFields', 'preFilled', + 'allowedOpposingTypes', + 'accountToTypes' + ) + ); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Transaction/DeleteController.php b/app/Http/Controllers/Transaction/DeleteController.php new file mode 100644 index 0000000000..4a926364ef --- /dev/null +++ b/app/Http/Controllers/Transaction/DeleteController.php @@ -0,0 +1,112 @@ +. + */ + +namespace FireflyIII\Http\Controllers\Transaction; + + +use FireflyIII\Http\Controllers\Controller; +use FireflyIII\Models\TransactionGroup; +use FireflyIII\Repositories\Journal\JournalRepositoryInterface; +use FireflyIII\Repositories\TransactionGroup\TransactionGroupRepositoryInterface; +use Illuminate\Http\RedirectResponse; +use Log; +use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use URL; + +/** + * Class DeleteController + */ +class DeleteController extends Controller +{ + /** @var TransactionGroupRepositoryInterface */ + private $repository; + + /** + * IndexController constructor. + * @codeCoverageIgnore + */ + public function __construct() + { + parent::__construct(); + + // translations: + $this->middleware( + function ($request, $next) { + app('view')->share('title', (string)trans('firefly.transactions')); + app('view')->share('mainTitleIcon', 'fa-repeat'); + + $this->repository = app(TransactionGroupRepositoryInterface::class); + + return $next($request); + } + ); + } + + /** + * Shows the form that allows a user to delete a transaction journal. + * + * @param TransactionGroup $group + * + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View + */ + public function delete(TransactionGroup $group) + { + Log::debug(sprintf('Start of delete view for group #%d', $group->id)); + + $journal = $group->transactionJournals->first(); + if (null === $journal) { + throw new NotFoundHttpException; + } + $objectType = strtolower($journal->transaction_type_type ?? $journal->transactionType->type); + $subTitle = (string)trans('firefly.delete_' . $objectType, ['description' => $group->title ?? $journal->description]); + $previous = URL::previous(route('index')); + // put previous url in session + Log::debug('Will try to remember previous URI'); + $this->rememberPreviousUri('transactions.delete.uri'); + + return view('transactions.delete', compact('group', 'journal', 'subTitle', 'objectType', 'previous')); + } + + /** + * Actually destroys the journal. + * + * @param TransactionGroup $group + * + * @return RedirectResponse + */ + public function destroy(TransactionGroup $group): RedirectResponse + { + $journal = $group->transactionJournals->first(); + if (null === $journal) { + throw new NotFoundHttpException; + } + $objectType = strtolower($journal->transaction_type_type ?? $journal->transactionType->type); + session()->flash('success', (string)trans('firefly.deleted_' . strtolower($objectType), ['description' => $group->title ?? $journal->description])); + + $this->repository->destroy($group); + + app('preferences')->mark(); + + return redirect($this->getPreviousUri('transactions.delete.uri')); + } + + +} \ No newline at end of file diff --git a/app/Http/Controllers/Transaction/EditController.php b/app/Http/Controllers/Transaction/EditController.php new file mode 100644 index 0000000000..a31c35409e --- /dev/null +++ b/app/Http/Controllers/Transaction/EditController.php @@ -0,0 +1,80 @@ +. + */ + +namespace FireflyIII\Http\Controllers\Transaction; + + +use FireflyIII\Http\Controllers\Controller; +use FireflyIII\Models\TransactionGroup; +use FireflyIII\Repositories\Account\AccountRepositoryInterface; + +/** + * Class EditController + */ +class EditController extends Controller +{ + + /** + * EditController constructor. + * @codeCoverageIgnore + */ + public function __construct() + { + parent::__construct(); + + $maxFileSize = app('steam')->phpBytes(ini_get('upload_max_filesize')); + $maxPostSize = app('steam')->phpBytes(ini_get('post_max_size')); + $uploadSize = min($maxFileSize, $maxPostSize); + + + app('view')->share('uploadSize', $uploadSize); + + // some useful repositories: + $this->middleware( + static function ($request, $next) { + + app('view')->share('title', (string)trans('firefly.transactions')); + app('view')->share('mainTitleIcon', 'fa-repeat'); + + return $next($request); + } + ); + } + + + /** + * @param TransactionGroup $transactionGroup + * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View + */ + public function edit(TransactionGroup $transactionGroup) + { + /** @var AccountRepositoryInterface $repository */ + $repository = app(AccountRepositoryInterface::class); + $allowedOpposingTypes = config('firefly.allowed_opposing_types'); + $accountToTypes = config('firefly.account_to_transaction'); + $defaultCurrency = app('amount')->getDefaultCurrency(); + $cash = $repository->getCashAccount(); + $previousUri = $this->rememberPreviousUri('transactions.edit.uri'); + + return view('transactions.edit', compact('cash', 'transactionGroup', 'allowedOpposingTypes', 'accountToTypes', 'defaultCurrency', 'previousUri')); + } + +} \ No newline at end of file diff --git a/app/Http/Controllers/Transaction/IndexController.php b/app/Http/Controllers/Transaction/IndexController.php new file mode 100644 index 0000000000..466ba01f71 --- /dev/null +++ b/app/Http/Controllers/Transaction/IndexController.php @@ -0,0 +1,156 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Http\Controllers\Transaction; + + +use Carbon\Carbon; +use FireflyIII\Helpers\Collector\GroupCollectorInterface; +use FireflyIII\Http\Controllers\Controller; +use FireflyIII\Repositories\Journal\JournalRepositoryInterface; +use FireflyIII\Support\Http\Controllers\PeriodOverview; +use Illuminate\Http\Request; + +/** + * Class IndexController + */ +class IndexController extends Controller +{ + use PeriodOverview; + + /** @var JournalRepositoryInterface */ + private $repository; + + /** + * IndexController constructor. + * @codeCoverageIgnore + */ + public function __construct() + { + parent::__construct(); + + // translations: + $this->middleware( + function ($request, $next) { + app('view')->share('mainTitleIcon', 'fa-credit-card'); + app('view')->share('title', (string)trans('firefly.accounts')); + + $this->repository = app(JournalRepositoryInterface::class); + + return $next($request); + } + ); + } + + /** + * Index for a range of transactions. + * + * @param Request $request + * @param string $objectType + * @param Carbon|null $start + * @param Carbon|null $end + * + * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View + * @throws \Exception + */ + public function index(Request $request, string $objectType, Carbon $start = null, Carbon $end = null) + { + $subTitleIcon = config('firefly.transactionIconsByType.' . $objectType); + $types = config('firefly.transactionTypesByType.' . $objectType); + $page = (int)$request->get('page'); + $pageSize = (int)app('preferences')->get('listPageSize', 50)->data; + if (null === $start) { + $start = session('start'); + $end = session('end'); + } + if (null === $end) { + $end = session('end'); // @codeCoverageIgnore + } + + [$start, $end] = $end < $start ? [$end, $start] : [$start, $end]; + $path = route('transactions.index', [$objectType, $start->format('Y-m-d'), $end->format('Y-m-d')]); + $startStr = $start->formatLocalized($this->monthAndDayFormat); + $endStr = $end->formatLocalized($this->monthAndDayFormat); + $subTitle = (string)trans(sprintf('firefly.title_%s_between', $objectType), ['start' => $startStr, 'end' => $endStr]); + + $firstJournal = $this->repository->firstNull(); + $startPeriod = null === $firstJournal ? new Carbon : $firstJournal->date; + $endPeriod = clone $end; + $periods = $this->getTransactionPeriodOverview($objectType, $startPeriod, $endPeriod); + + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + + $collector->setRange($start, $end) + ->setTypes($types) + ->setLimit($pageSize) + ->setPage($page) + ->withBudgetInformation() + ->withCategoryInformation() + ->withAccountInformation(); + $groups = $collector->getPaginatedGroups(); + $groups->setPath($path); + + return view('transactions.index', compact('subTitle', 'objectType', 'subTitleIcon', 'groups', 'periods', 'start', 'end')); + } + + /** + * Index for ALL transactions. + * + * @param Request $request + * @param string $objectType + * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View + * @throws \Exception + */ + public function indexAll(Request $request, string $objectType) + { + /** @var JournalRepositoryInterface $repository */ + $repository = app(JournalRepositoryInterface::class); + + + $subTitleIcon = config('firefly.transactionIconsByWhat.' . $objectType); + $types = config('firefly.transactionTypesByWhat.' . $objectType); + $page = (int)$request->get('page'); + $pageSize = (int)app('preferences')->get('listPageSize', 50)->data; + $path = route('transactions.index.all', [$objectType]); + $first = $repository->firstNull(); + $start = null === $first ? new Carbon : $first->date; + $end = new Carbon; + $subTitle = (string)trans('firefly.all_' . $objectType); + + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + + $collector->setRange($start, $end) + ->setTypes($types) + ->setLimit($pageSize) + ->setPage($page) + ->withAccountInformation() + ->withBudgetInformation() + ->withCategoryInformation(); + $groups = $collector->getPaginatedGroups(); + $groups->setPath($path); + + return view('transactions.index', compact('subTitle', 'objectType', 'subTitleIcon', 'groups', 'start', 'end')); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Transaction/LinkController.php b/app/Http/Controllers/Transaction/LinkController.php index ab938ff166..c30a196af8 100644 --- a/app/Http/Controllers/Transaction/LinkController.php +++ b/app/Http/Controllers/Transaction/LinkController.php @@ -28,6 +28,8 @@ use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionJournalLink; use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use FireflyIII\Repositories\LinkType\LinkTypeRepositoryInterface; +use Illuminate\Contracts\View\Factory; +use Illuminate\View\View; use Log; use URL; @@ -43,6 +45,7 @@ class LinkController extends Controller /** * LinkController constructor. + * @codeCoverageIgnore */ public function __construct() { @@ -61,12 +64,23 @@ class LinkController extends Controller ); } + /** + * @param TransactionJournal $journal + * @return Factory|View + */ + public function modal(TransactionJournal $journal) + { + $linkTypes = $this->repository->get(); + + return view('transactions.links.modal', compact('journal', 'linkTypes')); + } + /** * Delete a link. * * @param TransactionJournalLink $link * - * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View + * @return Factory|View */ public function delete(TransactionJournalLink $link) { @@ -104,9 +118,9 @@ class LinkController extends Controller */ public function store(JournalLinkRequest $request, TransactionJournal $journal) { + $linkInfo = $request->getLinkInfo(); Log::debug('We are here (store)'); - $linkInfo = $request->getLinkInfo(); $other = $this->journalRepository->findNull($linkInfo['transaction_journal_id']); if (null === $other) { session()->flash('error', (string)trans('firefly.invalid_link_selection')); @@ -131,7 +145,7 @@ class LinkController extends Controller $this->repository->storeLink($linkInfo, $other, $journal); session()->flash('success', (string)trans('firefly.journals_linked')); - return redirect(route('transactions.show', [$journal->id])); + return redirect(route('transactions.show', [$journal->transaction_group_id])); } /** diff --git a/app/Http/Controllers/Transaction/MassController.php b/app/Http/Controllers/Transaction/MassController.php index 56ac2f8173..219fd48c1f 100644 --- a/app/Http/Controllers/Transaction/MassController.php +++ b/app/Http/Controllers/Transaction/MassController.php @@ -23,24 +23,21 @@ declare(strict_types=1); namespace FireflyIII\Http\Controllers\Transaction; use Carbon\Carbon; -use FireflyIII\Events\UpdatedTransactionJournal; -use FireflyIII\Helpers\Collector\TransactionCollectorInterface; -use FireflyIII\Helpers\Filter\TransactionViewFilter; -use FireflyIII\Helpers\Filter\TransferFilter; +use FireflyIII\Events\UpdatedTransactionGroup; +use FireflyIII\Exceptions\FireflyException; use FireflyIII\Http\Controllers\Controller; use FireflyIII\Http\Requests\MassDeleteJournalRequest; use FireflyIII\Http\Requests\MassEditJournalRequest; use FireflyIII\Models\AccountType; -use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionJournal; +use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Repositories\Journal\JournalRepositoryInterface; -use FireflyIII\Transformers\TransactionTransformer; -use FireflyIII\User; -use Illuminate\Support\Collection; +use FireflyIII\Services\Internal\Update\JournalUpdateService; use Illuminate\View\View as IlluminateView; -use Symfony\Component\HttpFoundation\ParameterBag; +use InvalidArgumentException; +use Log; /** * Class MassController. @@ -54,6 +51,7 @@ class MassController extends Controller /** * MassController constructor. + * @codeCoverageIgnore */ public function __construct() { @@ -64,7 +62,6 @@ class MassController extends Controller app('view')->share('title', (string)trans('firefly.transactions')); app('view')->share('mainTitleIcon', 'fa-repeat'); $this->repository = app(JournalRepositoryInterface::class); - return $next($request); } ); @@ -73,11 +70,11 @@ class MassController extends Controller /** * Mass delete transactions. * - * @param Collection $journals + * @param array $journals * * @return IlluminateView */ - public function delete(Collection $journals): IlluminateView + public function delete(array $journals): IlluminateView { $subTitle = (string)trans('firefly.mass_delete_journals'); @@ -99,13 +96,14 @@ class MassController extends Controller { $ids = $request->get('confirm_mass_delete'); $count = 0; - if (\is_array($ids)) { + if (is_array($ids)) { /** @var string $journalId */ foreach ($ids as $journalId) { + /** @var TransactionJournal $journal */ $journal = $this->repository->findNull((int)$journalId); if (null !== $journal && (int)$journalId === $journal->id) { - $this->repository->destroy($journal); + $this->repository->destroyJournal($journal); ++$count; } } @@ -122,132 +120,69 @@ class MassController extends Controller /** * Mass edit of journals. * - * @param Collection $journals + * @param array $journals * * @return IlluminateView */ - public function edit(Collection $journals): IlluminateView + public function edit(array $journals): IlluminateView { - /** @var User $user */ - $user = auth()->user(); $subTitle = (string)trans('firefly.mass_edit_journals'); /** @var AccountRepositoryInterface $repository */ $repository = app(AccountRepositoryInterface::class); - $accounts = $repository->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]); + + // valid withdrawal sources: + $array = array_keys(config(sprintf('firefly.source_dests.%s', TransactionType::WITHDRAWAL))); + $withdrawalSources = $repository->getAccountsByType($array); + + // valid deposit destinations: + $array = config(sprintf('firefly.source_dests.%s.%s', TransactionType::DEPOSIT, AccountType::REVENUE)); + $depositDestinations = $repository->getAccountsByType($array); /** @var BudgetRepositoryInterface $budgetRepository */ $budgetRepository = app(BudgetRepositoryInterface::class); $budgets = $budgetRepository->getBudgets(); + // reverse amounts + foreach ($journals as $index => $journal) { + $journals[$index]['amount'] = app('steam')->positive($journal['amount']); + $journals[$index]['foreign_amount'] = null === $journal['foreign_amount'] ? + null : app('steam')->positive($journal['foreign_amount']); + } + $this->rememberPreviousUri('transactions.mass-edit.uri'); - /** @var TransactionTransformer $transformer */ - $transformer = app(TransactionTransformer::class); - $transformer->setParameters(new ParameterBag); - - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setUser($user); - $collector->withOpposingAccount()->withCategoryInformation()->withBudgetInformation(); - $collector->setJournals($journals); - $collector->addFilter(TransactionViewFilter::class); - $collector->addFilter(TransferFilter::class); - - - $collection = $collector->getTransactions(); - $transactions = $collection->map( - function (Transaction $transaction) use ($transformer) { - $transformed = $transformer->transform($transaction); - // make sure amount is positive: - $transformed['amount'] = app('steam')->positive((string)$transformed['amount']); - $transformed['foreign_amount'] = app('steam')->positive((string)$transformed['foreign_amount']); - - return $transformed; - } - ); - - return view('transactions.mass.edit', compact('transactions', 'subTitle', 'accounts', 'budgets')); + return view('transactions.mass.edit', compact('journals', 'subTitle', 'withdrawalSources', 'depositDestinations', 'budgets')); } /** * Mass update of journals. * - * @param MassEditJournalRequest $request - * @param JournalRepositoryInterface $repository - * - * @return mixed - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @param MassEditJournalRequest $request + * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector + * @throws FireflyException */ - public function update(MassEditJournalRequest $request, JournalRepositoryInterface $repository) + public function update(MassEditJournalRequest $request) { $journalIds = $request->get('journals'); - $count = 0; - if (\is_array($journalIds)) { - foreach ($journalIds as $journalId) { - $journal = $repository->findNull((int)$journalId); - if (null !== $journal) { - // get optional fields: - $what = strtolower($this->repository->getTransactionType($journal)); - $sourceAccountId = $request->get('source_id')[$journal->id] ?? null; - $currencyId = $request->get('transaction_currency_id')[$journal->id] ?? 1; - $sourceAccountName = $request->get('source_name')[$journal->id] ?? null; - $destAccountId = $request->get('destination_id')[$journal->id] ?? null; - $destAccountName = $request->get('destination_name')[$journal->id] ?? null; - $budgetId = (int)($request->get('budget_id')[$journal->id] ?? 0.0); - $category = $request->get('category')[$journal->id]; - $tags = $journal->tags->pluck('tag')->toArray(); - $amount = round($request->get('amount')[$journal->id], 12); - $foreignAmount = isset($request->get('foreign_amount')[$journal->id]) ? round($request->get('foreign_amount')[$journal->id], 12) : null; - $foreignCurrencyId = isset($request->get('foreign_currency_id')[$journal->id]) ? - (int)$request->get('foreign_currency_id')[$journal->id] : null; - // build data array - $data = [ - 'id' => $journal->id, - 'what' => $what, - 'description' => $request->get('description')[$journal->id], - 'date' => new Carbon($request->get('date')[$journal->id]), - 'bill_id' => null, - 'bill_name' => null, - 'notes' => $repository->getNoteText($journal), - 'transactions' => [[ - - 'category_id' => null, - 'category_name' => $category, - 'budget_id' => $budgetId, - 'budget_name' => null, - 'source_id' => (int)$sourceAccountId, - 'source_name' => $sourceAccountName, - 'destination_id' => (int)$destAccountId, - 'destination_name' => $destAccountName, - 'amount' => $amount, - 'identifier' => 0, - 'reconciled' => false, - 'currency_id' => (int)$currencyId, - 'currency_code' => null, - 'description' => null, - 'foreign_amount' => $foreignAmount, - 'foreign_currency_id' => $foreignCurrencyId, - 'foreign_currency_code' => null, - ]], - 'currency_id' => $foreignCurrencyId, - 'tags' => $tags, - 'interest_date' => $journal->interest_date, - 'book_date' => $journal->book_date, - 'process_date' => $journal->process_date, - - ]; - // call repository update function. - $repository->update($journal, $data); - - // trigger rules - event(new UpdatedTransactionJournal($journal)); - - ++$count; - } + if (!is_array($journalIds)) { + // TODO something error. + throw new FireflyException('This is not an array.'); // @codeCoverageIgnore + } + $count = 0; + /** @var string $journalId */ + foreach ($journalIds as $journalId) { + $integer = (int)$journalId; + try { + $this->updateJournal($integer, $request); + $count++; + } catch (FireflyException $e) { // @codeCoverageIgnore + // do something with error. + //echo $e->getMessage(); + //exit; } } + app('preferences')->mark(); session()->flash('success', (string)trans('firefly.mass_edited_transactions_success', ['amount' => $count])); @@ -255,4 +190,106 @@ class MassController extends Controller return redirect($this->getPreviousUri('transactions.mass-edit.uri')); } + /** + * @param int $journalId + * @param MassEditJournalRequest $request + * @throws FireflyException + */ + private function updateJournal(int $journalId, MassEditJournalRequest $request): void + { + $journal = $this->repository->findNull($journalId); + if (null === $journal) { + throw new FireflyException(sprintf('Trying to edit non-existent or deleted journal #%d', $journalId)); // @codeCoverageIgnore + } + $service = app(JournalUpdateService::class); + // for each field, call the update service. + $service->setTransactionJournal($journal); + + $data = [ + 'date' => $this->getDateFromRequest($request, $journal->id, 'date'), + 'description' => $this->getStringFromRequest($request, $journal->id, 'description'), + 'source_id' => $this->getIntFromRequest($request, $journal->id, 'source_id'), + 'source_name' => $this->getStringFromRequest($request, $journal->id, 'source_name'), + 'destination_id' => $this->getIntFromRequest($request, $journal->id, 'destination_id'), + 'destination_name' => $this->getStringFromRequest($request, $journal->id, 'destination_name'), + 'budget_id' => $this->getIntFromRequest($request, $journal->id, 'budget_id'), + 'category_name' => $this->getStringFromRequest($request, $journal->id, 'category'), + 'amount' => $this->getStringFromRequest($request, $journal->id, 'amount'), + 'foreign_amount' => $this->getStringFromRequest($request, $journal->id, 'foreign_amount'), + ]; + Log::debug(sprintf('Will update journal #%d with data.', $journal->id), $data); + + // call service to update. + $service->setData($data); + $service->update(); + // trigger rules + event(new UpdatedTransactionGroup($journal->transactionGroup)); + } + + /** + * @param MassEditJournalRequest $request + * @param int $journalId + * @param string $string + * @return int|null + * @codeCoverageIgnore + */ + private function getIntFromRequest(MassEditJournalRequest $request, int $journalId, string $string): ?int + { + $value = $request->get($string); + if (!is_array($value)) { + return null; + } + if (!isset($value[$journalId])) { + return null; + } + + return (int)$value[$journalId]; + } + + /** + * @param MassEditJournalRequest $request + * @param int $journalId + * @param string $string + * @return string|null + * @codeCoverageIgnore + */ + private function getStringFromRequest(MassEditJournalRequest $request, int $journalId, string $string): ?string + { + $value = $request->get($string); + if (!is_array($value)) { + return null; + } + if (!isset($value[$journalId])) { + return null; + } + + return (string)$value[$journalId]; + } + + /** + * @param MassEditJournalRequest $request + * @param int $journalId + * @param string $string + * @return Carbon|null + * @codeCoverageIgnore + */ + private function getDateFromRequest(MassEditJournalRequest $request, int $journalId, string $string): ?Carbon + { + $value = $request->get($string); + if (!is_array($value)) { + return null; + } + if (!isset($value[$journalId])) { + return null; + } + try { + $carbon = Carbon::parse($value[$journalId]); + } catch (InvalidArgumentException $e) { + $e->getMessage(); + + return null; + } + + return $carbon; + } } diff --git a/app/Http/Controllers/Transaction/ShowController.php b/app/Http/Controllers/Transaction/ShowController.php new file mode 100644 index 0000000000..80b9240914 --- /dev/null +++ b/app/Http/Controllers/Transaction/ShowController.php @@ -0,0 +1,131 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Http\Controllers\Transaction; + + +use FireflyIII\Http\Controllers\Controller; +use FireflyIII\Models\TransactionGroup; +use FireflyIII\Models\TransactionJournal; +use FireflyIII\Repositories\TransactionGroup\TransactionGroupRepositoryInterface; +use FireflyIII\Transformers\TransactionGroupTransformer; +use Illuminate\Http\Request; +use Symfony\Component\HttpFoundation\ParameterBag; + +/** + * Class ShowController + */ +class ShowController extends Controller +{ + /** @var TransactionGroupRepositoryInterface */ + private $repository; + + /** + * ShowController constructor. + */ + public function __construct() + { + parent::__construct(); + + // some useful repositories: + $this->middleware( + function ($request, $next) { + $this->repository = app(TransactionGroupRepositoryInterface::class); + + app('view')->share('title', (string)trans('firefly.transactions')); + app('view')->share('mainTitleIcon', 'fa-exchange'); + + return $next($request); + } + ); + } + + /** + * @param TransactionGroup $transactionGroup + * + * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View + */ + public function show(Request $request, TransactionGroup $transactionGroup) + { + /** @var TransactionJournal $first */ + $first = $transactionGroup->transactionJournals->first(); + $splits = $transactionGroup->transactionJournals->count(); + $type = $first->transactionType->type; + $title = 1 === $splits ? $first->description : $transactionGroup->title; + $subTitle = sprintf('%s: "%s"', $type, $title); + + /** @var TransactionGroupTransformer $transformer */ + $transformer = app(TransactionGroupTransformer::class); + $transformer->setParameters(new ParameterBag); + $groupArray = $transformer->transformObject($transactionGroup); + + // do some amount calculations: + $amounts = $this->getAmounts($groupArray); + + + $events = $this->repository->getPiggyEvents($transactionGroup); + $attachments = $this->repository->getAttachments($transactionGroup); + $links = $this->repository->getLinks($transactionGroup); + + return view( + 'transactions.show', compact( + 'transactionGroup', 'amounts', 'first', 'type', 'subTitle', 'splits', 'groupArray', + 'events', 'attachments', 'links' + ) + ); + } + + /** + * @param array $group + * @return array + */ + private function getAmounts(array $group): array + { + $amounts = []; + foreach ($group['transactions'] as $transaction) { + $symbol = $transaction['currency_symbol']; + if (!isset($amounts[$symbol])) { + $amounts[$symbol] = [ + 'amount' => '0', + 'symbol' => $symbol, + 'decimal_places' => $transaction['currency_decimal_places'], + ]; + } + $amounts[$symbol]['amount'] = bcadd($amounts[$symbol]['amount'], $transaction['amount']); + if (null !== $transaction['foreign_amount']) { + // same for foreign currency: + $foreignSymbol = $transaction['foreign_currency_symbol']; + if (!isset($amounts[$foreignSymbol])) { + $amounts[$foreignSymbol] = [ + 'amount' => '0', + 'symbol' => $foreignSymbol, + 'decimal_places' => $transaction['foreign_currency_decimal_places'], + ]; + } + $amounts[$foreignSymbol]['amount'] = bcadd($amounts[$foreignSymbol]['amount'], $transaction['foreign_amount']); + } + } + + return $amounts; + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Transaction/SingleController.php b/app/Http/Controllers/Transaction/SingleController.php deleted file mode 100644 index 50a44d616b..0000000000 --- a/app/Http/Controllers/Transaction/SingleController.php +++ /dev/null @@ -1,491 +0,0 @@ -. - */ -declare(strict_types=1); - -namespace FireflyIII\Http\Controllers\Transaction; - -use Carbon\Carbon; -use FireflyIII\Events\StoredTransactionJournal; -use FireflyIII\Events\UpdatedTransactionJournal; -use FireflyIII\Helpers\Attachments\AttachmentHelperInterface; -use FireflyIII\Http\Controllers\Controller; -use FireflyIII\Http\Requests\JournalFormRequest; -use FireflyIII\Models\Transaction; -use FireflyIII\Models\TransactionJournal; -use FireflyIII\Models\TransactionJournalMeta; -use FireflyIII\Models\TransactionType; -use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; -use FireflyIII\Repositories\Journal\JournalRepositoryInterface; -use FireflyIII\Support\Http\Controllers\ModelInformation; -use Illuminate\Http\JsonResponse; -use Illuminate\Http\RedirectResponse; -use Illuminate\Http\Request; -use Log; -use View; - -/** - * Class SingleController. - * - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) - */ -class SingleController extends Controller -{ - use ModelInformation; - - /** @var AttachmentHelperInterface The attachment helper. */ - private $attachments; - /** @var BudgetRepositoryInterface The budget repository */ - private $budgets; - /** @var JournalRepositoryInterface Journals and transactions overview */ - private $repository; - - /** - * SingleController constructor. - */ - public function __construct() - { - parent::__construct(); - - $maxFileSize = app('steam')->phpBytes(ini_get('upload_max_filesize')); - $maxPostSize = app('steam')->phpBytes(ini_get('post_max_size')); - $uploadSize = min($maxFileSize, $maxPostSize); - app('view')->share('uploadSize', $uploadSize); - - // some useful repositories: - $this->middleware( - function ($request, $next) { - $this->budgets = app(BudgetRepositoryInterface::class); - $this->attachments = app(AttachmentHelperInterface::class); - $this->repository = app(JournalRepositoryInterface::class); - - app('view')->share('title', (string)trans('firefly.transactions')); - app('view')->share('mainTitleIcon', 'fa-repeat'); - - return $next($request); - } - ); - } - - /** - * CLone a transaction. - * - * @param TransactionJournal $journal - * - * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector - * - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - */ - public function cloneTransaction(TransactionJournal $journal) - { - $source = $this->repository->getJournalSourceAccounts($journal)->first(); - $destination = $this->repository->getJournalDestinationAccounts($journal)->first(); - $budgetId = $this->repository->getJournalBudgetId($journal); - $categoryName = $this->repository->getJournalCategoryName($journal); - $tags = implode(',', $this->repository->getTags($journal)); - /** @var Transaction $transaction */ - $transaction = $journal->transactions()->first(); - $amount = app('steam')->positive($transaction->amount); - $foreignAmount = null === $transaction->foreign_amount ? null : app('steam')->positive($transaction->foreign_amount); - - // make sure previous URI is correct: - session()->put('transactions.create.fromStore', true); - session()->put('transactions.create.uri', app('url')->previous()); - - $preFilled = [ - 'description' => $journal->description, - 'source_id' => $source->id, - 'source_name' => $source->name, - 'destination_id' => $destination->id, - 'destination_name' => $destination->name, - 'amount' => $amount, - 'source_amount' => $amount, - 'destination_amount' => $foreignAmount, - 'foreign_amount' => $foreignAmount, - 'native_amount' => $foreignAmount, - 'amount_currency_id_amount' => $transaction->foreign_currency_id ?? 0, - 'date' => (new Carbon())->format('Y-m-d'), - 'budget_id' => $budgetId, - 'category' => $categoryName, - 'tags' => $tags, - 'interest_date' => $this->repository->getMetaField($journal, 'interest_date'), - 'book_date' => $this->repository->getMetaField($journal, 'book_date'), - 'process_date' => $this->repository->getMetaField($journal, 'process_date'), - 'due_date' => $this->repository->getMetaField($journal, 'due_date'), - 'payment_date' => $this->repository->getMetaField($journal, 'payment_date'), - 'invoice_date' => $this->repository->getMetaField($journal, 'invoice_date'), - 'internal_reference' => $this->repository->getMetaField($journal, 'internal_reference'), - 'notes' => $this->repository->getNoteText($journal), - ]; - - session()->flash('preFilled', $preFilled); - - return redirect(route('transactions.create', [strtolower($journal->transactionType->type)])); - } - - /** - * Create a new journal. - * - * @param Request $request - * @param string|null $what - * - * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View - * - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - */ - public function create(Request $request, string $what = null) - { - $what = strtolower($what ?? TransactionType::DEPOSIT); - $what = (string)($request->old('what') ?? $what); - $budgets = app('expandedform')->makeSelectListWithEmpty($this->budgets->getActiveBudgets()); - $preFilled = session()->has('preFilled') ? session('preFilled') : []; - $subTitle = (string)trans('form.add_new_' . $what); - $subTitleIcon = 'fa-plus'; - $optionalFields = app('preferences')->get('transaction_journal_optional_fields', [])->data; - $source = (int)$request->get('source'); - - // grab old currency ID from old data: - $currencyID = (int)$request->old('amount_currency_id_amount'); - $preFilled['amount_currency_id_amount'] = $currencyID; - - if (('withdrawal' === $what || 'transfer' === $what) && $source > 0) { - $preFilled['source_id'] = $source; - } - if ('deposit' === $what && $source > 0) { - $preFilled['destination_id'] = $source; - } - - session()->put('preFilled', $preFilled); - - // put previous url in session if not redirect from store (not "create another"). - if (true !== session('transactions.create.fromStore')) { - $this->rememberPreviousUri('transactions.create.uri'); - } - session()->forget('transactions.create.fromStore'); - - return view( - 'transactions.single.create', - compact('subTitleIcon', 'budgets', 'what', 'subTitle', 'optionalFields', 'preFilled') - ); - } - - /** - * Show a special JSONified view of a transaction, for easier debug purposes. - * - * @param TransactionJournal $journal - * - * @codeCoverageIgnore - * @return JsonResponse - */ - public function debugShow(TransactionJournal $journal): JsonResponse - { - $array = $journal->toArray(); - $array['transactions'] = []; - $array['meta'] = []; - - /** @var Transaction $transaction */ - foreach ($journal->transactions as $transaction) { - $array['transactions'][] = $transaction->toArray(); - } - /** @var TransactionJournalMeta $meta */ - foreach ($journal->transactionJournalMeta as $meta) { - $array['meta'][] = $meta->toArray(); - } - - return response()->json($array); - } - - /** - * Shows the form that allows a user to delete a transaction journal. - * - * @param TransactionJournal $journal - * - * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View - */ - public function delete(TransactionJournal $journal) - { - Log::debug(sprintf('Start of delete view for journal #%d', $journal->id)); - // Covered by another controller's tests - // @codeCoverageIgnoreStart - if ($this->isOpeningBalance($journal)) { - return $this->redirectToAccount($journal); - } - // @codeCoverageIgnoreEnd - - $what = strtolower($journal->transaction_type_type ?? $journal->transactionType->type); - $subTitle = (string)trans('firefly.delete_' . $what, ['description' => $journal->description]); - - // put previous url in session - Log::debug('Will try to remember previous URI'); - $this->rememberPreviousUri('transactions.delete.uri'); - - return view('transactions.single.delete', compact('journal', 'subTitle', 'what')); - } - - /** - * Actually destroys the journal. - * - * @param TransactionJournal $transactionJournal - * - * @return \Illuminate\Http\RedirectResponse - */ - public function destroy(TransactionJournal $transactionJournal): RedirectResponse - { - // @codeCoverageIgnoreStart - if ($this->isOpeningBalance($transactionJournal)) { - return $this->redirectToAccount($transactionJournal); - } - // @codeCoverageIgnoreEnd - $type = $this->repository->getTransactionType($transactionJournal); - session()->flash('success', (string)trans('firefly.deleted_' . strtolower($type), ['description' => $transactionJournal->description])); - - $this->repository->destroy($transactionJournal); - - app('preferences')->mark(); - - return redirect($this->getPreviousUri('transactions.delete.uri')); - } - - /** - * Edit a journal. - * - * @param TransactionJournal $journal - * - * @param JournalRepositoryInterface $repository - * - * @return mixed - * - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - */ - public function edit(TransactionJournal $journal, JournalRepositoryInterface $repository) - { - $transactionType = $repository->getTransactionType($journal); - - // redirect to account: - if ($transactionType === TransactionType::OPENING_BALANCE) { - return $this->redirectToAccount($journal); - } - // redirect to reconcile edit: - if ($transactionType === TransactionType::RECONCILIATION) { - return redirect(route('accounts.reconcile.edit', [$journal->id])); - } - - // redirect to split edit: - if ($this->isSplitJournal($journal)) { - return redirect(route('transactions.split.edit', [$journal->id])); - } - - $what = strtolower($transactionType); - $budgetList = app('expandedform')->makeSelectListWithEmpty($this->budgets->getBudgets()); - - // view related code - $subTitle = (string)trans('breadcrumbs.edit_journal', ['description' => $journal->description]); - - // journal related code - $sourceAccounts = $repository->getJournalSourceAccounts($journal); - $destinationAccounts = $repository->getJournalDestinationAccounts($journal); - $optionalFields = app('preferences')->get('transaction_journal_optional_fields', [])->data; - $pTransaction = $repository->getFirstPosTransaction($journal); - $foreignCurrency = $pTransaction->foreignCurrency ?? $pTransaction->transactionCurrency; - $preFilled = [ - 'date' => $repository->getJournalDate($journal, null), // $journal->dateAsString() - 'interest_date' => $repository->getJournalDate($journal, 'interest_date'), - 'book_date' => $repository->getJournalDate($journal, 'book_date'), - 'process_date' => $repository->getJournalDate($journal, 'process_date'), - 'category' => $repository->getJournalCategoryName($journal), - 'budget_id' => $repository->getJournalBudgetId($journal), - 'tags' => implode(',', $repository->getTags($journal)), - 'source_id' => $sourceAccounts->first()->id, - 'source_name' => $sourceAccounts->first()->edit_name, - 'destination_id' => $destinationAccounts->first()->id, - 'destination_name' => $destinationAccounts->first()->edit_name, - 'bill_id' => $journal->bill_id, - 'bill_name' => null === $journal->bill_id ? null : $journal->bill->name, - - // new custom fields: - 'due_date' => $repository->getJournalDate($journal, 'due_date'), - 'payment_date' => $repository->getJournalDate($journal, 'payment_date'), - 'invoice_date' => $repository->getJournalDate($journal, 'invoice_date'), - 'interal_reference' => $repository->getMetaField($journal, 'internal_reference'), - 'notes' => $repository->getNoteText($journal), - - // amount fields - 'amount' => $pTransaction->amount, - 'source_amount' => $pTransaction->amount, - 'native_amount' => $pTransaction->amount, - 'destination_amount' => $pTransaction->foreign_amount, - 'currency' => $pTransaction->transactionCurrency, - 'source_currency' => $pTransaction->transactionCurrency, - 'native_currency' => $pTransaction->transactionCurrency, - 'foreign_currency' => $foreignCurrency, - 'destination_currency' => $foreignCurrency, - ]; - - // amounts for withdrawals and deposits: - // amount, native_amount, source_amount, destination_amount - if (null !== $pTransaction->foreign_amount && ($journal->isWithdrawal() || $journal->isDeposit())) { - $preFilled['amount'] = $pTransaction->foreign_amount; - $preFilled['currency'] = $pTransaction->foreignCurrency; - } - - session()->flash('preFilled', $preFilled); - - // put previous url in session if not redirect from store (not "return_to_edit"). - if (true !== session('transactions.edit.fromUpdate')) { - $this->rememberPreviousUri('transactions.edit.uri'); - } - session()->forget('transactions.edit.fromUpdate'); - - return view( - 'transactions.single.edit', - compact('journal', 'optionalFields', 'what', 'budgetList', 'subTitle') - )->with('data', $preFilled); - } - - /** - * Stores a new journal. - * - * @param JournalFormRequest $request - * @param JournalRepositoryInterface $repository - * - * @return RedirectResponse - * @throws \FireflyIII\Exceptions\FireflyException - * - * - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - */ - public function store(JournalFormRequest $request, JournalRepositoryInterface $repository): RedirectResponse - { - $doSplit = 1 === (int)$request->get('split_journal'); - $createAnother = 1 === (int)$request->get('create_another'); - $data = $request->getJournalData(); - $journal = $repository->store($data); - - - if (null === $journal->id) { - // error! - Log::error('Could not store transaction journal.'); - session()->flash('error', (string)trans('firefly.unknown_journal_error')); - - return redirect(route('transactions.create', [$request->input('what')]))->withInput(); - } - - /** @var array $files */ - $files = $request->hasFile('attachments') ? $request->file('attachments') : null; - $this->attachments->saveAttachmentsForModel($journal, $files); - - // store the journal only, flash the rest. - Log::debug(sprintf('Count of error messages is %d', $this->attachments->getErrors()->count())); - if (\count($this->attachments->getErrors()->get('attachments')) > 0) { - session()->flash('error', e($this->attachments->getErrors()->get('attachments'))); - } - // flash messages - if (\count($this->attachments->getMessages()->get('attachments')) > 0) { - session()->flash('info', $this->attachments->getMessages()->get('attachments')); - } - - event(new StoredTransactionJournal($journal)); - - session()->flash('success_uri', route('transactions.show', [$journal->id])); - session()->flash('success', (string)trans('firefly.stored_journal', ['description' => $journal->description])); - app('preferences')->mark(); - - // @codeCoverageIgnoreStart - if (true === $createAnother) { - session()->put('transactions.create.fromStore', true); - - return redirect(route('transactions.create', [$request->input('what')]))->withInput(); - } - - if (true === $doSplit) { - return redirect(route('transactions.split.edit', [$journal->id])); - } - - // @codeCoverageIgnoreEnd - - return redirect($this->getPreviousUri('transactions.create.uri')); - } - - /** - * Update a journal. - * - * @param JournalFormRequest $request - * @param JournalRepositoryInterface $repository - * @param TransactionJournal $journal - * - * @return $this|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector - * - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - */ - public function update(JournalFormRequest $request, JournalRepositoryInterface $repository, TransactionJournal $journal) - { - // @codeCoverageIgnoreStart - if ($this->isOpeningBalance($journal)) { - return $this->redirectToAccount($journal); - } - // @codeCoverageIgnoreEnd - - $data = $request->getJournalData(); - - // keep current bill: - $data['bill_id'] = $journal->bill_id; - - // remove it if no checkbox: - if (!$request->boolean('keep_bill_id')) { - $data['bill_id'] = null; - } - - - $journal = $repository->update($journal, $data); - /** @var array $files */ - $files = $request->hasFile('attachments') ? $request->file('attachments') : null; - $this->attachments->saveAttachmentsForModel($journal, $files); - - // @codeCoverageIgnoreStart - if (\count($this->attachments->getErrors()->get('attachments')) > 0) { - session()->flash('error', e($this->attachments->getErrors()->get('attachments'))); - } - if (\count($this->attachments->getMessages()->get('attachments')) > 0) { - session()->flash('info', $this->attachments->getMessages()->get('attachments')); - } - // @codeCoverageIgnoreEnd - - event(new UpdatedTransactionJournal($journal)); - // update, get events by date and sort DESC - - $type = strtolower($this->repository->getTransactionType($journal)); - session()->flash('success', (string)trans('firefly.updated_' . $type, ['description' => $data['description']])); - app('preferences')->mark(); - - // @codeCoverageIgnoreStart - if (1 === (int)$request->get('return_to_edit')) { - session()->put('transactions.edit.fromUpdate', true); - - return redirect(route('transactions.edit', [$journal->id]))->withInput(['return_to_edit' => 1]); - } - // @codeCoverageIgnoreEnd - - // redirect to previous URL. - return redirect($this->getPreviousUri('transactions.edit.uri')); - } -} diff --git a/app/Http/Controllers/Transaction/SplitController.php b/app/Http/Controllers/Transaction/SplitController.php deleted file mode 100644 index 4ba15b5503..0000000000 --- a/app/Http/Controllers/Transaction/SplitController.php +++ /dev/null @@ -1,174 +0,0 @@ -. - */ -declare(strict_types=1); - -namespace FireflyIII\Http\Controllers\Transaction; - -use FireflyIII\Events\UpdatedTransactionJournal; -use FireflyIII\Exceptions\FireflyException; -use FireflyIII\Helpers\Attachments\AttachmentHelperInterface; -use FireflyIII\Http\Controllers\Controller; -use FireflyIII\Http\Requests\SplitJournalFormRequest; -use FireflyIII\Models\TransactionJournal; -use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; -use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; -use FireflyIII\Repositories\Journal\JournalRepositoryInterface; -use FireflyIII\Support\Http\Controllers\ModelInformation; -use FireflyIII\Support\Http\Controllers\RequestInformation; -use Illuminate\Http\Request; -use View; - -/** - * Class SplitController. - * - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) - */ -class SplitController extends Controller -{ - use ModelInformation, RequestInformation; - - /** @var AttachmentHelperInterface Attachment helper */ - private $attachments; - - /** @var BudgetRepositoryInterface The budget repository */ - private $budgets; - - /** @var CurrencyRepositoryInterface The currency repository */ - private $currencies; - /** @var JournalRepositoryInterface Journals and transactions overview */ - private $repository; - - /** - * SplitController constructor. - */ - public function __construct() - { - parent::__construct(); - - // some useful repositories: - $this->middleware( - function ($request, $next) { - $this->budgets = app(BudgetRepositoryInterface::class); - $this->attachments = app(AttachmentHelperInterface::class); - $this->currencies = app(CurrencyRepositoryInterface::class); - $this->repository = app(JournalRepositoryInterface::class); - app('view')->share('mainTitleIcon', 'fa-share-alt'); - app('view')->share('title', (string)trans('firefly.split-transactions')); - - return $next($request); - } - ); - } - - /** - * Edit a split. - * - * @param Request $request - * @param TransactionJournal $journal - * - * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View - * @throws FireflyException - */ - public function edit(Request $request, TransactionJournal $journal) - { - if ($this->isOpeningBalance($journal)) { - return $this->redirectToAccount($journal); // @codeCoverageIgnore - } - // basic fields: - $uploadSize = min(app('steam')->phpBytes(ini_get('upload_max_filesize')), app('steam')->phpBytes(ini_get('post_max_size'))); - $subTitle = (string)trans('breadcrumbs.edit_journal', ['description' => $journal->description]); - $subTitleIcon = 'fa-pencil'; - - // lists and collections - $currencies = $this->currencies->get(); - $budgets = app('expandedform')->makeSelectListWithEmpty($this->budgets->getActiveBudgets()); - - // other fields - $optionalFields = app('preferences')->get('transaction_journal_optional_fields', [])->data; - $preFilled = $this->arrayFromJournal($request, $journal); - - // put previous url in session if not redirect from store (not "return_to_edit"). - if (true !== session('transactions.edit-split.fromUpdate')) { - $this->rememberPreviousUri('transactions.edit-split.uri'); - } - session()->forget('transactions.edit-split.fromUpdate'); - - return view( - 'transactions.split.edit', compact( - 'subTitleIcon', 'currencies', 'optionalFields', 'preFilled', 'subTitle', 'uploadSize', 'budgets', - 'journal' - ) - ); - } - - /** - * Store new split journal. - * - * @param SplitJournalFormRequest $request - * @param TransactionJournal $journal - * - * @return $this|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector - * - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - */ - public function update(SplitJournalFormRequest $request, TransactionJournal $journal) - { - if ($this->isOpeningBalance($journal)) { - return $this->redirectToAccount($journal); // @codeCoverageIgnore - } - $data = $request->getAll(); - - // keep current bill: - $data['bill_id'] = $journal->bill_id; - $journal = $this->repository->update($journal, $data); - - /** @var array $files */ - $files = $request->hasFile('attachments') ? $request->file('attachments') : null; - // save attachments: - $this->attachments->saveAttachmentsForModel($journal, $files); - event(new UpdatedTransactionJournal($journal)); - - // flash messages - // @codeCoverageIgnoreStart - if (\count($this->attachments->getMessages()->get('attachments')) > 0) { - session()->flash('info', $this->attachments->getMessages()->get('attachments')); - } - // @codeCoverageIgnoreEnd - - $type = strtolower($this->repository->getTransactionType($journal)); - session()->flash('success', (string)trans('firefly.updated_' . $type, ['description' => $journal->description])); - app('preferences')->mark(); - - // @codeCoverageIgnoreStart - if (1 === (int)$request->get('return_to_edit')) { - // set value so edit routine will not overwrite URL: - session()->put('transactions.edit-split.fromUpdate', true); - - return redirect(route('transactions.split.edit', [$journal->id]))->withInput(['return_to_edit' => 1]); - } - // @codeCoverageIgnoreEnd - - // redirect to previous URL. - return redirect($this->getPreviousUri('transactions.edit-split.uri')); - } - -} diff --git a/app/Http/Controllers/TransactionController.php b/app/Http/Controllers/TransactionController.php deleted file mode 100644 index 1da5411663..0000000000 --- a/app/Http/Controllers/TransactionController.php +++ /dev/null @@ -1,272 +0,0 @@ -. - */ -/** @noinspection CallableParameterUseCaseInTypeContextInspection */ -/** @noinspection MoreThanThreeArgumentsInspection */ -declare(strict_types=1); - -namespace FireflyIII\Http\Controllers; - -use Carbon\Carbon; -use FireflyIII\Exceptions\FireflyException; -use FireflyIII\Helpers\Collector\TransactionCollectorInterface; -use FireflyIII\Helpers\Filter\CountAttachmentsFilter; -use FireflyIII\Helpers\Filter\InternalTransferFilter; -use FireflyIII\Helpers\Filter\SplitIndicatorFilter; -use FireflyIII\Models\Attachment; -use FireflyIII\Models\Transaction; -use FireflyIII\Models\TransactionJournal; -use FireflyIII\Models\TransactionType; -use FireflyIII\Repositories\Attachment\AttachmentRepositoryInterface; -use FireflyIII\Repositories\Journal\JournalRepositoryInterface; -use FireflyIII\Repositories\LinkType\LinkTypeRepositoryInterface; -use FireflyIII\Support\Http\Controllers\ModelInformation; -use FireflyIII\Support\Http\Controllers\PeriodOverview; -use FireflyIII\Transformers\TransactionTransformer; -use Illuminate\Http\JsonResponse; -use Illuminate\Http\Request; -use Illuminate\Support\Collection; -use Log; -use Symfony\Component\HttpFoundation\ParameterBag; -use View; - -/** - * Class TransactionController. - * - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) - */ -class TransactionController extends Controller -{ - use ModelInformation, PeriodOverview; - /** @var AttachmentRepositoryInterface */ - private $attachmentRepository; - /** @var JournalRepositoryInterface Journals and transactions overview */ - private $repository; - - /** - * TransactionController constructor. - */ - public function __construct() - { - parent::__construct(); - - $this->middleware( - function ($request, $next) { - app('view')->share('title', (string)trans('firefly.transactions')); - app('view')->share('mainTitleIcon', 'fa-repeat'); - $this->repository = app(JournalRepositoryInterface::class); - $this->attachmentRepository = app(AttachmentRepositoryInterface::class); - - return $next($request); - } - ); - } - - /** - * Index for a range of transactions. - * - * @param Request $request - * @param string $what - * @param Carbon|null $start - * @param Carbon|null $end - * - * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View - */ - public function index(Request $request, string $what, Carbon $start = null, Carbon $end = null) - { - $subTitleIcon = config('firefly.transactionIconsByWhat.' . $what); - $types = config('firefly.transactionTypesByWhat.' . $what); - $page = (int)$request->get('page'); - $pageSize = (int)app('preferences')->get('listPageSize', 50)->data; - if (null === $start) { - $start = session('start'); - $end = session('end'); - } - if (null === $end) { - $end = session('end'); - } - - if ($end < $start) { - [$start, $end] = [$end, $start]; - } - - $path = route('transactions.index', [$what, $start->format('Y-m-d'), $end->format('Y-m-d')]); - - $startStr = $start->formatLocalized($this->monthAndDayFormat); - $endStr = $end->formatLocalized($this->monthAndDayFormat); - $subTitle = (string)trans('firefly.title_' . $what . '_between', ['start' => $startStr, 'end' => $endStr]); - $periods = $this->getTransactionPeriodOverview($what, $end); - - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setAllAssetAccounts()->setRange($start, $end) - ->setTypes($types)->setLimit($pageSize)->setPage($page)->withOpposingAccount() - ->withBudgetInformation()->withCategoryInformation(); - $collector->removeFilter(InternalTransferFilter::class); - $collector->addFilter(SplitIndicatorFilter::class); - $collector->addFilter(CountAttachmentsFilter::class); - $transactions = $collector->getPaginatedTransactions(); - $transactions->setPath($path); - - return view('transactions.index', compact('subTitle', 'what', 'subTitleIcon', 'transactions', 'periods', 'start', 'end')); - } - - /** - * Index for ALL transactions. - * - * @param Request $request - * @param string $what - * - * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View - */ - public function indexAll(Request $request, string $what) - { - $subTitleIcon = config('firefly.transactionIconsByWhat.' . $what); - $types = config('firefly.transactionTypesByWhat.' . $what); - $page = (int)$request->get('page'); - $pageSize = (int)app('preferences')->get('listPageSize', 50)->data; - $path = route('transactions.index.all', [$what]); - $first = $this->repository->firstNull(); - $start = null === $first ? new Carbon : $first->date; - $end = new Carbon; - $subTitle = (string)trans('firefly.all_' . $what); - - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setAllAssetAccounts()->setRange($start, $end) - ->setTypes($types)->setLimit($pageSize)->setPage($page)->withOpposingAccount() - ->withBudgetInformation()->withCategoryInformation(); - $collector->removeFilter(InternalTransferFilter::class); - $collector->addFilter(SplitIndicatorFilter::class); - $collector->addFilter(CountAttachmentsFilter::class); - $transactions = $collector->getPaginatedTransactions(); - $transactions->setPath($path); - - return view('transactions.index', compact('subTitle', 'what', 'subTitleIcon', 'transactions', 'start', 'end')); - } - - /** - * Do a reconciliation. - * - * @param Request $request - * - * @return JsonResponse - */ - public function reconcile(Request $request): JsonResponse - { - $transactionIds = $request->get('transactions'); - foreach ($transactionIds as $transactionId) { - $transactionId = (int)$transactionId; - $transaction = $this->repository->findTransaction($transactionId); - if (null !== $transaction) { - Log::debug(sprintf('Transaction ID is %d', $transaction->id)); - $this->repository->reconcile($transaction); - } - } - - return response()->json(['ok' => 'reconciled']); - } - - /** - * Reorder transactions. - * - * @param Request $request - * - * @return \Illuminate\Http\JsonResponse - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - */ - public function reorder(Request $request): JsonResponse - { - $ids = $request->get('items'); - $date = new Carbon($request->get('date')); - if (\count($ids) > 0) { - $order = 0; - $ids = array_unique($ids); - foreach ($ids as $id) { - $journal = $this->repository->findNull((int)$id); - if (null !== $journal && $journal->date->isSameDay($date)) { - $this->repository->setOrder($journal, $order); - ++$order; - } - } - } - app('preferences')->mark(); - - return response()->json([true]); - } - - /** - * Show a transaction. - * - * @param TransactionJournal $journal - * @param LinkTypeRepositoryInterface $linkTypeRepository - * - * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View - * @throws FireflyException - */ - public function show(TransactionJournal $journal, LinkTypeRepositoryInterface $linkTypeRepository) - { - if ($this->isOpeningBalance($journal)) { - return $this->redirectToAccount($journal); - } - $transactionType = $journal->transactionType->type; - if (TransactionType::RECONCILIATION === $transactionType) { - return redirect(route('accounts.reconcile.show', [$journal->id])); // @codeCoverageIgnore - } - $linkTypes = $linkTypeRepository->get(); - $links = $linkTypeRepository->getLinks($journal); - - // get attachments: - $attachments = $this->repository->getAttachments($journal); - $attachments = $attachments->each( - function (Attachment $attachment) { - $attachment->file_exists = $this->attachmentRepository->exists($attachment); - - return $attachment; - } - ); - - // get transactions using the collector: - $collector = app(TransactionCollectorInterface::class); - $collector->setUser(auth()->user()); - $collector->withOpposingAccount()->withCategoryInformation()->withBudgetInformation(); - // filter on specific journals. - $collector->setJournals(new Collection([$journal])); - $set = $collector->getTransactions(); - $transactions = []; - - /** @var TransactionTransformer $transformer */ - $transformer = app(TransactionTransformer::class); - $transformer->setParameters(new ParameterBag); - - /** @var Transaction $transaction */ - foreach ($set as $transaction) { - $transactions[] = $transformer->transform($transaction); - } - - $events = $this->repository->getPiggyBankEvents($journal); - $what = strtolower($transactionType); - $subTitle = trans('firefly.' . $what) . ' "' . $journal->description . '"'; - - return view('transactions.show', compact('journal', 'attachments', 'events', 'subTitle', 'what', 'transactions', 'linkTypes', 'links')); - } - - -} diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index c352b2fb55..63141f0c25 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -27,6 +27,7 @@ use FireflyIII\Http\Middleware\AuthenticateTwoFactor; use FireflyIII\Http\Middleware\Binder; use FireflyIII\Http\Middleware\EncryptCookies; use FireflyIII\Http\Middleware\Installer; +use FireflyIII\Http\Middleware\InterestingMessage; use FireflyIII\Http\Middleware\IsAdmin; use FireflyIII\Http\Middleware\Range; use FireflyIII\Http\Middleware\RedirectIfAuthenticated; @@ -47,6 +48,7 @@ use Illuminate\Foundation\Http\Middleware\ValidatePostSize; use Illuminate\Routing\Middleware\ThrottleRequests; use Illuminate\View\Middleware\ShareErrorsFromSession; use Laravel\Passport\Http\Middleware\CreateFreshApiToken; +use PragmaRX\Google2FALaravel\Middleware as MFAMiddleware; /** * Class Kernel @@ -125,7 +127,7 @@ class Kernel extends HttpKernel VerifyCsrfToken::class, Binder::class, Authenticate::class, - RedirectIfTwoFactorAuthenticated::class, + //RedirectIfTwoFactorAuthenticated::class, ], // MUST be logged in @@ -154,10 +156,11 @@ class Kernel extends HttpKernel ShareErrorsFromSession::class, VerifyCsrfToken::class, Authenticate::class, - AuthenticateTwoFactor::class, + MFAMiddleware::class, Range::class, Binder::class, CreateFreshApiToken::class, + InterestingMessage::class, ], // MUST be logged in // MUST have 2fa @@ -172,7 +175,7 @@ class Kernel extends HttpKernel ShareErrorsFromSession::class, VerifyCsrfToken::class, Authenticate::class, - AuthenticateTwoFactor::class, + //AuthenticateTwoFactor::class, IsAdmin::class, Range::class, Binder::class, diff --git a/app/Http/Middleware/AuthenticateTwoFactor.php b/app/Http/Middleware/AuthenticateTwoFactor.php deleted file mode 100644 index 1b2e62b728..0000000000 --- a/app/Http/Middleware/AuthenticateTwoFactor.php +++ /dev/null @@ -1,89 +0,0 @@ -. - */ -/** @noinspection PhpMethodParametersCountMismatchInspection */ -declare(strict_types=1); - -namespace FireflyIII\Http\Middleware; - -use Closure; -use Illuminate\Contracts\Auth\Factory as Auth; -use Log; - -/** - * Class AuthenticateTwoFactor. - */ -class AuthenticateTwoFactor -{ - /** - * The authentication factory instance. - * - * @var \Illuminate\Contracts\Auth\Factory - */ - protected $auth; - - /** - * Create a new middleware instance. - * - * @param \Illuminate\Contracts\Auth\Factory $auth - * - * @return void - */ - public function __construct(Auth $auth) - { - $this->auth = $auth; - } - - - /** - * Handle 2FA request. - * - * @param $request - * @param Closure $next - * - * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|mixed - * @throws \Psr\Container\NotFoundExceptionInterface - * @throws \Psr\Container\ContainerExceptionInterface - * - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - */ - public function handle($request, Closure $next) - { - /** @noinspection PhpUndefinedMethodInspection */ - if ($this->auth->guest()) { - return response()->redirectTo(route('login')); - } - - - $is2faEnabled = app('preferences')->get('twoFactorAuthEnabled', false)->data; - $has2faSecret = null !== app('preferences')->get('twoFactorAuthSecret'); - /** @noinspection PhpUndefinedMethodInspection */ - $is2faAuthed = 'true' === $request->cookie('twoFactorAuthenticated'); - - if ($is2faEnabled && $has2faSecret && !$is2faAuthed) { - Log::debug('Does not seem to be 2 factor authed, redirect.'); - - return response()->redirectTo(route('two-factor.index')); - } - - return $next($request); - } - -} diff --git a/app/Http/Middleware/Installer.php b/app/Http/Middleware/Installer.php index cf24d83e54..732289eaa8 100644 --- a/app/Http/Middleware/Installer.php +++ b/app/Http/Middleware/Installer.php @@ -54,6 +54,7 @@ class Installer */ public function handle($request, Closure $next) { + Log::debug(sprintf('Installer middleware for URI %s', $request->url())); // ignore installer in test environment. if ('testing' === config('app.env')) { return $next($request); diff --git a/app/Http/Middleware/InterestingMessage.php b/app/Http/Middleware/InterestingMessage.php new file mode 100644 index 0000000000..19e38be5cd --- /dev/null +++ b/app/Http/Middleware/InterestingMessage.php @@ -0,0 +1,118 @@ +. + */ + +namespace FireflyIII\Http\Middleware; + + +use Closure; +use FireflyIII\Models\TransactionGroup; +use FireflyIII\Models\TransactionJournal; +use Illuminate\Http\Request; +use Log; + +/** + * Class InterestingMessage + */ +class InterestingMessage +{ + /** + * Flashes the user an interesting message if the URL parameters warrant it. + * + * @param Request $request + * @param \Closure $next + * + * @return mixed + * + */ + public function handle(Request $request, Closure $next) + { + //Log::debug(sprintf('Interesting Message middleware for URI %s', $request->url())); + if ($this->testing()) { + return $next($request); + } + + if ($this->groupMessage($request)) { + $this->handleGroupMessage($request); + } + + return $next($request); + } + + /** + * @param Request $request + * + * @return bool + */ + private function groupMessage(Request $request): bool + { + // get parameters from request. + $transactionGroupId = $request->get('transaction_group_id'); + $message = $request->get('message'); + + return null !== $transactionGroupId && null !== $message; + } + + /** + * @param Request $request + */ + private function handleGroupMessage(Request $request): void + { + + // get parameters from request. + $transactionGroupId = $request->get('transaction_group_id'); + $message = $request->get('message'); + + // send message about newly created transaction group. + /** @var TransactionGroup $group */ + $group = auth()->user()->transactionGroups()->with(['transactionJournals', 'transactionJournals.transactionType'])->find((int)$transactionGroupId); + + if (null === $group) { + return; + } + + $count = $group->transactionJournals->count(); + + /** @var TransactionJournal $journal */ + $journal = $group->transactionJournals->first(); + if (null === $journal) { + return; + } + $title = $count > 1 ? $group->title : $journal->description; + if ('created' === $message) { + session()->flash('success_uri', route('transactions.show', [$transactionGroupId])); + session()->flash('success', (string)trans('firefly.stored_journal', ['description' => $title])); + } + if ('updated' === $message) { + $type = strtolower($journal->transactionType->type); + session()->flash('success_uri', route('transactions.show', [$transactionGroupId])); + session()->flash('success', (string)trans(sprintf('firefly.updated_%s', $type), ['description' => $title])); + } + } + + /** + * @return bool + */ + private function testing(): bool + { + // ignore middleware in test environment. + return 'testing' === config('app.env') || !auth()->check(); + } +} \ No newline at end of file diff --git a/app/Http/Middleware/RedirectIfTwoFactorAuthenticated.php b/app/Http/Middleware/RedirectIfTwoFactorAuthenticated.php deleted file mode 100644 index f6bcefab69..0000000000 --- a/app/Http/Middleware/RedirectIfTwoFactorAuthenticated.php +++ /dev/null @@ -1,58 +0,0 @@ -. - */ -declare(strict_types=1); - -namespace FireflyIII\Http\Middleware; - -use Closure; -use Illuminate\Support\Facades\Auth; - -/** - * Class RedirectIfTwoFactorAuthenticated. - */ -class RedirectIfTwoFactorAuthenticated -{ - /** - * Handle an incoming request. - * - * @param \Illuminate\Http\Request $request - * @param \Closure $next - * @param string|null $guard - * - * @return mixed - * - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - */ - public function handle($request, Closure $next, $guard = null) - { - if (Auth::guard($guard)->check()) { - $is2faEnabled = app('preferences')->get('twoFactorAuthEnabled', false)->data; - $has2faSecret = null !== app('preferences')->get('twoFactorAuthSecret'); - $is2faAuthed = 'true' === $request->cookie('twoFactorAuthenticated'); - - if ($is2faEnabled && $has2faSecret && $is2faAuthed) { - return response()->redirectTo(route('index')); - } - } - - return $next($request); - } -} diff --git a/app/Http/Middleware/SecureHeaders.php b/app/Http/Middleware/SecureHeaders.php index 6859e734f8..0edfcdba17 100644 --- a/app/Http/Middleware/SecureHeaders.php +++ b/app/Http/Middleware/SecureHeaders.php @@ -44,9 +44,11 @@ class SecureHeaders { $response = $next($request); $google = ''; + $googleImg = ''; $analyticsId = config('firefly.analytics_id'); if ('' !== $analyticsId) { - $google = 'www.googletagmanager.com/gtag/js'; // @codeCoverageIgnore + $google = 'www.googletagmanager.com/gtag/js https://www.google-analytics.com/analytics.js'; // @codeCoverageIgnore + $googleImg = 'https://www.google-analytics.com/'; } $csp = [ "default-src 'none'", @@ -54,9 +56,9 @@ class SecureHeaders sprintf("script-src 'self' 'unsafe-eval' 'unsafe-inline' %s", $google), "style-src 'self' 'unsafe-inline'", "base-uri 'self'", - "font-src 'self'", + "font-src 'self' data:", "connect-src 'self'", - "img-src 'self' data: https://api.tiles.mapbox.com", + sprintf("img-src 'self' data: https://api.tiles.mapbox.com %s", $googleImg), "manifest-src 'self'", ]; diff --git a/app/Http/Middleware/StartFireflySession.php b/app/Http/Middleware/StartFireflySession.php index 00b3580734..513f49b74c 100644 --- a/app/Http/Middleware/StartFireflySession.php +++ b/app/Http/Middleware/StartFireflySession.php @@ -41,18 +41,22 @@ class StartFireflySession extends StartSession */ protected function storeCurrentUrl(Request $request, $session): void { - $uri = $request->fullUrl(); + $uri = $request->fullUrl(); $isScriptPage = strpos($uri, 'jscript'); - $isDeletePage = strpos($uri, 'delete'); + $isDeletePage = strpos($uri, 'delete'); + $isLoginPage = strpos($uri, '/login'); // also stop remembering "delete" URL's. - if (false === $isScriptPage && false === $isDeletePage && 'GET' === $request->method() && !$request->ajax()) { + if (false === $isScriptPage && false === $isDeletePage + && false === $isLoginPage + && 'GET' === $request->method() + && !$request->ajax()) { $session->setPreviousUrl($uri); - Log::debug(sprintf('Will set previous URL to %s', $uri)); + //Log::debug(sprintf('Will set previous URL to %s', $uri)); return; } - Log::debug(sprintf('Will NOT set previous URL to %s', $uri)); + //Log::debug(sprintf('Will NOT set previous URL to %s', $uri)); } } diff --git a/app/Http/Requests/AccountFormRequest.php b/app/Http/Requests/AccountFormRequest.php index 33d3310334..9bc9a38fa3 100644 --- a/app/Http/Requests/AccountFormRequest.php +++ b/app/Http/Requests/AccountFormRequest.php @@ -24,7 +24,6 @@ namespace FireflyIII\Http\Requests; use FireflyIII\Models\Account; use FireflyIII\Rules\UniqueIban; -use FireflyIII\Rules\ZeroOrMore; /** * Class AccountFormRequest. @@ -50,24 +49,24 @@ class AccountFormRequest extends Request public function getAccountData(): array { $data = [ - 'name' => $this->string('name'), - 'active' => $this->boolean('active'), - 'accountType' => $this->string('what'), - 'account_type_id' => 0, - 'currency_id' => $this->integer('currency_id'), - 'virtualBalance' => $this->string('virtualBalance'), - 'iban' => $this->string('iban'), - 'BIC' => $this->string('BIC'), - 'accountNumber' => $this->string('accountNumber'), - 'accountRole' => $this->string('accountRole'), - 'openingBalance' => $this->string('openingBalance'), - 'openingBalanceDate' => $this->date('openingBalanceDate'), - 'ccType' => $this->string('ccType'), - 'ccMonthlyPaymentDate' => $this->string('ccMonthlyPaymentDate'), - 'notes' => $this->string('notes'), - 'interest' => $this->string('interest'), - 'interest_period' => $this->string('interest_period'), - 'include_net_worth' => '1', + 'name' => $this->string('name'), + 'active' => $this->boolean('active'), + 'account_type' => $this->string('objectType'), + 'account_type_id' => 0, + 'currency_id' => $this->integer('currency_id'), + 'virtual_balance' => $this->string('virtual_balance'), + 'iban' => $this->string('iban'), + 'BIC' => $this->string('BIC'), + 'account_number' => $this->string('account_number'), + 'account_role' => $this->string('account_role'), + 'opening_balance' => $this->string('opening_balance'), + 'opening_balance_date' => $this->date('opening_balance_date'), + 'cc_type' => $this->string('cc_type'), + 'cc_monthly_payment_date' => $this->string('cc_monthly_payment_date'), + 'notes' => $this->string('notes'), + 'interest' => $this->string('interest'), + 'interest_period' => $this->string('interest_period'), + 'include_net_worth' => '1', ]; if (false === $this->boolean('include_net_worth')) { $data['include_net_worth'] = '0'; @@ -75,13 +74,9 @@ class AccountFormRequest extends Request // if the account type is "liabilities" there are actually four types of liability // that could have been selected. - if ('liabilities' === $data['accountType']) { - $data['accountType'] = null; + if ('liabilities' === $data['account_type']) { + $data['account_type'] = null; $data['account_type_id'] = $this->integer('liability_type_id'); - // also reverse the opening balance: - if ('' !== $data['openingBalance']) { - $data['openingBalance'] = bcmul($data['openingBalance'], '-1'); - } } return $data; @@ -98,27 +93,26 @@ class AccountFormRequest extends Request $types = implode(',', array_keys(config('firefly.subTitlesByIdentifier'))); $ccPaymentTypes = implode(',', array_keys(config('firefly.ccTypes'))); $rules = [ - 'name' => 'required|min:1|uniqueAccountForUser', - 'openingBalance' => 'numeric|required_with:openingBalanceDate|nullable', - 'openingBalanceDate' => 'date|required_with:openingBalance|nullable', - 'iban' => ['iban', 'nullable', new UniqueIban(null, $this->string('what'))], - 'BIC' => 'bic|nullable', - 'virtualBalance' => 'numeric|nullable', - 'currency_id' => 'exists:transaction_currencies,id', - 'accountNumber' => 'between:1,255|uniqueAccountNumberForUser|nullable', - 'accountRole' => 'in:' . $accountRoles, - 'active' => 'boolean', - 'ccType' => 'in:' . $ccPaymentTypes, - 'ccMonthlyPaymentDate' => 'date', - 'amount_currency_id_openingBalance' => 'exists:transaction_currencies,id', - 'amount_currency_id_virtualBalance' => 'exists:transaction_currencies,id', - 'what' => 'in:' . $types, - 'interest_period' => 'in:daily,monthly,yearly', + 'name' => 'required|min:1|uniqueAccountForUser', + 'opening_balance' => 'numeric|required_with:opening_balance_date|nullable', + 'opening_balance_date' => 'date|required_with:opening_balance|nullable', + 'iban' => ['iban', 'nullable', new UniqueIban(null, $this->string('objectType'))], + 'BIC' => 'bic|nullable', + 'virtual_balance' => 'numeric|nullable', + 'currency_id' => 'exists:transaction_currencies,id', + 'account_number' => 'between:1,255|uniqueAccountNumberForUser|nullable', + 'account_role' => 'in:' . $accountRoles, + 'active' => 'boolean', + 'cc_type' => 'in:' . $ccPaymentTypes, + 'amount_currency_id_opening_balance' => 'exists:transaction_currencies,id', + 'amount_currency_id_virtual_balance' => 'exists:transaction_currencies,id', + 'what' => 'in:' . $types, + 'interest_period' => 'in:daily,monthly,yearly', ]; - if ('liabilities' === $this->get('what')) { - $rules['openingBalance'] = ['numeric', 'required', new ZeroOrMore]; - $rules['openingBalanceDate'] = 'date|required'; + if ('liabilities' === $this->get('objectType')) { + $rules['opening_balance'] = ['numeric', 'required']; + $rules['opening_balance_date'] = 'date|required'; } /** @var Account $account */ diff --git a/app/Http/Requests/BudgetFormRequest.php b/app/Http/Requests/BudgetFormRequest.php index c803cdaaaa..937704396f 100644 --- a/app/Http/Requests/BudgetFormRequest.php +++ b/app/Http/Requests/BudgetFormRequest.php @@ -28,6 +28,7 @@ use FireflyIII\Models\Budget; * Class BudgetFormRequest. * * @codeCoverageIgnore + * TODO after 4.8.0, split for update/store */ class BudgetFormRequest extends Request { diff --git a/app/Http/Requests/ExportFormRequest.php b/app/Http/Requests/ExportFormRequest.php deleted file mode 100644 index d19441209c..0000000000 --- a/app/Http/Requests/ExportFormRequest.php +++ /dev/null @@ -1,69 +0,0 @@ -. - */ -declare(strict_types=1); - -namespace FireflyIII\Http\Requests; - -use Carbon\Carbon; - -/** - * Class ExportFormRequest. - */ -class ExportFormRequest extends Request -{ - /** - * Verify the request. - * - * @return bool - */ - public function authorize(): bool - { - // Only allow logged in users - return auth()->check(); - } - - /** - * Rules for this request. - * - * @return array - */ - public function rules(): array - { - /** @var Carbon $sessionFirst */ - $sessionFirst = clone session('first'); - $first = $sessionFirst->subDay()->format('Y-m-d'); - $today = Carbon::now()->addDay()->format('Y-m-d'); - $formats = implode(',', array_keys(config('firefly.export_formats'))); - - // fixed - - return [ - 'export_start_range' => 'required|date|after:' . $first, - 'export_end_range' => 'required|date|before:' . $today, - 'accounts' => 'required', - 'job' => 'required|belongsToUser:export_jobs,key', - 'accounts.*' => 'required|exists:accounts,id|belongsToUser:accounts', - 'include_attachments' => 'in:0,1', - 'include_config' => 'in:0,1', - 'exportFormat' => 'in:' . $formats, - ]; - } -} diff --git a/app/Http/Requests/JournalFormRequest.php b/app/Http/Requests/JournalFormRequest.php deleted file mode 100644 index aa70075cf2..0000000000 --- a/app/Http/Requests/JournalFormRequest.php +++ /dev/null @@ -1,341 +0,0 @@ -. - */ -declare(strict_types=1); - -namespace FireflyIII\Http\Requests; - -use FireflyIII\Exceptions\FireflyException; -use FireflyIII\Models\TransactionType; -use Illuminate\Validation\Validator; -use Log; - -/** - * Class JournalFormRequest. - */ -class JournalFormRequest extends Request -{ - /** - * Verify the request. - * - * @return bool - */ - public function authorize(): bool - { - // Only allow logged in users - return auth()->check(); - } - - /** - * Returns and validates the data required to store a new journal. Can handle both single transaction journals and split journals. - * - * @return array - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - */ - public function getJournalData(): array - { - $currencyId = $this->integer('amount_currency_id_amount'); - $data = [ - 'type' => $this->get('what'), // type. can be 'deposit', 'withdrawal' or 'transfer' - 'date' => $this->date('date'), - 'tags' => explode(',', $this->string('tags')), - 'user' => auth()->user()->id, - - // all custom fields: - 'interest_date' => $this->date('interest_date'), - 'book_date' => $this->date('book_date'), - 'process_date' => $this->date('process_date'), - 'due_date' => $this->date('due_date'), - 'payment_date' => $this->date('payment_date'), - 'invoice_date' => $this->date('invoice_date'), - 'internal_reference' => $this->string('internal_reference'), - 'notes' => $this->string('notes'), - - // journal data: - 'description' => $this->string('description'), - 'piggy_bank_id' => $this->integer('piggy_bank_id'), - 'piggy_bank_name' => null, - 'bill_id' => null, - 'bill_name' => null, - 'original-source' => sprintf('gui-v%s', config('firefly.version')), - - // transaction data: - 'transactions' => [ - [ - 'currency_id' => null, - 'currency_code' => null, - 'description' => null, - 'amount' => $this->string('amount'), - 'budget_id' => $this->integer('budget_id'), - 'budget_name' => null, - 'category_id' => null, - 'category_name' => $this->string('category'), - 'source_id' => $this->integer('source_id'), - 'source_name' => $this->string('source_name'), - 'destination_id' => $this->integer('destination_id'), - 'destination_name' => $this->string('destination_name'), - 'foreign_currency_id' => null, - 'foreign_currency_code' => null, - 'foreign_amount' => null, - 'reconciled' => false, - 'identifier' => 0, - ], - ], - ]; - switch (strtolower($data['type'])) { - case 'withdrawal': - $sourceCurrency = $this->integer('source_account_currency'); - $data['transactions'][0]['currency_id'] = $sourceCurrency; - $data['transactions'][0]['destination_id'] = null; // clear destination ID (transfer) - if ($sourceCurrency !== $currencyId) { - // user has selected a foreign currency. - $data['transactions'][0]['foreign_currency_id'] = $currencyId; - $data['transactions'][0]['foreign_amount'] = $this->string('amount'); - $data['transactions'][0]['amount'] = $this->string('native_amount'); - } - - break; - case 'deposit': - $destinationCurrency = $this->integer('destination_account_currency'); - $data['transactions'][0]['currency_id'] = $destinationCurrency; - $data['transactions'][0]['source_id'] = null; // clear destination ID (transfer) - if ($destinationCurrency !== $currencyId) { - // user has selected a foreign currency. - $data['transactions'][0]['foreign_currency_id'] = $currencyId; - $data['transactions'][0]['foreign_amount'] = $this->string('amount'); - $data['transactions'][0]['amount'] = $this->string('native_amount'); - } - break; - case 'transfer': - // by default just assume source currency - $sourceCurrency = $this->integer('source_account_currency'); - $destinationCurrency = $this->integer('destination_account_currency'); - $data['transactions'][0]['currency_id'] = $sourceCurrency; - if ($sourceCurrency !== $destinationCurrency) { - // user has selected a foreign currency. - $data['transactions'][0]['foreign_currency_id'] = $destinationCurrency; - $data['transactions'][0]['foreign_amount'] = $this->string('destination_amount'); - $data['transactions'][0]['amount'] = $this->string('source_amount'); - } - break; - - } - - return $data; - } - - /** - * Rules for this request. - * - * @return array - * @throws FireflyException - */ - public function rules(): array - { - $what = $this->get('what'); - $rules = [ - 'what' => 'required|in:withdrawal,deposit,transfer', - 'date' => 'required|date', - 'amount_currency_id_amount' => 'exists:transaction_currencies,id|required', - // then, custom fields: - 'interest_date' => 'date|nullable', - 'book_date' => 'date|nullable', - 'process_date' => 'date|nullable', - 'due_date' => 'date|nullable', - 'payment_date' => 'date|nullable', - 'invoice_date' => 'date|nullable', - 'internal_reference' => 'min:1|max:255|nullable', - 'notes' => 'min:1|max:50000|nullable', - // and then transaction rules: - 'description' => 'required|between:1,255', - 'amount' => 'numeric|required|more:0|less:10000000',// - 'budget_id' => 'mustExist:budgets,id|belongsToUser:budgets,id|nullable', - 'category' => 'between:1,255|nullable', - 'source_id' => 'numeric|belongsToUser:accounts,id|nullable', - 'source_name' => 'between:1,255|nullable', - 'destination_id' => 'numeric|belongsToUser:accounts,id|nullable', - 'destination_name' => 'between:1,255|nullable', - 'piggy_bank_id' => 'numeric|nullable', - - // foreign currency amounts - 'native_amount' => 'numeric|more:0|nullable', - 'source_amount' => 'numeric|more:0|nullable', - 'destination_amount' => 'numeric|more:0|nullable', - ]; - - // some rules get an upgrade depending on the type of data: - $rules = $this->enhanceRules($what, $rules); - - return $rules; - } - - /** - * Configure the validator instance. - * - * @param Validator $validator - * - * @return void - */ - public function withValidator(Validator $validator): void - { - $validator->after( - function (Validator $validator) { - $this->validNativeAmount($validator); - } - ); - } - - /** - * Inspired by https://www.youtube.com/watch?v=WwnI0RS6J5A. - * - * @param string $what - * @param array $rules - * - * @return array - * - * @throws FireflyException - */ - private function enhanceRules(string $what, array $rules): array - { - switch ($what) { - case strtolower(TransactionType::WITHDRAWAL): - $rules['source_id'] = 'required|exists:accounts,id|belongsToUser:accounts'; - $rules['destination_name'] = 'between:1,255|nullable'; - break; - case strtolower(TransactionType::DEPOSIT): - $rules['source_name'] = 'between:1,255|nullable'; - $rules['destination_id'] = 'required|exists:accounts,id|belongsToUser:accounts'; - break; - case strtolower(TransactionType::TRANSFER): - // this may not work: - $rules['source_id'] = 'required|exists:accounts,id|belongsToUser:accounts|different:destination_id'; - $rules['destination_id'] = 'required|exists:accounts,id|belongsToUser:accounts|different:source_id'; - - break; - default: - throw new FireflyException(sprintf('Cannot handle transaction type of type "%s"', $what)); // @codeCoverageIgnore - } - - return $rules; - } - - /** - * Check if amounts are valid. - * - * @param Validator $validator - */ - private function validNativeAmount(Validator $validator): void - { - $data = $validator->getData(); - $type = $data['what'] ?? 'invalid'; - Log::debug(sprintf('Type is %s', $type)); - if ('withdrawal' === $type) { - $this->validateWithdrawal($validator); - } - - // same thing for deposits: - if ('deposit' === $type) { - $this->validateDeposit($validator); - } - - // and for transfers - if ('transfer' === $type) { - $this->validateTransfer($validator); - } - } - - /** - * Check if deposit amount is valid. - * - * @param Validator $validator - * - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - */ - private function validateDeposit(Validator $validator): void - { - $data = $validator->getData(); - $selectedCurrency = (int)($data['amount_currency_id_amount'] ?? 0); - $accountCurrency = (int)($data['destination_account_currency'] ?? 0); - $nativeAmount = (string)($data['native_amount'] ?? ''); - - Log::debug('Now in validateDeposit.'); - Log::debug(sprintf('SelectedCurrency is "%s", accountCurrency is "%s", native amount is "%s".', $selectedCurrency, $accountCurrency, $nativeAmount)); - - if ($selectedCurrency !== $accountCurrency && '' === $nativeAmount && 0 !== $selectedCurrency && 0 !== $accountCurrency) { - Log::debug('Adding an error about missing native amount.'); - $validator->errors()->add('native_amount', (string)trans('validation.numeric_native')); - - return; - } - } - - /** - * Check if transfer amount is valid. - * - * @param Validator $validator - * - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - */ - private function validateTransfer(Validator $validator): void - { - $data = $validator->getData(); - $sourceCurrency = (int)($data['source_account_currency'] ?? 0); - $destinationCurrency = (int)($data['destination_account_currency'] ?? 0); - $sourceAmount = (string)($data['source_amount'] ?? ''); - $destinationAmount = (string)($data['destination_amount'] ?? ''); - - Log::debug(sprintf('Source currency is %d, destination currency is %d', $sourceCurrency, $destinationCurrency)); - - if ($sourceCurrency !== $destinationCurrency && '' === $sourceAmount && 0 !== $sourceCurrency && 0 !== $destinationCurrency) { - $validator->errors()->add('source_amount', (string)trans('validation.numeric_source')); - } - - if ($sourceCurrency !== $destinationCurrency && '' === $destinationAmount && 0 !== $sourceCurrency && 0 !== $destinationCurrency) { - $validator->errors()->add('destination_amount', (string)trans('validation.numeric_destination')); - $validator->errors()->add('destination_amount', (string)trans('validation.numeric', ['attribute' => 'destination_amount'])); - } - - } - - /** - * Check if withdrawal amount is valid. - * - * @param Validator $validator - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - */ - private function validateWithdrawal(Validator $validator): void - { - $data = $validator->getData(); - $selectedCurrency = (int)($data['amount_currency_id_amount'] ?? 0); - $accountCurrency = (int)($data['source_account_currency'] ?? 0); - Log::debug(sprintf('Selected currency is %d, account currency is %d', $selectedCurrency, $accountCurrency)); - $nativeAmount = (string)($data['native_amount'] ?? ''); - if ($selectedCurrency !== $accountCurrency && '' === $nativeAmount - && 0 !== $selectedCurrency - && 0 !== $accountCurrency - ) { - Log::debug('ADD validation error on native_amount'); - $validator->errors()->add('native_amount', (string)trans('validation.numeric_native')); - - return; - } - } -} diff --git a/app/Http/Requests/JournalLinkRequest.php b/app/Http/Requests/JournalLinkRequest.php index d5fac5708f..aefb3a7cd0 100644 --- a/app/Http/Requests/JournalLinkRequest.php +++ b/app/Http/Requests/JournalLinkRequest.php @@ -52,12 +52,9 @@ class JournalLinkRequest extends Request $linkType = $this->get('link_type'); $parts = explode('_', $linkType); $return['link_type_id'] = (int)$parts[0]; - $return['transaction_journal_id'] = $this->integer('link_journal_id'); + $return['transaction_journal_id'] = $this->integer('opposing'); $return['notes'] = $this->string('notes'); $return['direction'] = $parts[1]; - if (0 === $return['transaction_journal_id'] && ctype_digit($this->string('link_other'))) { - $return['transaction_journal_id'] = $this->integer('link_other'); - } return $return; } @@ -81,8 +78,8 @@ class JournalLinkRequest extends Request // fixed return [ - 'link_type' => sprintf('required|in:%s', $string), - 'link_journal_id' => 'belongsToUser:transaction_journals', + 'link_type' => sprintf('required|in:%s', $string), + 'opposing' => 'belongsToUser:transaction_journals', ]; } } diff --git a/app/Http/Requests/LinkTypeFormRequest.php b/app/Http/Requests/LinkTypeFormRequest.php index 7e75f6abcf..05d086a263 100644 --- a/app/Http/Requests/LinkTypeFormRequest.php +++ b/app/Http/Requests/LinkTypeFormRequest.php @@ -23,7 +23,7 @@ declare(strict_types=1); namespace FireflyIII\Http\Requests; /** - * Class BillFormRequest. + * Class LinkTypeFormRequest. */ class LinkTypeFormRequest extends Request { diff --git a/app/Http/Requests/MassEditJournalRequest.php b/app/Http/Requests/MassEditJournalRequest.php index b171ecfa20..94467f0239 100644 --- a/app/Http/Requests/MassEditJournalRequest.php +++ b/app/Http/Requests/MassEditJournalRequest.php @@ -53,6 +53,7 @@ class MassEditJournalRequest extends Request 'description.*' => 'required|min:1,max:255', 'source_id.*' => 'numeric|belongsToUser:accounts,id', 'destination_id.*' => 'numeric|belongsToUser:accounts,id', + 'journals.*' => 'numeric|belongsToUser:transaction_journals,id', 'revenue_account' => 'max:255', 'expense_account' => 'max:255', ]; diff --git a/app/Http/Requests/ReconciliationStoreRequest.php b/app/Http/Requests/ReconciliationStoreRequest.php index 3e1fc096f9..1113266f04 100644 --- a/app/Http/Requests/ReconciliationStoreRequest.php +++ b/app/Http/Requests/ReconciliationStoreRequest.php @@ -23,7 +23,7 @@ declare(strict_types=1); namespace FireflyIII\Http\Requests; -use FireflyIII\Rules\ValidTransactions; +use FireflyIII\Rules\ValidJournals; use Log; /** @@ -49,8 +49,8 @@ class ReconciliationStoreRequest extends Request */ public function getAll(): array { - $transactions = $this->get('transactions'); - if (!\is_array($transactions)) { + $transactions = $this->get('journals'); + if (!is_array($transactions)) { $transactions = []; // @codeCoverageIgnore } $data = [ @@ -59,7 +59,7 @@ class ReconciliationStoreRequest extends Request 'start_balance' => $this->string('startBalance'), 'end_balance' => $this->string('endBalance'), 'difference' => $this->string('difference'), - 'transactions' => $transactions, + 'journals' => $transactions, 'reconcile' => $this->string('reconcile'), ]; Log::debug('In ReconciliationStoreRequest::getAll(). Will now return data.'); @@ -80,7 +80,7 @@ class ReconciliationStoreRequest extends Request 'startBalance' => 'numeric', 'endBalance' => 'numeric', 'difference' => 'required|numeric', - 'transactions' => [new ValidTransactions], + 'journals' => [new ValidJournals], 'reconcile' => 'required|in:create,nothing', ]; } diff --git a/app/Http/Requests/RecurrenceFormRequest.php b/app/Http/Requests/RecurrenceFormRequest.php index f80821163e..5863128db3 100644 --- a/app/Http/Requests/RecurrenceFormRequest.php +++ b/app/Http/Requests/RecurrenceFormRequest.php @@ -29,6 +29,9 @@ use FireflyIII\Models\Recurrence; use FireflyIII\Models\TransactionType; use FireflyIII\Rules\ValidRecurrenceRepetitionType; use FireflyIII\Rules\ValidRecurrenceRepetitionValue; +use FireflyIII\Validation\AccountValidator; +use Illuminate\Validation\Validator; +use Log; /** * Class RecurrenceFormRequest @@ -120,11 +123,11 @@ class RecurrenceFormRequest extends Request default: throw new FireflyException(sprintf('Cannot handle transaction type "%s"', $this->string('transaction_type'))); // @codeCoverageIgnore case 'withdrawal': - $return['transactions'][0]['source_id'] = $this->integer('source_id'); - $return['transactions'][0]['destination_name'] = $this->string('destination_name'); + $return['transactions'][0]['source_id'] = $this->integer('source_id'); + $return['transactions'][0]['destination_id'] = $this->integer('withdrawal_destination_id'); break; case 'deposit': - $return['transactions'][0]['source_name'] = $this->string('source_name'); + $return['transactions'][0]['source_id'] = $this->integer('deposit_source_id'); $return['transactions'][0]['destination_id'] = $this->integer('destination_id'); break; case 'transfer': @@ -136,6 +139,86 @@ class RecurrenceFormRequest extends Request return $return; } + + /** + * Configure the validator instance with special rules for after the basic validation rules. + * + * @param Validator $validator + * + * @return void + */ + public function withValidator(Validator $validator): void + { + $validator->after( + function (Validator $validator) { + // validate all account info + $this->validateAccountInformation($validator); + } + ); + } + + /** + * Validates the given account information. Switches on given transaction type. + * + * @param Validator $validator + * @throws FireflyException + */ + public function validateAccountInformation(Validator $validator): void + { + Log::debug('Now in validateAccountInformation()'); + /** @var AccountValidator $accountValidator */ + $accountValidator = app(AccountValidator::class); + $data = $validator->getData(); + $transactionType = $data['transaction_type'] ?? 'invalid'; + + $accountValidator->setTransactionType($transactionType); + + // default values: + $sourceId = null; + $destinationId = null; + + switch ($this->string('transaction_type')) { + default: + throw new FireflyException(sprintf('Cannot handle transaction type "%s"', $this->string('transaction_type'))); // @codeCoverageIgnore + case 'withdrawal': + $sourceId = (int)$data['source_id']; + $destinationId = (int)$data['withdrawal_destination_id']; + break; + case 'deposit': + $sourceId = (int)$data['deposit_source_id']; + $destinationId = (int)$data['destination_id']; + break; + case 'transfer': + $sourceId = (int)$data['source_id']; + $destinationId = (int)$data['destination_id']; + break; + } + + + // validate source account. + $validSource = $accountValidator->validateSource($sourceId, null); + + // do something with result: + if (false === $validSource) { + $message = (string)trans('validation.generic_invalid_source'); + $validator->errors()->add('source_id', $message); + $validator->errors()->add('deposit_source_id', $message); + + return; + } + + // validate destination account + $validDestination = $accountValidator->validateDestination($destinationId, null); + // do something with result: + if (false === $validDestination) { + $message = (string)trans('validation.generic_invalid_destination'); + $validator->errors()->add('destination_id', $message); + $validator->errors()->add('withdrawal_destination_id', $message); + + return; + } + } + /** * The rules for this request. * @@ -251,7 +334,7 @@ class RecurrenceFormRequest extends Request } //monthly,17 //ndom,3,7 - if (\in_array(substr($value, 0, 6), ['yearly', 'weekly'])) { + if (in_array(substr($value, 0, 6), ['yearly', 'weekly'])) { $return['type'] = substr($value, 0, 6); $return['moment'] = substr($value, 7); } diff --git a/app/Http/Requests/ReportFormRequest.php b/app/Http/Requests/ReportFormRequest.php index e806760e4c..1402033232 100644 --- a/app/Http/Requests/ReportFormRequest.php +++ b/app/Http/Requests/ReportFormRequest.php @@ -60,7 +60,7 @@ class ReportFormRequest extends Request $repository = app(AccountRepositoryInterface::class); $set = $this->get('accounts'); $collection = new Collection; - if (\is_array($set)) { + if (is_array($set)) { foreach ($set as $accountId) { $account = $repository->findNull((int)$accountId); if (null !== $account) { @@ -83,7 +83,7 @@ class ReportFormRequest extends Request $repository = app(BudgetRepositoryInterface::class); $set = $this->get('budget'); $collection = new Collection; - if (\is_array($set)) { + if (is_array($set)) { foreach ($set as $budgetId) { $budget = $repository->findNull((int)$budgetId); if (null !== $budget) { @@ -106,7 +106,7 @@ class ReportFormRequest extends Request $repository = app(CategoryRepositoryInterface::class); $set = $this->get('category'); $collection = new Collection; - if (\is_array($set)) { + if (is_array($set)) { foreach ($set as $categoryId) { $category = $repository->findNull((int)$categoryId); if (null !== $category) { @@ -130,7 +130,7 @@ class ReportFormRequest extends Request $date = new Carbon; $range = $this->get('daterange'); $parts = explode(' - ', (string)$range); - if (2 === \count($parts)) { + if (2 === count($parts)) { try { $date = new Carbon($parts[1]); // @codeCoverageIgnoreStart @@ -157,7 +157,7 @@ class ReportFormRequest extends Request $repository = app(AccountRepositoryInterface::class); $set = $this->get('exp_rev'); $collection = new Collection; - if (\is_array($set)) { + if (is_array($set)) { foreach ($set as $accountId) { $account = $repository->findNull((int)$accountId); if (null !== $account) { @@ -181,7 +181,7 @@ class ReportFormRequest extends Request $date = new Carbon; $range = $this->get('daterange'); $parts = explode(' - ', (string)$range); - if (2 === \count($parts)) { + if (2 === count($parts)) { try { $date = new Carbon($parts[0]); // @codeCoverageIgnoreStart @@ -208,7 +208,7 @@ class ReportFormRequest extends Request $set = $this->get('tag'); $collection = new Collection; Log::debug('Set is:', $set ?? []); - if (\is_array($set)) { + if (is_array($set)) { foreach ($set as $tagTag) { Log::debug(sprintf('Now searching for "%s"', $tagTag)); $tag = $repository->findByTag($tagTag); diff --git a/app/Http/Requests/Request.php b/app/Http/Requests/Request.php index be04cca2ba..d531c6f7a1 100644 --- a/app/Http/Requests/Request.php +++ b/app/Http/Requests/Request.php @@ -24,6 +24,7 @@ namespace FireflyIII\Http\Requests; use Carbon\Carbon; use Carbon\Exceptions\InvalidDateException; +use Exception; use Illuminate\Foundation\Http\FormRequest; use Log; @@ -36,6 +37,26 @@ use Log; */ class Request extends FormRequest { + /** + * @param $array + * + * @return array|null + */ + public function arrayFromValue($array): ?array + { + if (is_array($array)) { + return $array; + } + if (null === $array) { + return null; + } + if (is_string($array)) { + return explode(',', $array); + } + + return null; + } + /** * Return a boolean value. * @@ -60,11 +81,17 @@ class Request extends FormRequest * * @return bool */ - public function convertBoolean(string $value): bool + public function convertBoolean(?string $value): bool { + if (null === $value) { + return false; + } if ('true' === $value) { return true; } + if ('yes' === $value) { + return true; + } if (1 === $value) { return true; } @@ -78,6 +105,30 @@ class Request extends FormRequest return false; } + /** + * @param string|null $string + * + * @return Carbon|null + */ + public function dateFromValue(?string $string): ?Carbon + { + if (null === $string) { + return null; + } + if ('' === $string) { + return null; + } + try { + $carbon = new Carbon($string); + } catch (Exception $e) { + Log::debug(sprintf('Invalid date: %s: %s', $string, $e->getMessage())); + + return null; + } + + return $carbon; + } + /** * Return floating value. * @@ -107,6 +158,25 @@ class Request extends FormRequest return (int)$this->get($field); } + /** + * Parse to integer + * + * @param string|null $string + * + * @return int|null + */ + public function integerFromValue(?string $string): ?int + { + if (null === $string) { + return null; + } + if ('' === $string) { + return null; + } + + return (int)$string; + } + /** * Return string value. * @@ -121,6 +191,24 @@ class Request extends FormRequest return app('steam')->cleanString((string)($this->get($field) ?? '')); } + /** + * Parse and clean a string. + * + * @param string|null $string + * + * @return string|null + */ + public function stringFromValue(?string $string): ?string + { + if (null === $string) { + return null; + } + $result = app('steam')->cleanString($string); + + return '' === $result ? null : $result; + + } + /** * Return date or NULL. * @@ -130,7 +218,14 @@ class Request extends FormRequest */ protected function date(string $field): ?Carbon { - return $this->get($field) ? new Carbon($this->get($field)) : null; + $result = null; + try { + $result = $this->get($field) ? new Carbon($this->get($field)) : null; + } catch (Exception $e) { + Log::debug(sprintf('Exception when parsing date. Not interesting: %s', $e->getMessage())); + } + + return $result; } /** @@ -146,7 +241,7 @@ class Request extends FormRequest return null; } $value = (string)$this->get($field); - if (10 === \strlen($value)) { + if (10 === strlen($value)) { // probably a date format. try { $result = Carbon::createFromFormat('Y-m-d', $value); diff --git a/app/Http/Requests/RuleFormRequest.php b/app/Http/Requests/RuleFormRequest.php index 38e4ad8ac2..da90a90ba9 100644 --- a/app/Http/Requests/RuleFormRequest.php +++ b/app/Http/Requests/RuleFormRequest.php @@ -112,7 +112,7 @@ class RuleFormRequest extends Request { $return = []; $actionData = $this->get('actions'); - if (\is_array($actionData)) { + if (is_array($actionData)) { foreach ($actionData as $action) { $stopProcessing = $action['stop_processing'] ?? '0'; $return[] = [ @@ -133,7 +133,7 @@ class RuleFormRequest extends Request { $return = []; $triggerData = $this->get('triggers'); - if (\is_array($triggerData)) { + if (is_array($triggerData)) { foreach ($triggerData as $trigger) { $stopProcessing = $trigger['stop_processing'] ?? '0'; $return[] = [ diff --git a/app/Http/Requests/SelectTransactionsRequest.php b/app/Http/Requests/SelectTransactionsRequest.php index 5697fdfb62..74085b2806 100644 --- a/app/Http/Requests/SelectTransactionsRequest.php +++ b/app/Http/Requests/SelectTransactionsRequest.php @@ -25,7 +25,7 @@ namespace FireflyIII\Http\Requests; use Carbon\Carbon; /** - * Class ExportFormRequest. + * Class SelectTransactionsRequest. * * @codeCoverageIgnore */ diff --git a/app/Http/Requests/SplitJournalFormRequest.php b/app/Http/Requests/SplitJournalFormRequest.php deleted file mode 100644 index caed4c9d5f..0000000000 --- a/app/Http/Requests/SplitJournalFormRequest.php +++ /dev/null @@ -1,180 +0,0 @@ -. - */ -declare(strict_types=1); - -namespace FireflyIII\Http\Requests; - -use Illuminate\Validation\Validator; - -/** - * Class SplitJournalFormRequest. - */ -class SplitJournalFormRequest extends Request -{ - /** - * Verify the request. - * - * @return bool - */ - public function authorize(): bool - { - // Only allow logged in users - return auth()->check(); - } - - /** - * Get all info for the controller. - * - * @return array - * - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - */ - public function getAll(): array - { - $data = [ - 'description' => $this->string('journal_description'), - 'type' => $this->string('what'), - 'date' => $this->date('date'), - 'tags' => explode(',', $this->string('tags')), - 'bill_id' => null, - 'bill_name' => null, - 'piggy_bank_id' => null, - 'piggy_bank_name' => null, - 'notes' => $this->string('notes'), - 'transactions' => [], - ]; - // switch type to get correct source / destination info: - $sourceId = null; - $sourceName = null; - $destinationId = null; - $destinationName = null; - - foreach ($this->get('transactions') as $index => $transaction) { - switch ($data['type']) { - case 'withdrawal': - $sourceId = $this->integer('journal_source_id'); - $destinationName = $transaction['destination_name'] ?? ''; - break; - case 'deposit': - $sourceName = $transaction['source_name'] ?? ''; - $destinationId = $this->integer('journal_destination_id'); - break; - case 'transfer': - $sourceId = $this->integer('journal_source_id'); - $destinationId = $this->integer('journal_destination_id'); - break; - } - $foreignAmount = $transaction['foreign_amount'] ?? null; - $foreignCurrencyId = (int)($transaction['foreign_currency_id'] ?? 0.0); - $set = [ - 'source_id' => $sourceId, - 'source_name' => $sourceName, - 'destination_id' => $destinationId, - 'destination_name' => $destinationName, - 'foreign_amount' => $foreignAmount, - 'foreign_currency_id' => $foreignCurrencyId, - 'foreign_currency_code' => null, - 'reconciled' => false, - 'identifier' => $index, - 'currency_id' => (int)$transaction['currency_id'], - 'currency_code' => null, - 'description' => $transaction['transaction_description'] ?? '', - 'amount' => $transaction['amount'] ?? '', - 'budget_id' => (int)($transaction['budget_id'] ?? 0.0), - 'budget_name' => null, - 'category_id' => null, - 'category_name' => $transaction['category_name'] ?? '', - ]; - $data['transactions'][] = $set; - } - - return $data; - } - - /** - * Rules for this request. - * - * @return array - */ - public function rules(): array - { - return [ - 'what' => 'required|in:withdrawal,deposit,transfer', - 'journal_description' => 'required|between:1,255', - 'id' => 'numeric|belongsToUser:transaction_journals,id', - 'journal_source_id' => 'numeric|belongsToUser:accounts,id', - 'journal_source_name.*' => 'between:1,255', - 'journal_currency_id' => 'required|exists:transaction_currencies,id', - 'date' => 'required|date', - 'interest_date' => 'date|nullable', - 'book_date' => 'date|nullable', - 'process_date' => 'date|nullable', - 'transactions.*.transaction_description' => 'required|between:1,255', - 'transactions.*.destination_id' => 'numeric|belongsToUser:accounts,id', - 'transactions.*.destination_name' => 'between:1,255|nullable', - 'transactions.*.amount' => 'required|numeric', - 'transactions.*.budget_id' => 'belongsToUser:budgets,id', - 'transactions.*.category_name' => 'between:1,255|nullable', - 'transactions.*.piggy_bank_id' => 'numeric|nullable', - ]; - } - - /** - * Configure the validator instance. - * - * @param Validator $validator - * - * @return void - */ - public function withValidator(Validator $validator): void - { - $validator->after( - function (Validator $validator) { - $this->sameAccounts($validator); - } - ); - } - - /** - * Verify that source and destination are not the same. - * - * @param Validator $validator - * - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - */ - protected function sameAccounts(Validator $validator): void - { - $data = $this->getAll(); - $transactions = $data['transactions'] ?? []; - /** @var array $array */ - foreach ($transactions as $array) { - if (null !== $array['destination_id'] && null !== $array['source_id'] && $array['destination_id'] === $array['source_id']) { - // @codeCoverageIgnoreStart - $validator->errors()->add('journal_source_id', (string)trans('validation.source_equals_destination')); - $validator->errors()->add('journal_destination_id', (string)trans('validation.source_equals_destination')); - // @codeCoverageIgnoreEnd - } - } - - } - -} diff --git a/app/Import/Converter/Amount.php b/app/Import/Converter/Amount.php index a2ee082264..637867f32d 100644 --- a/app/Import/Converter/Amount.php +++ b/app/Import/Converter/Amount.php @@ -110,7 +110,7 @@ class Amount implements ConverterInterface */ private function alternativeDecimalSign(string $value): bool { - $length = \strlen($value); + $length = strlen($value); $altPosition = $length - 2; return $length > 1 && ('.' === $value[$altPosition] || ',' === $value[$altPosition]); @@ -125,7 +125,7 @@ class Amount implements ConverterInterface */ private function decimalIsComma(string $value): bool { - $length = \strlen($value); + $length = strlen($value); $decimalPosition = $length - 3; return $length > 2 && ',' === $value[$decimalPosition]; @@ -140,7 +140,7 @@ class Amount implements ConverterInterface */ private function decimalIsDot(string $value): bool { - $length = \strlen($value); + $length = strlen($value); $decimalPosition = $length - 3; return ($length > 2 && '.' === $value[$decimalPosition]) || ($length > 2 && strpos($value, '.') > $decimalPosition); @@ -177,7 +177,7 @@ class Amount implements ConverterInterface */ private function getAlternativeDecimalSign(string $value): string { - $length = \strlen($value); + $length = strlen($value); $altPosition = $length - 2; return $value[$altPosition]; @@ -222,7 +222,7 @@ class Amount implements ConverterInterface // have to strip the € because apparantly the Postbank (DE) thinks "1.000,00 €" is a normal way to format a number. $value = trim((string)str_replace(['€'], '', $value)); $str = preg_replace('/[^\-\(\)\.\,0-9 ]/', '', $value); - $len = \strlen($str); + $len = strlen($str); if ('(' === $str[0] && ')' === $str[$len - 1]) { $str = '-' . substr($str, 1, $len - 2); } diff --git a/app/Import/Converter/BankDebitCredit.php b/app/Import/Converter/BankDebitCredit.php index 3e131f4aaf..56ced2c60f 100644 --- a/app/Import/Converter/BankDebitCredit.php +++ b/app/Import/Converter/BankDebitCredit.php @@ -46,10 +46,11 @@ class BankDebitCredit implements ConverterInterface $negative = [ 'D', // Old style Rabobank (NL). Short for "Debit" 'A', // New style Rabobank (NL). Short for "Af" + 'DR', // https://old.reddit.com/r/FireflyIII/comments/bn2edf/generic_debitcredit_indicator/ 'Af', // ING (NL). 'Debet', // Triodos (NL) ]; - if (\in_array(trim($value), $negative, true)) { + if (in_array(trim($value), $negative, true)) { return -1; } diff --git a/app/Import/JobConfiguration/FakeJobConfiguration.php b/app/Import/JobConfiguration/FakeJobConfiguration.php index 6936768b78..b762b5397c 100644 --- a/app/Import/JobConfiguration/FakeJobConfiguration.php +++ b/app/Import/JobConfiguration/FakeJobConfiguration.php @@ -100,8 +100,8 @@ class FakeJobConfiguration implements JobConfigurationInterface $this->repository->setConfiguration($this->importJob, $configuration); $messages = new MessageBag(); - if (3 !== \count($configuration)) { - $messages->add('some_key', 'Ignore this error: ' . \count($configuration)); + if (3 !== count($configuration)) { + $messages->add('some_key', 'Ignore this error: ' . count($configuration)); } return $messages; diff --git a/app/Import/Mapper/AssetAccountIbans.php b/app/Import/Mapper/AssetAccountIbans.php index 8b4956e744..2b8ab198e2 100644 --- a/app/Import/Mapper/AssetAccountIbans.php +++ b/app/Import/Mapper/AssetAccountIbans.php @@ -57,7 +57,7 @@ class AssetAccountIbans implements MapperInterface $name = $account->iban . ' (' . $account->name . ')'; // is a liability? - if (\in_array($account->accountType->type, [AccountType::LOAN, AccountType::DEBT, AccountType::CREDITCARD, AccountType::MORTGAGE], true)) { + if (in_array($account->accountType->type, [AccountType::LOAN, AccountType::DEBT, AccountType::CREDITCARD, AccountType::MORTGAGE], true)) { $name = $name . ' (' . strtolower(trans('import.import_liability_select')) . ')'; } @@ -66,7 +66,7 @@ class AssetAccountIbans implements MapperInterface if ('' === $iban) { $name = $account->name; // is a liability? - if (\in_array($account->accountType->type, [AccountType::LOAN, AccountType::DEBT, AccountType::CREDITCARD, AccountType::MORTGAGE], true)) { + if (in_array($account->accountType->type, [AccountType::LOAN, AccountType::DEBT, AccountType::CREDITCARD, AccountType::MORTGAGE], true)) { $name = $name . ' (' . strtolower(trans('import.import_liability_select')) . ')'; } $list[$accountId] = $name; diff --git a/app/Import/Mapper/AssetAccounts.php b/app/Import/Mapper/AssetAccounts.php index 43dce07ffa..e6b9f44467 100644 --- a/app/Import/Mapper/AssetAccounts.php +++ b/app/Import/Mapper/AssetAccounts.php @@ -55,7 +55,7 @@ class AssetAccounts implements MapperInterface } // is a liability? - if (\in_array($account->accountType->type, [AccountType::LOAN, AccountType::DEBT, AccountType::CREDITCARD, AccountType::MORTGAGE], true)) { + if (in_array($account->accountType->type, [AccountType::LOAN, AccountType::DEBT, AccountType::CREDITCARD, AccountType::MORTGAGE], true)) { $name = trans('import.import_liability_select') . ': ' . $name; } diff --git a/app/Import/Mapper/OpposingAccountIbans.php b/app/Import/Mapper/OpposingAccountIbans.php index 5a03f15b73..6f3217521a 100644 --- a/app/Import/Mapper/OpposingAccountIbans.php +++ b/app/Import/Mapper/OpposingAccountIbans.php @@ -59,7 +59,7 @@ class OpposingAccountIbans implements MapperInterface $name = $account->iban . ' (' . $account->name . ')'; // is a liability? - if (\in_array($account->accountType->type, [AccountType::LOAN, AccountType::DEBT, AccountType::CREDITCARD, AccountType::MORTGAGE], true)) { + if (in_array($account->accountType->type, [AccountType::LOAN, AccountType::DEBT, AccountType::CREDITCARD, AccountType::MORTGAGE], true)) { $name = $name . ' (' . strtolower(trans('import.import_liability_select')) . ')'; } @@ -69,7 +69,7 @@ class OpposingAccountIbans implements MapperInterface if ('' === $iban) { $name = $account->name; // is a liability? - if (\in_array($account->accountType->type, [AccountType::LOAN, AccountType::DEBT, AccountType::CREDITCARD, AccountType::MORTGAGE], true)) { + if (in_array($account->accountType->type, [AccountType::LOAN, AccountType::DEBT, AccountType::CREDITCARD, AccountType::MORTGAGE], true)) { $name = $name . ' (' . strtolower(trans('import.import_liability_select')) . ')'; } $list[$accountId] = $name; diff --git a/app/Import/Mapper/OpposingAccounts.php b/app/Import/Mapper/OpposingAccounts.php index 82680bf6ad..b99193d00e 100644 --- a/app/Import/Mapper/OpposingAccounts.php +++ b/app/Import/Mapper/OpposingAccounts.php @@ -59,7 +59,7 @@ class OpposingAccounts implements MapperInterface $name .= ' (' . $iban . ')'; } // is a liability? - if (\in_array($account->accountType->type, [AccountType::LOAN, AccountType::DEBT, AccountType::CREDITCARD, AccountType::MORTGAGE], true)) { + if (in_array($account->accountType->type, [AccountType::LOAN, AccountType::DEBT, AccountType::CREDITCARD, AccountType::MORTGAGE], true)) { $name = trans('import.import_liability_select') . ': ' . $name; } $list[$accountId] = $name; diff --git a/app/Import/Prerequisites/BunqPrerequisites.php b/app/Import/Prerequisites/BunqPrerequisites.php index 3da4d085a4..3592cfe5f2 100644 --- a/app/Import/Prerequisites/BunqPrerequisites.php +++ b/app/Import/Prerequisites/BunqPrerequisites.php @@ -39,6 +39,16 @@ class BunqPrerequisites implements PrerequisitesInterface /** @var User The current user */ private $user; + /** + * BunqPrerequisites constructor. + */ + public function __construct() + { + if ('testing' === config('app.env')) { + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); + } + } + /** * Returns view name that allows user to fill in prerequisites. * diff --git a/app/Import/Prerequisites/FakePrerequisites.php b/app/Import/Prerequisites/FakePrerequisites.php index 39dbec4762..dbe11a85cc 100644 --- a/app/Import/Prerequisites/FakePrerequisites.php +++ b/app/Import/Prerequisites/FakePrerequisites.php @@ -24,6 +24,7 @@ namespace FireflyIII\Import\Prerequisites; use FireflyIII\User; use Illuminate\Support\MessageBag; +use Log; /** * This class contains all the routines necessary for the fake import provider. @@ -35,6 +36,16 @@ class FakePrerequisites implements PrerequisitesInterface /** @var User The current user */ private $user; + /** + * FakePrerequisites constructor. + */ + public function __construct() + { + if ('testing' === config('app.env')) { + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); + } + } + /** * Returns view name that allows user to fill in prerequisites. Currently asks for the API key. * @@ -97,7 +108,7 @@ class FakePrerequisites implements PrerequisitesInterface { $apiKey = $data['api_key'] ?? ''; $messageBag = new MessageBag(); - if (32 !== \strlen($apiKey)) { + if (32 !== strlen($apiKey)) { $messageBag->add('api_key', 'API key must be 32 chars.'); return $messageBag; @@ -122,7 +133,7 @@ class FakePrerequisites implements PrerequisitesInterface if (null === $apiKey->data) { return false; } - if (32 === \strlen((string)$apiKey->data)) { + if (32 === strlen((string)$apiKey->data)) { return true; } diff --git a/app/Import/Prerequisites/FilePrerequisites.php b/app/Import/Prerequisites/FilePrerequisites.php index 14109b0fb3..459a51ffd7 100644 --- a/app/Import/Prerequisites/FilePrerequisites.php +++ b/app/Import/Prerequisites/FilePrerequisites.php @@ -24,6 +24,7 @@ namespace FireflyIII\Import\Prerequisites; use FireflyIII\User; use Illuminate\Support\MessageBag; +use Log; /** * @@ -33,6 +34,16 @@ use Illuminate\Support\MessageBag; */ class FilePrerequisites implements PrerequisitesInterface { + + /** + * FilePrerequisites constructor. + */ + public function __construct() + { + if ('testing' === config('app.env')) { + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); + } + } /** * Returns view name that allows user to fill in prerequisites. * diff --git a/app/Import/Prerequisites/SpectrePrerequisites.php b/app/Import/Prerequisites/SpectrePrerequisites.php index 8cd4616ae9..997dd5e0d9 100644 --- a/app/Import/Prerequisites/SpectrePrerequisites.php +++ b/app/Import/Prerequisites/SpectrePrerequisites.php @@ -35,6 +35,16 @@ class SpectrePrerequisites implements PrerequisitesInterface /** @var User The current user */ private $user; + /** + * SpectrePrerequisites constructor. + */ + public function __construct() + { + if ('testing' === config('app.env')) { + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); + } + } + /** * Returns view name that allows user to fill in prerequisites. * diff --git a/app/Import/Prerequisites/YnabPrerequisites.php b/app/Import/Prerequisites/YnabPrerequisites.php index 8ad488d7c0..832baf2d3c 100644 --- a/app/Import/Prerequisites/YnabPrerequisites.php +++ b/app/Import/Prerequisites/YnabPrerequisites.php @@ -35,6 +35,16 @@ class YnabPrerequisites implements PrerequisitesInterface /** @var User The current user */ private $user; + /** + * YnabPrerequisites constructor. + */ + public function __construct() + { + if ('testing' === config('app.env')) { + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); + } + } + /** * Returns view name that allows user to fill in prerequisites. * diff --git a/app/Import/Routine/BunqRoutine.php b/app/Import/Routine/BunqRoutine.php index ec4dc6af79..7454940af8 100644 --- a/app/Import/Routine/BunqRoutine.php +++ b/app/Import/Routine/BunqRoutine.php @@ -52,7 +52,7 @@ class BunqRoutine implements RoutineInterface { Log::info(sprintf('Now in BunqRoutine::run() with status "%s" and stage "%s".', $this->importJob->status, $this->importJob->stage)); $valid = ['ready_to_run']; // should be only ready_to_run - if (\in_array($this->importJob->status, $valid, true)) { + if (in_array($this->importJob->status, $valid, true)) { switch ($this->importJob->stage) { default: throw new FireflyException(sprintf('BunqRoutine cannot handle stage "%s".', $this->importJob->stage)); // @codeCoverageIgnore diff --git a/app/Import/Routine/FinTSRoutine.php b/app/Import/Routine/FinTSRoutine.php index 43b099fc5b..b05d32ce1d 100644 --- a/app/Import/Routine/FinTSRoutine.php +++ b/app/Import/Routine/FinTSRoutine.php @@ -52,7 +52,7 @@ class FinTSRoutine implements RoutineInterface { Log::debug(sprintf('Now in FinTSRoutine::run() with status "%s" and stage "%s".', $this->importJob->status, $this->importJob->stage)); $valid = ['ready_to_run']; // should be only ready_to_run - if (\in_array($this->importJob->status, $valid, true)) { + if (in_array($this->importJob->status, $valid, true)) { switch ($this->importJob->stage) { default: throw new FireflyException(sprintf('FinTSRoutine cannot handle stage "%s".', $this->importJob->stage)); // @codeCoverageIgnore @@ -67,8 +67,6 @@ class FinTSRoutine implements RoutineInterface $this->repository->setTransactions($this->importJob, $transactions); $this->repository->setStatus($this->importJob, 'provider_finished'); $this->repository->setStage($this->importJob, 'final'); - - return; } } } diff --git a/app/Import/Routine/SpectreRoutine.php b/app/Import/Routine/SpectreRoutine.php index 5bb5a07b53..2805142ebd 100644 --- a/app/Import/Routine/SpectreRoutine.php +++ b/app/Import/Routine/SpectreRoutine.php @@ -56,7 +56,7 @@ class SpectreRoutine implements RoutineInterface { Log::debug(sprintf('Now in SpectreRoutine::run() with status "%s" and stage "%s".', $this->importJob->status, $this->importJob->stage)); $valid = ['ready_to_run']; // should be only ready_to_run - if (\in_array($this->importJob->status, $valid, true)) { + if (in_array($this->importJob->status, $valid, true)) { switch ($this->importJob->stage) { default: throw new FireflyException(sprintf('SpectreRoutine cannot handle stage "%s".', $this->importJob->stage)); // @codeCoverageIgnore diff --git a/app/Import/Routine/YnabRoutine.php b/app/Import/Routine/YnabRoutine.php index 71ff3fb970..12aaf0fdeb 100644 --- a/app/Import/Routine/YnabRoutine.php +++ b/app/Import/Routine/YnabRoutine.php @@ -54,7 +54,7 @@ class YnabRoutine implements RoutineInterface { Log::debug(sprintf('Now in YNAB routine::run() with status "%s" and stage "%s".', $this->importJob->status, $this->importJob->stage)); $valid = ['ready_to_run']; // should be only ready_to_run - if (\in_array($this->importJob->status, $valid, true)) { + if (in_array($this->importJob->status, $valid, true)) { // get access token from YNAB if ('get_access_token' === $this->importJob->stage) { @@ -83,14 +83,14 @@ class YnabRoutine implements RoutineInterface $budgets = $configuration['budgets'] ?? []; // if more than 1 budget, select budget first. - if (\count($budgets) > 1) { + if (count($budgets) > 1) { $this->repository->setStage($this->importJob, 'select_budgets'); $this->repository->setStatus($this->importJob, 'need_job_config'); return; } - if (1 === \count($budgets)) { + if (1 === count($budgets)) { $this->repository->setStatus($this->importJob, 'ready_to_run'); $this->repository->setStage($this->importJob, 'get_accounts'); } diff --git a/app/Import/Specifics/AbnAmroDescription.php b/app/Import/Specifics/AbnAmroDescription.php index 2c4296a119..7f373d897d 100644 --- a/app/Import/Specifics/AbnAmroDescription.php +++ b/app/Import/Specifics/AbnAmroDescription.php @@ -147,7 +147,7 @@ class AbnAmroDescription implements SpecificInterface // SEPA plain descriptions contain several key-value pairs, split by a colon preg_match_all('/([A-Za-z]+(?=:\s)):\s([A-Za-z 0-9._#-]+(?=\s|$))/', $this->row[7], $matches, PREG_SET_ORDER); - if (\is_array($matches)) { + if (is_array($matches)) { foreach ($matches as $match) { $key = $match[1]; $value = trim($match[2]); @@ -165,7 +165,7 @@ class AbnAmroDescription implements SpecificInterface case 'IBAN': $this->row[9] = $value; break; - default: + default: // @codeCoverageIgnore // Ignore the rest } } @@ -203,7 +203,7 @@ class AbnAmroDescription implements SpecificInterface // Search for properties specified in the TRTP format. If no description // is provided, use the type, name and reference as new description - if (\is_array($matches)) { + if (is_array($matches)) { foreach ($matches as $match) { $key = $match[1]; $value = trim($match[2]); @@ -224,7 +224,7 @@ class AbnAmroDescription implements SpecificInterface case 'TRTP': $type = $value; break; - default: + default: // @codeCoverageIgnore // Ignore the rest } } diff --git a/app/Import/Specifics/Belfius.php b/app/Import/Specifics/Belfius.php new file mode 100644 index 0000000000..714cfb9848 --- /dev/null +++ b/app/Import/Specifics/Belfius.php @@ -0,0 +1,94 @@ + + * + * This file is part of Firefly III. + * + * Firefly III is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Firefly III is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Firefly III. If not, see . + */ +declare(strict_types=1); + +namespace FireflyIII\Import\Specifics; + +/** + * Class Belfius. + * + * Fixes Belfius CSV files to: + * - Correct descriptions for recurring transactions so doubles can be detected when the equivalent incoming + * transaction is imported. + * + */ +class Belfius implements SpecificInterface +{ + /** + * Description of this specific fix. + * + * @return string + * @codeCoverageIgnore + */ + public static function getDescription(): string + { + return 'import.specific_belfius_descr'; + } + + /** + * Name of specific fix. + * + * @return string + * @codeCoverageIgnore + */ + public static function getName(): string + { + return 'import.specific_belfius_name'; + } + + /** + * Run the fix. + * + * @param array $row + * + * @return array + * + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + */ + public function run(array $row): array + { + return Belfius::processRecurringTransactionDescription($row); + } + + /** + * Fixes the description for outgoing recurring transactions so doubles can be detected when the equivalent incoming + * transaction is imported for another bank account. + * + * @return array the row containing the new description + */ + protected static function processRecurringTransactionDescription(array $row): array + { + if (!isset($row[5]) || !isset($row[14])) { + return $row; + } + + $opposingAccountName = $row[5]; + $description = $row[14]; + + preg_match('/DOORLOPENDE OPDRACHT.*\s+' . preg_quote($opposingAccountName, '/') . '\s+(.+)\s+REF.\s*:/', $description, $matches); + + if (isset($matches[1])) { + $row[14] = $matches[1]; + } + + return $row; + } +} diff --git a/app/Import/Specifics/IngBelgium.php b/app/Import/Specifics/IngBelgium.php new file mode 100644 index 0000000000..b77e811696 --- /dev/null +++ b/app/Import/Specifics/IngBelgium.php @@ -0,0 +1,139 @@ + + * + * This file is part of Firefly III. + * + * Firefly III is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Firefly III is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Firefly III. If not, see . + */ +declare(strict_types=1); + +namespace FireflyIII\Import\Specifics; + +/** + * Class IngBelgium. + * + * Parses the description and opposing account information (IBAN and name) from CSV files for ING Belgium bank accounts. + * + */ +class IngBelgium implements SpecificInterface +{ + /** + * Description of the current specific. + * + * @return string + * @codeCoverageIgnore + */ + public static function getDescription(): string + { + return 'import.specific_ingbelgium_descr'; + } + + /** + * Name of the current specific. + * + * @return string + * @codeCoverageIgnore + */ + public static function getName(): string + { + return 'import.specific_ingbelgium_name'; + } + + /** + * Run the specific code. + * + * @param array $row + * + * @return array + * + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + */ + public function run(array $row): array + { + return IngBelgium::processTransactionDetails($row); + } + + /** + * Gets the description and opposing account information (IBAN and name) from the transaction details and adds + * them to the row of data. + * + * @return array the row containing the description and opposing account's IBAN + */ + protected static function processTransactionDetails(array $row): array + { + if(isset($row[9])) { + $transactionDetails = $row[9]; + $row[11] = IngBelgium::opposingAccountName($transactionDetails); + $row[12] = IngBelgium::opposingAccountIban($transactionDetails); + $row[13] = IngBelgium::description($transactionDetails); + } + return $row; + } + + /** + * Gets the opposing account name from the transaction details. + * + * @return string the opposing account name + */ + protected static function opposingAccountName(string $transactionDetails): string + { + return IngBelgium::parseInformationFromTransactionDetails($transactionDetails, '/Van:\s*(.+?)(?=\s{2,})/'); + + } + + /** + * Gets the opposing account's IBAN from the transaction details. + * + * @return string the opposing account's IBAN + */ + protected static function opposingAccountIban(string $transactionDetails): string + { + return IngBelgium::parseInformationFromTransactionDetails($transactionDetails, '/IBAN:\s*(.+?)(?=\s+)/'); + } + + /** + * Gets the description from the transaction details and makes sure structured descriptions are in the + * "+++090/9337/55493+++" format. + * + * @return string the description + */ + protected static function description(string $transactionDetails): string + { + $description = IngBelgium::parseInformationFromTransactionDetails($transactionDetails, '/Mededeling:\s*(.+)$/'); + return IngBelgium::convertStructuredDescriptionToProperFormat($description); + } + + private static function convertStructuredDescriptionToProperFormat(string $description): string + { + preg_match('/^\*\*\*(\d{3}\/\d{4}\/\d{5})\*\*\*$/', $description, $matches); + if(isset($matches[1])) { + return '+++' . $matches[1] . '+++'; + } + return $description; + } + + private static function parseInformationFromTransactionDetails(string $transactionDetails, string $regex): string + { + if(isset($transactionDetails)) { + preg_match($regex, $transactionDetails, $matches); + if (isset($matches[1])) { + return trim($matches[1]); + } + } + + return ''; + } +} diff --git a/app/Import/Specifics/IngDescription.php b/app/Import/Specifics/IngDescription.php index 6980105893..74d142d8d3 100644 --- a/app/Import/Specifics/IngDescription.php +++ b/app/Import/Specifics/IngDescription.php @@ -71,7 +71,7 @@ class IngDescription implements SpecificInterface public function run(array $row): array { $this->row = array_values($row); - if (\count($this->row) >= 8) { // check if the array is correct + if (count($this->row) >= 8) { // check if the array is correct switch ($this->row[4]) { // Get value for the mutation type case 'GT': // InternetBankieren case 'OV': // Overschrijving diff --git a/app/Import/Storage/ImportArrayStorage.php b/app/Import/Storage/ImportArrayStorage.php index 19df205f90..b10c3a9f92 100644 --- a/app/Import/Storage/ImportArrayStorage.php +++ b/app/Import/Storage/ImportArrayStorage.php @@ -26,20 +26,21 @@ namespace FireflyIII\Import\Storage; use Carbon\Carbon; use DB; +use Exception; use FireflyIII\Events\RequestedReportOnJournals; use FireflyIII\Exceptions\FireflyException; -use FireflyIII\Helpers\Collector\TransactionCollectorInterface; -use FireflyIII\Helpers\Filter\InternalTransferFilter; -use FireflyIII\Helpers\Filter\NegativeAmountFilter; -use FireflyIII\Helpers\Filter\PositiveAmountFilter; +use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Models\ImportJob; +use FireflyIII\Models\Preference; use FireflyIII\Models\Rule; -use FireflyIII\Models\Transaction; +use FireflyIII\Models\TransactionGroup; +use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\ImportJob\ImportJobRepositoryInterface; use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use FireflyIII\Repositories\Rule\RuleRepositoryInterface; use FireflyIII\Repositories\Tag\TagRepositoryInterface; +use FireflyIII\Repositories\TransactionGroup\TransactionGroupRepositoryInterface; use FireflyIII\TransactionRules\Processor; use Illuminate\Database\QueryException; use Illuminate\Support\Collection; @@ -54,6 +55,8 @@ use Log; */ class ImportArrayStorage { + /** @var int Number of hits required for a transfer to match. */ + private const REQUIRED_HITS = 4; /** @var bool Check for transfers during import. */ private $checkForTransfers = false; /** @var ImportJob The import job */ @@ -62,8 +65,13 @@ class ImportArrayStorage private $journalRepos; /** @var ImportJobRepositoryInterface Import job repository */ private $repository; - /** @var Collection The transfers the user already has. */ + /** @var array The transfers the user already has. */ private $transfers; + /** @var TransactionGroupRepositoryInterface */ + private $groupRepos; + + /** @var string */ + private $language = 'en_US'; /** * Set job, count transfers in the array and create the repository. @@ -81,9 +89,65 @@ class ImportArrayStorage $this->journalRepos = app(JournalRepositoryInterface::class); $this->journalRepos->setUser($importJob->user); + $this->groupRepos = app(TransactionGroupRepositoryInterface::class); + $this->groupRepos->setUser($importJob->user); + + // get language of user. + /** @var Preference $pref */ + $pref = app('preferences')->getForUser($importJob->user, 'language', config('firefly.default_language', 'en_US')); + $this->language = $pref->data; + Log::debug('Constructed ImportArrayStorage()'); } + /** + * Count the number of transfers in the array. If this is zero, don't bother checking for double transfers. + */ + private function countTransfers(): void + { + Log::debug('Now in countTransfers()'); + /** @var array $array */ + $array = $this->repository->getTransactions($this->importJob); + + + $count = 0; + foreach ($array as $index => $group) { + + foreach ($group['transactions'] as $transaction) { + if (strtolower(TransactionType::TRANSFER) === strtolower($transaction['type'])) { + $count++; + Log::debug(sprintf('Row #%d is a transfer, increase count to %d', $index + 1, $count)); + } + } + } + Log::debug(sprintf('Count of transfers in import array is %d.', $count)); + if ($count > 0) { + $this->checkForTransfers = true; + Log::debug('Will check for duplicate transfers.'); + // get users transfers. Needed for comparison. + $this->getTransfers(); + } + } + + /** + * Get the users transfers, so they can be compared to whatever the user is trying to import. + */ + private function getTransfers(): void + { + Log::debug('Now in getTransfers()'); + app('preferences')->mark(); + + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + + $collector->setUser($this->importJob->user); + $collector + ->setTypes([TransactionType::TRANSFER])->setLimit(10000)->setPage(1) + ->withAccountInformation(); + $this->transfers = $collector->getExtractedJournals(); + Log::debug(sprintf('Count of getTransfers() is %d', count($this->transfers))); + } + /** * Actually does the storing. Does three things. * - Store journals @@ -97,7 +161,7 @@ class ImportArrayStorage { // store transactions $this->setStatus('storing_data'); - $collection = $this->storeArray(); + $collection = $this->storeGroupArray(); $this->setStatus('stored_data'); // link tag: @@ -122,87 +186,127 @@ class ImportArrayStorage } /** - * Applies the users rules to the created journals. - * - * @param Collection $collection + * Shorthand method to quickly set job status * + * @param string $status */ - private function applyRules(Collection $collection): void + private function setStatus(string $status): void { - $rules = $this->getRules(); - if ($rules->count() > 0) { - foreach ($collection as $journal) { - $rules->each( - function (Rule $rule) use ($journal) { - Log::debug(sprintf('Going to apply rule #%d to journal %d.', $rule->id, $journal->id)); - /** @var Processor $processor */ - $processor = app(Processor::class); - $processor->make($rule); - $processor->handleTransactionJournal($journal); - $journal->refresh(); - if ($rule->stop_processing) { - return false; - } - - return true; - } - ); - } - } + $this->repository->setStatus($this->importJob, $status); } /** - * Count the number of transfers in the array. If this is zero, don't bother checking for double transfers. + * Store array as journals. + * + * @return Collection + * @throws FireflyException + * + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ - private function countTransfers(): void + private function storeGroupArray(): Collection { - Log::debug('Now in countTransfers()'); /** @var array $array */ $array = $this->repository->getTransactions($this->importJob); + $count = count($array); + Log::notice(sprintf('Will now store the groups. Count of groups is %d.', $count)); + Log::notice('Going to store...'); - $count = 0; - foreach ($array as $index => $transaction) { - if (strtolower(TransactionType::TRANSFER) === strtolower($transaction['type'])) { - $count++; - Log::debug(sprintf('Row #%d is a transfer, increase count to %d', $index + 1, $count)); + $collection = new Collection; + foreach ($array as $index => $group) { + Log::debug(sprintf('Now store #%d', $index + 1)); + $result = $this->storeGroup($index, $group); + if (null !== $result) { + $collection->push($result); } } - Log::debug(sprintf('Count of transfers in import array is %d.', $count)); - if ($count > 0) { - $this->checkForTransfers = true; - Log::debug('Will check for duplicate transfers.'); - // get users transfers. Needed for comparison. - $this->getTransfers(); - } + Log::notice(sprintf('Done storing. Firefly III has stored %d transactions.', $collection->count())); + + return $collection; } /** - * @param int $index - * @param array $transaction - * - * @return bool - * @throws FireflyException + * @param int $index + * @param array $group + * @return TransactionGroup|null */ - private function duplicateDetected(int $index, array $transaction): bool + private function storeGroup(int $index, array $group): ?TransactionGroup { - $hash = $this->getHash($transaction); - $existingId = $this->hashExists($hash); - if (null !== $existingId) { - $message = sprintf('Row #%d ("%s") could not be imported. It already exists.', $index, $transaction['description']); - $this->logDuplicateObject($transaction, $existingId); - $this->repository->addErrorMessage($this->importJob, $message); - return true; + + + Log::debug(sprintf('Going to store entry #%d', $index + 1)); + + // do some basic error catching. + foreach ($group['transactions'] as $groupIndex => $transaction) { + $group['transactions'][$groupIndex]['date'] = Carbon::parse($transaction['date'], config('app.timezone')); + $group['transactions'][$groupIndex]['description'] = '' === $transaction['description'] ? '(empty description)' : $transaction['description']; } - // do transfer detection: - if ($this->checkForTransfers && $this->transferExists($transaction)) { - $message = sprintf('Row #%d ("%s") could not be imported. Such a transfer already exists.', $index, $transaction['description']); - $this->logDuplicateTransfer($transaction); - $this->repository->addErrorMessage($this->importJob, $message); + // do duplicate detection! + if ($this->duplicateDetected($index, $group)) { + Log::warning(sprintf('Row #%d seems to be a imported already and will be ignored.', $index)); - return true; + return null; + } + + // store the group + try { + $newGroup = $this->groupRepos->store($group); + // @codeCoverageIgnoreStart + } catch (FireflyException $e) { + Log::error($e->getMessage()); + Log::error($e->getTraceAsString()); + $this->repository->addErrorMessage($this->importJob, sprintf('Row #%d could not be imported. %s', $index, $e->getMessage())); + + return null; + } + // @codeCoverageIgnoreEnd + Log::debug(sprintf('Stored as group #%d', $newGroup->id)); + + // add to collection of transfers, if necessary: + if ('transfer' === strtolower($group['transactions'][0]['type'])) { + $journals = $this->getTransactionFromJournal($newGroup); + Log::debug('We just stored a transfer, so add the journal to the list of transfers.'); + foreach ($journals as $newJournal) { + $this->transfers[] = $newJournal; + } + Log::debug(sprintf('List length is now %d', count($this->transfers))); + } + + return $newGroup; + } + + /** + * @param int $index + * @param array $group + * + * @return bool + */ + private function duplicateDetected(int $index, array $group): bool + { + Log::debug(sprintf('Now in duplicateDetected(%d)', $index)); + $transactions = $group['transactions'] ?? []; + foreach ($transactions as $transaction) { + $hash = $this->getHash($transaction); + $existingId = $this->hashExists($hash); + if (null !== $existingId) { + $message = (string)trans('import.duplicate_row', ['row' => $index, 'description' => $transaction['description']]); + $this->logDuplicateObject($transaction, $existingId); + $this->repository->addErrorMessage($this->importJob, $message); + + return true; + } + + // do transfer detection: + if ($this->checkForTransfers && $this->transferExists($transaction)) { + $message = (string)trans('import.duplicate_row', ['row' => $index, 'description' => $transaction['description']]); + $this->logDuplicateTransfer($transaction); + $this->repository->addErrorMessage($this->importJob, $message); + + return true; + } } return false; @@ -213,18 +317,22 @@ class ImportArrayStorage * * @param array $transaction * - * @throws FireflyException * @return string */ private function getHash(array $transaction): string { - unset($transaction['importHashV2'], $transaction['original-source']); + unset($transaction['import_hash_v2'], $transaction['original_source']); $json = json_encode($transaction); if (false === $json) { // @codeCoverageIgnoreStart /** @noinspection ForgottenDebugOutputInspection */ - Log::error('Could not encode import array.', print_r($transaction, true)); - throw new FireflyException('Could not encode import array. Please see the logs.'); + Log::error('Could not encode import array.', $transaction); + try { + $json = random_int(1, 10000); + } catch (Exception $e) { + // seriously? + Log::error(sprintf('random_int() just failed. I want a medal: %s', $e->getMessage())); + } // @codeCoverageIgnoreEnd } $hash = hash('sha256', $json); @@ -233,72 +341,6 @@ class ImportArrayStorage return $hash; } - /** - * Gets the users rules. - * - * @return Collection - */ - private function getRules(): Collection - { - /** @var RuleRepositoryInterface $repository */ - $repository = app(RuleRepositoryInterface::class); - $repository->setUser($this->importJob->user); - $set = $repository->getForImport(); - - Log::debug(sprintf('Found %d user rules.', $set->count())); - - return $set; - } - - /** - * @param $journal - * - * @return Transaction - */ - private function getTransactionFromJournal($journal): Transaction - { - // collect transactions using the journal collector - $collector = app(TransactionCollectorInterface::class); - $collector->setUser($this->importJob->user); - $collector->withOpposingAccount(); - // filter on specific journals. - $collector->setJournals(new Collection([$journal])); - - // add filter to remove transactions: - $transactionType = $journal->transactionType->type; - if ($transactionType === TransactionType::WITHDRAWAL) { - $collector->addFilter(PositiveAmountFilter::class); - } - if (!($transactionType === TransactionType::WITHDRAWAL)) { - $collector->addFilter(NegativeAmountFilter::class); - } - /** @var Transaction $result */ - $result = $collector->getTransactions()->first(); - Log::debug(sprintf('Return transaction #%d with journal id #%d based on ID #%d', $result->id, $result->journal_id, $journal->id)); - - return $result; - } - - /** - * Get the users transfers, so they can be compared to whatever the user is trying to import. - */ - private function getTransfers(): void - { - Log::debug('Now in getTransfers()'); - app('preferences')->mark(); - - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setUser($this->importJob->user); - $collector->setAllAssetAccounts() - ->ignoreCache() - ->setTypes([TransactionType::TRANSFER]) - ->withOpposingAccount(); - $collector->removeFilter(InternalTransferFilter::class); - $this->transfers = $collector->getTransactions(); - Log::debug(sprintf('Count of getTransfers() is %d', $this->transfers->count())); - } - /** * Check if the hash exists for the array the user wants to import. * @@ -319,6 +361,192 @@ class ImportArrayStorage return (int)$entry->transaction_journal_id; } + /** + * Log about a duplicate object (double hash). + * + * @param array $transaction + * @param int $existingId + */ + private function logDuplicateObject(array $transaction, int $existingId): void + { + Log::info( + 'Transaction is a duplicate, and will not be imported (the hash exists).', + [ + 'existing' => $existingId, + 'description' => $transaction['description'] ?? '', + 'amount' => $transaction['transactions'][0]['amount'] ?? 0, + 'date' => $transaction['date'] ?? '', + ] + ); + + } + + /** + * Check if a transfer exists. + * + * @param array $transaction + * + * @return bool + * + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + * @SuppressWarnings(PHPMD.NPathComplexity) + */ + private function transferExists(array $transaction): bool + { + Log::debug('transferExists() Check if transaction is a double transfer.'); + + // how many hits do we need? + Log::debug(sprintf('System has %d existing transfers', count($this->transfers))); + // loop over each split: + + // check if is a transfer + if (strtolower(TransactionType::TRANSFER) !== strtolower($transaction['type'])) { + // @codeCoverageIgnoreStart + Log::debug(sprintf('Is a %s, not a transfer so no.', $transaction['type'])); + + return false; + // @codeCoverageIgnoreEnd + } + + + Log::debug(sprintf('Required hits for transfer comparison is %d', self::REQUIRED_HITS)); + + // get the amount: + /** @noinspection UnnecessaryCastingInspection */ + $amount = (string)($transaction['amount'] ?? '0'); + if (bccomp($amount, '0') === -1) { + $amount = bcmul($amount, '-1'); // @codeCoverageIgnore + } + + // get the description: + //$description = '' === (string)$transaction['description'] ? $transaction['description'] : $transaction['description']; + $description = (string)$transaction['description']; + + // get the source and destination ID's: + $transactionSourceIDs = [(int)$transaction['source_id'], (int)$transaction['destination_id']]; + sort($transactionSourceIDs); + + // get the source and destination names: + $transactionSourceNames = [(string)$transaction['source_name'], (string)$transaction['destination_name']]; + sort($transactionSourceNames); + + // then loop all transfers: + /** @var array $transfer */ + foreach ($this->transfers as $transfer) { + // number of hits for this split-transfer combination: + $hits = 0; + Log::debug(sprintf('Now looking at transaction journal #%d', $transfer['transaction_journal_id'])); + // compare amount: + $originalAmount = app('steam')->positive($transfer['amount']); + Log::debug(sprintf('Amount %s compared to %s', $amount, $originalAmount)); + if (0 !== bccomp($amount, $originalAmount)) { + Log::debug('Amount is not a match, continue with next transfer.'); + continue; + } + ++$hits; + Log::debug(sprintf('Comparison is a hit! (%s)', $hits)); + + // compare description: + $comparison = '(empty description)' === $transfer['description'] ? '' : $transfer['description']; + Log::debug(sprintf('Comparing "%s" to "%s" (original: "%s")', $description, $transfer['description'], $comparison)); + if ($description !== $comparison) { + Log::debug('Description is not a match, continue with next transfer.'); + continue; // @codeCoverageIgnore + } + ++$hits; + Log::debug(sprintf('Comparison is a hit! (%s)', $hits)); + + // compare date: + $transferDate = $transfer['date']->format('Y-m-d H:i:s'); + $transactionDate = $transaction['date']->format('Y-m-d H:i:s'); + Log::debug(sprintf('Comparing dates "%s" to "%s"', $transactionDate, $transferDate)); + if ($transactionDate !== $transferDate) { + Log::debug('Date is not a match, continue with next transfer.'); + continue; // @codeCoverageIgnore + } + ++$hits; + Log::debug(sprintf('Comparison is a hit! (%s)', $hits)); + + // compare source and destination id's + $transferSourceIDs = [(int)$transfer['source_account_id'], (int)$transfer['destination_account_id']]; + sort($transferSourceIDs); + /** @noinspection DisconnectedForeachInstructionInspection */ + Log::debug('Comparing current transaction source+dest IDs', $transactionSourceIDs); + Log::debug('.. with current transfer source+dest IDs', $transferSourceIDs); + if ($transactionSourceIDs === $transferSourceIDs) { + ++$hits; + Log::debug(sprintf('Source IDs are the same! (%d)', $hits)); + } + if ($transactionSourceIDs !== $transferSourceIDs) { + Log::debug('Source IDs are not the same.'); + } + unset($transferSourceIDs); + + // compare source and destination names + $transferSource = [(string)($transfer['source_account_name'] ?? ''), (string)($transfer['destination_account_name'] ?? '')]; + sort($transferSource); + /** @noinspection DisconnectedForeachInstructionInspection */ + Log::debug('Comparing current transaction source+dest names', $transactionSourceNames); + Log::debug('.. with current transfer source+dest names', $transferSource); + if ($transactionSourceNames === $transferSource) { + // @codeCoverageIgnoreStart + ++$hits; + Log::debug(sprintf('Source names are the same! (%d)', $hits)); + // @codeCoverageIgnoreEnd + } + if ($transactionSourceNames !== $transferSource) { + Log::debug('Source names are not the same.'); + } + + Log::debug(sprintf('Number of hits is %d', $hits)); + if ($hits >= self::REQUIRED_HITS) { + Log::debug(sprintf('Is more than %d, return true.', self::REQUIRED_HITS)); + + return true; + } + } + Log::debug('Is not an existing transfer, return false.'); + + return false; + } + + /** + * Log about a duplicate transfer. + * + * @param array $transaction + */ + private function logDuplicateTransfer(array $transaction): void + { + Log::info( + 'Transaction is a duplicate transfer, and will not be imported (such a transfer exists already).', + [ + 'description' => $transaction['description'] ?? '', + 'amount' => $transaction['transactions'][0]['amount'] ?? 0, + 'date' => $transaction['date'] ?? '', + ] + ); + } + + /** + * @param TransactionGroup $transactionGroup + * + * @return array + */ + private function getTransactionFromJournal(TransactionGroup $transactionGroup): array + { + // collect transactions using the journal collector + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + + $collector->setUser($this->importJob->user); + $collector->setGroup($transactionGroup); + + $result = $collector->getExtractedJournals(); + + return $result; + } + /** * Link all imported journals to a tag. * @@ -344,263 +572,82 @@ class ImportArrayStorage $tag = $repository->store($data); Log::debug(sprintf('Created tag #%d ("%s")', $tag->id, $tag->tag)); - Log::debug('Looping journals...'); - $journalIds = $collection->pluck('id')->toArray(); - $tagId = $tag->id; - foreach ($journalIds as $journalId) { - Log::debug(sprintf('Linking journal #%d to tag #%d...', $journalId, $tagId)); - try { - DB::table('tag_transaction_journal')->insert(['transaction_journal_id' => $journalId, 'tag_id' => $tagId]); - } catch (QueryException $e) { - Log::error(sprintf('Could not link journal #%d to tag #%d because: %s', $journalId, $tagId, $e->getMessage())); - Log::error($e->getTraceAsString()); - } - } - Log::info(sprintf('Linked %d journals to tag #%d ("%s")', $collection->count(), $tag->id, $tag->tag)); + Log::debug('Looping groups...'); + // TODO double loop. + + /** @var TransactionGroup $group */ + foreach ($collection as $group) { + Log::debug(sprintf('Looping journals in group #%d', $group->id)); + /** @var TransactionJournal $journal */ + $journalIds = $group->transactionJournals->pluck('id')->toArray(); + $tagId = $tag->id; + foreach ($journalIds as $journalId) { + Log::debug(sprintf('Linking journal #%d to tag #%d...', $journalId, $tagId)); + // @codeCoverageIgnoreStart + try { + DB::table('tag_transaction_journal')->insert(['transaction_journal_id' => $journalId, 'tag_id' => $tagId]); + } catch (QueryException $e) { + Log::error(sprintf('Could not link journal #%d to tag #%d because: %s', $journalId, $tagId, $e->getMessage())); + Log::error($e->getTraceAsString()); + } + // @codeCoverageIgnoreEnd + } + Log::info(sprintf('Linked %d journals to tag #%d ("%s")', $collection->count(), $tag->id, $tag->tag)); + } $this->repository->setTag($this->importJob, $tag); } /** - * Log about a duplicate object (double hash). + * Applies the users rules to the created journals. + * + * TODO this piece of code must be replaced with the rule engine for consistent processing. + * TODO double for-each is terrible. + * @param Collection $collection * - * @param array $transaction - * @param int $existingId */ - private function logDuplicateObject(array $transaction, int $existingId): void + private function applyRules(Collection $collection): void { - Log::info( - 'Transaction is a duplicate, and will not be imported (the hash exists).', - [ - 'existing' => $existingId, - 'description' => $transaction['description'] ?? '', - 'amount' => $transaction['transactions'][0]['amount'] ?? 0, - 'date' => $transaction['date'] ?? '', - ] - ); - + $rules = $this->getRules(); + if ($rules->count() > 0) { + /** @var TransactionGroup $group */ + foreach ($collection as $group) { + $rules->each( + static function (Rule $rule) use ($group) { + Log::debug(sprintf('Going to apply rule #%d to group %d.', $rule->id, $group->id)); + foreach ($group->transactionJournals as $journal) { + /** @var Processor $processor */ + $processor = app(Processor::class); + $processor->make($rule); + $processor->handleTransactionJournal($journal); + $journal->refresh(); + if ($rule->stop_processing) { + return false; // @codeCoverageIgnore + } + } + return true; + } + ); + } + } } /** - * Log about a duplicate transfer. - * - * @param array $transaction - */ - private function logDuplicateTransfer(array $transaction): void - { - Log::info( - 'Transaction is a duplicate transfer, and will not be imported (such a transfer exists already).', - [ - 'description' => $transaction['description'] ?? '', - 'amount' => $transaction['transactions'][0]['amount'] ?? 0, - 'date' => $transaction['date'] ?? '', - ] - ); - } - - /** - * Shorthand method to quickly set job status - * - * @param string $status - */ - private function setStatus(string $status): void - { - $this->repository->setStatus($this->importJob, $status); - } - - /** - * Store array as journals. + * Gets the users rules. * * @return Collection - * @throws FireflyException - * - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ - private function storeArray(): Collection + private function getRules(): Collection { - /** @var array $array */ - $array = $this->repository->getTransactions($this->importJob); - $count = \count($array); - $toStore = []; + /** @var RuleRepositoryInterface $repository */ + $repository = app(RuleRepositoryInterface::class); + $repository->setUser($this->importJob->user); + $set = $repository->getForImport(); - Log::notice(sprintf('Will now store the transactions. Count of items is %d.', $count)); + Log::debug(sprintf('Found %d user rules.', $set->count())); - /* - * Detect duplicates in initial array: - */ - foreach ($array as $index => $transaction) { - Log::debug(sprintf('Now at item %d out of %d', $index + 1, $count)); - if ($this->duplicateDetected($index, $transaction)) { - Log::warning(sprintf('Row #%d seems to be a duplicate entry and will be ignored.', $index)); - continue; - } - $transaction['importHashV2'] = $this->getHash($transaction); - $toStore[] = $transaction; - } - $count = \count($toStore); - if (0 === $count) { - Log::info('No transactions to store left!'); - - return new Collection; - } - Log::notice(sprintf('After a first check for duplicates, the count of items is %d.', $count)); - Log::notice('Going to store...'); - // now actually store them: - $collection = new Collection; - foreach ($toStore as $index => $store) { - // do duplicate detection again! - if ($this->duplicateDetected($index, $store)) { - Log::warning(sprintf('Row #%d seems to be a imported already and will be ignored.', $index), $store); - continue; - } - - Log::debug(sprintf('Going to store entry %d of %d', $index + 1, $count)); - // convert the date to an object: - $store['date'] = Carbon::parse($store['date'], config('app.timezone')); - $store['description'] = '' === $store['description'] ? '(empty description)' : $store['description']; - // store the journal. - try { - $journal = $this->journalRepos->store($store); - } catch (FireflyException $e) { - Log::error($e->getMessage()); - Log::error($e->getTraceAsString()); - $this->repository->addErrorMessage($this->importJob, sprintf('Row #%d could not be imported. %s', $index, $e->getMessage())); - continue; - } - - Log::info(sprintf('Stored #%d: "%s" (ID #%d)', $index, $journal->description, $journal->id)); - Log::debug(sprintf('Stored as journal #%d', $journal->id)); - $collection->push($journal); - - // add to collection of transfers, if necessary: - if ('transfer' === strtolower($store['type'])) { - $transaction = $this->getTransactionFromJournal($journal); - Log::debug('We just stored a transfer, so add the journal to the list of transfers.'); - $this->transfers->push($transaction); - Log::debug(sprintf('List length is now %d', $this->transfers->count())); - } - } - Log::notice(sprintf('Done storing. Firefly III has stored %d transactions.', $collection->count())); - - return $collection; - } - - /** - * Check if a transfer exists. - * - * @param $transaction - * - * @return bool - * - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - * @SuppressWarnings(PHPMD.NPathComplexity) - */ - private function transferExists(array $transaction): bool - { - Log::debug('Check if is a double transfer.'); - if (strtolower(TransactionType::TRANSFER) !== strtolower($transaction['type'])) { - Log::debug(sprintf('Is a %s, not a transfer so no.', $transaction['type'])); - - return false; - } - // how many hits do we need? - $requiredHits = \count($transaction['transactions']) * 4; - $totalHits = 0; - Log::debug(sprintf('Required hits for transfer comparison is %d', $requiredHits)); - Log::debug(sprintf('Array has %d transactions.', \count($transaction['transactions']))); - Log::debug(sprintf('System has %d existing transfers', \count($this->transfers))); - // loop over each split: - foreach ($transaction['transactions'] as $current) { - - // get the amount: - /** @noinspection UnnecessaryCastingInspection */ - $amount = (string)($current['amount'] ?? '0'); - if (bccomp($amount, '0') === -1) { - $amount = bcmul($amount, '-1'); // @codeCoverageIgnore - } - - // get the description: - $description = '' === (string)$current['description'] ? $transaction['description'] : $current['description']; - - // get the source and destination ID's: - $currentSourceIDs = [(int)$current['source_id'], (int)$current['destination_id']]; - sort($currentSourceIDs); - - // get the source and destination names: - $currentSourceNames = [(string)$current['source_name'], (string)$current['destination_name']]; - sort($currentSourceNames); - - // then loop all transfers: - /** @var Transaction $transfer */ - foreach ($this->transfers as $transfer) { - // number of hits for this split-transfer combination: - $hits = 0; - Log::debug(sprintf('Now looking at transaction journal #%d', $transfer->journal_id)); - // compare amount: - Log::debug(sprintf('Amount %s compared to %s', $amount, $transfer->transaction_amount)); - if (0 !== bccomp($amount, $transfer->transaction_amount)) { - continue; - } - ++$hits; - Log::debug(sprintf('Comparison is a hit! (%s)', $hits)); - - // compare description: - $comparison = '(empty description)' === $transfer->description ? '' : $transfer->description; - Log::debug(sprintf('Comparing "%s" to "%s" (original: "%s")', $description, $transfer->description, $comparison)); - if ($description !== $comparison) { - continue; // @codeCoverageIgnore - } - ++$hits; - Log::debug(sprintf('Comparison is a hit! (%s)', $hits)); - - // compare date: - $transferDate = $transfer->date->format('Y-m-d H:i:s'); - Log::debug(sprintf('Comparing dates "%s" to "%s"', $transaction['date'], $transferDate)); - if ($transaction['date'] !== $transferDate) { - continue; // @codeCoverageIgnore - } - ++$hits; - Log::debug(sprintf('Comparison is a hit! (%s)', $hits)); - - // compare source and destination id's - $transferSourceIDs = [(int)$transfer->account_id, (int)$transfer->opposing_account_id]; - sort($transferSourceIDs); - /** @noinspection DisconnectedForeachInstructionInspection */ - Log::debug('Comparing current transaction source+dest IDs', $currentSourceIDs); - Log::debug('.. with current transfer source+dest IDs', $transferSourceIDs); - if ($currentSourceIDs === $transferSourceIDs) { - ++$hits; - Log::debug(sprintf('Source IDs are the same! (%d)', $hits)); - } - Log::debug('Source IDs are not the same.'); - unset($transferSourceIDs); - - // compare source and destination names - $transferSource = [(string)$transfer->account_name, (string)$transfer->opposing_account_name]; - sort($transferSource); - /** @noinspection DisconnectedForeachInstructionInspection */ - Log::debug('Comparing current transaction source+dest names', $currentSourceNames); - Log::debug('.. with current transfer source+dest names', $transferSource); - if ($currentSourceNames === $transferSource) { - // @codeCoverageIgnoreStart - ++$hits; - Log::debug(sprintf('Source names are the same! (%d)', $hits)); - // @codeCoverageIgnoreEnd - } - Log::debug('Source names are not the same.'); - $totalHits += $hits; - Log::debug(sprintf('Total hits is now %d, hits is %d', $totalHits, $hits)); - if ($totalHits >= $requiredHits) { - return true; - } - } - } - Log::debug(sprintf('Total hits: %d, required: %d', $totalHits, $requiredHits)); - - return $totalHits >= $requiredHits; + return $set; } } diff --git a/app/Jobs/CreateRecurringTransactions.php b/app/Jobs/CreateRecurringTransactions.php index 36b72b12fb..e77c04ee40 100644 --- a/app/Jobs/CreateRecurringTransactions.php +++ b/app/Jobs/CreateRecurringTransactions.php @@ -47,20 +47,16 @@ namespace FireflyIII\Jobs; use Carbon\Carbon; use FireflyIII\Events\RequestedReportOnJournals; -use FireflyIII\Events\StoredTransactionJournal; +use FireflyIII\Events\StoredTransactionGroup; use FireflyIII\Factory\PiggyBankEventFactory; -use FireflyIII\Factory\PiggyBankFactory; use FireflyIII\Models\Recurrence; -use FireflyIII\Models\RecurrenceMeta; use FireflyIII\Models\RecurrenceRepetition; use FireflyIII\Models\RecurrenceTransaction; -use FireflyIII\Models\Rule; +use FireflyIII\Models\TransactionGroup; use FireflyIII\Models\TransactionJournal; use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use FireflyIII\Repositories\Recurring\RecurringRepositoryInterface; -use FireflyIII\Repositories\Rule\RuleRepositoryInterface; -use FireflyIII\TransactionRules\Processor; -use FireflyIII\User; +use FireflyIII\Repositories\TransactionGroup\TransactionGroupRepositoryInterface; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; @@ -71,8 +67,8 @@ use Log; /** * Class CreateRecurringTransactions. - * * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * */ class CreateRecurringTransactions implements ShouldQueue { @@ -82,44 +78,74 @@ class CreateRecurringTransactions implements ShouldQueue private $date; /** @var JournalRepositoryInterface Journal repository */ private $journalRepository; + /** @var TransactionGroupRepositoryInterface */ + private $groupRepository; /** @var RecurringRepositoryInterface Recurring transactions repository. */ private $repository; - /** @var array The users rules. */ - private $rules = []; + /** @var bool Force the transaction to be created no matter what. */ + private $force; + + /** @var int Number of recurrences submitted */ + public $submitted; + /** @var int Number of recurrences actually fired */ + public $executed; + /** @var int Transaction groups created */ + public $created; + + /** + * @param Carbon $date + */ + public function setDate(Carbon $date): void + { + $this->date = $date; + } /** * Create a new job instance. + * @codeCoverageIgnore * * @param Carbon $date */ - public function __construct(Carbon $date) + public function __construct(?Carbon $date) { - $date->startOfDay(); - $this->date = $date; + if (null !== $date) { + $date->startOfDay(); + $this->date = $date; + } $this->repository = app(RecurringRepositoryInterface::class); $this->journalRepository = app(JournalRepositoryInterface::class); + $this->groupRepository = app(TransactionGroupRepositoryInterface::class); + $this->force = false; + $this->submitted = 0; + $this->executed = 0; + $this->created = 0; + + Log::debug(sprintf('Created new CreateRecurringTransactions("%s")', $this->date->format('Y-m-d'))); } + /** + * @param bool $force + */ + public function setForce(bool $force): void + { + $this->force = $force; + } + /** * Execute the job. - * - * @throws \FireflyIII\Exceptions\FireflyException */ public function handle(): void { Log::debug(sprintf('Now at start of CreateRecurringTransactions() job for %s.', $this->date->format('D d M Y'))); - $recurrences = $this->repository->getAll(); - $result = []; - Log::debug(sprintf('Count of collection is %d', $recurrences->count())); + $recurrences = $this->repository->getAll(); + $result = []; + $count = $recurrences->count(); + $this->submitted = $count; + Log::debug(sprintf('Count of collection is %d', $count)); - /** @var Collection $filtered */ - $filtered = $recurrences->filter( - function (Recurrence $recurrence) { - return $this->validRecurrence($recurrence); - - } - ); + // filter recurrences: + $filtered = $this->filterRecurrences($recurrences); Log::debug(sprintf('Left after filtering is %d', $filtered->count())); /** @var Recurrence $recurrence */ foreach ($filtered as $recurrence) { @@ -128,13 +154,12 @@ class CreateRecurringTransactions implements ShouldQueue } $this->repository->setUser($recurrence->user); $this->journalRepository->setUser($recurrence->user); + $this->groupRepository->setUser($recurrence->user); Log::debug(sprintf('Now at recurrence #%d', $recurrence->id)); $created = $this->handleRepetitions($recurrence); Log::debug(sprintf('Done with recurrence #%d', $recurrence->id)); $result[$recurrence->user_id] = $result[$recurrence->user_id]->merge($created); - - // apply rules: - $this->applyRules($recurrence->user, $created); + $this->executed++; } Log::debug('Now running report thing.'); @@ -161,40 +186,6 @@ class CreateRecurringTransactions implements ShouldQueue return $recurrence->active; } - /** - * Apply the users rules to newly created journals. - * - * @param User $user - * @param Collection $journals - * - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - */ - private function applyRules(User $user, Collection $journals): void - { - $userId = $user->id; - if (!isset($this->rules[$userId])) { - $this->rules[$userId] = $this->getRules($user); - } - // run the rules: - if ($this->rules[$userId]->count() > 0) { - /** @var TransactionJournal $journal */ - foreach ($journals as $journal) { - $this->rules[$userId]->each( - function (Rule $rule) use ($journal) { - Log::debug(sprintf('Going to apply rule #%d to journal %d.', $rule->id, $journal->id)); - /** @var Processor $processor */ - $processor = app(Processor::class); - $processor->make($rule); - $processor->handleTransactionJournal($journal); - if ($rule->stop_processing) { - return; - } - } - ); - } - } - } - /** * Helper function for debug information. * @@ -212,43 +203,6 @@ class CreateRecurringTransactions implements ShouldQueue return $return; } - /** - * @param Recurrence $recurrence - * - * @return int - */ - private function getPiggyId(Recurrence $recurrence): int - { - $meta = $recurrence->recurrenceMeta; - /** @var RecurrenceMeta $metaEntry */ - foreach ($meta as $metaEntry) { - if ('piggy_bank_id' === $metaEntry->name) { - return (int)$metaEntry->value; - } - } - - return 0; - } - - /** - * Get the users rules. - * - * @param User $user - * - * @return Collection - */ - private function getRules(User $user): Collection - { - /** @var RuleRepositoryInterface $repository */ - $repository = app(RuleRepositoryInterface::class); - $repository->setUser($user); - $set = $repository->getForImport(); - - Log::debug(sprintf('Found %d user rules.', $set->count())); - - return $set; - } - /** * Get the start date of a recurrence. * @@ -270,19 +224,24 @@ class CreateRecurringTransactions implements ShouldQueue * Get transaction information from a recurring transaction. * * @param Recurrence $recurrence + * @param Carbon $date * * @return array + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ - private function getTransactionData(Recurrence $recurrence): array + private function getTransactionData(Recurrence $recurrence, Carbon $date): array { $transactions = $recurrence->recurrenceTransactions()->get(); $return = []; /** @var RecurrenceTransaction $transaction */ foreach ($transactions as $index => $transaction) { $single = [ + 'type' => strtolower($recurrence->transactionType->type), + 'date' => $date, + 'user' => $recurrence->user_id, 'currency_id' => (int)$transaction->transaction_currency_id, 'currency_code' => null, - 'description' => null, + 'description' => $recurrence->recurrenceTransactions()->first()->description, 'amount' => $transaction->amount, 'budget_id' => $this->repository->getBudget($transaction), 'budget_name' => null, @@ -297,6 +256,14 @@ class CreateRecurringTransactions implements ShouldQueue 'foreign_amount' => $transaction->foreign_amount, 'reconciled' => false, 'identifier' => $index, + 'recurrence_id' => (int)$recurrence->id, + 'order' => $index, + 'notes' => (string)trans('firefly.created_from_recurrence', ['id' => $recurrence->id, 'title' => $recurrence->title]), + 'tags' => $this->repository->getTags($recurrence), + 'piggy_bank_id' => null, + 'piggy_bank_name' => null, + 'bill_id' => null, + 'bill_name' => null, ]; $return[] = $single; } @@ -308,82 +275,83 @@ class CreateRecurringTransactions implements ShouldQueue * Check if the occurences should be executed. * * @param Recurrence $recurrence - * @param array $occurrences + * @param array $occurrences * * @return Collection - * @throws \FireflyIII\Exceptions\FireflyException - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ private function handleOccurrences(Recurrence $recurrence, array $occurrences): Collection { $collection = new Collection; /** @var Carbon $date */ foreach ($occurrences as $date) { - Log::debug(sprintf('Now at date %s.', $date->format('Y-m-d'))); - if ($date->ne($this->date)) { - Log::debug(sprintf('%s is not not today (%s)', $date->format('Y-m-d'), $this->date->format('Y-m-d'))); - - continue; + $result = $this->handleOccurrence($recurrence, $date); + if (null !== $result) { + $collection->push($result); } - Log::debug(sprintf('%s IS today (%s)', $date->format('Y-m-d'), $this->date->format('Y-m-d'))); - - // count created journals on THIS day. - $journalCount = $this->repository->getJournalCount($recurrence, $date, $date); - if ($journalCount > 0) { - Log::info(sprintf('Already created %d journal(s) for date %s', $journalCount, $date->format('Y-m-d'))); - continue; - } - - // create transaction array and send to factory. - $array = [ - 'type' => $recurrence->transactionType->type, - 'date' => $date, - 'tags' => $this->repository->getTags($recurrence), - 'user' => $recurrence->user_id, - 'notes' => (string)trans('firefly.created_from_recurrence', ['id' => $recurrence->id, 'title' => $recurrence->title]), - // journal data: - 'description' => $recurrence->recurrenceTransactions()->first()->description, - 'piggy_bank_id' => null, - 'piggy_bank_name' => null, - 'bill_id' => null, - 'bill_name' => null, - 'recurrence_id' => (int)$recurrence->id, - // transaction data: - 'transactions' => $this->getTransactionData($recurrence), - ]; - $journal = $this->journalRepository->store($array); - Log::info(sprintf('Created new journal #%d', $journal->id)); - - // get piggy bank ID from meta data: - $piggyBankId = $this->getPiggyId($recurrence); - Log::debug(sprintf('Piggy bank ID for recurrence #%d is #%d', $recurrence->id, $piggyBankId)); - - // trigger event: - event(new StoredTransactionJournal($journal)); - - // link to piggy bank: - /** @var PiggyBankFactory $factory */ - $factory = app(PiggyBankFactory::class); - $factory->setUser($recurrence->user); - - $piggyBank = $factory->find($piggyBankId, null); - if (null !== $piggyBank) { - /** @var PiggyBankEventFactory $factory */ - $factory = app(PiggyBankEventFactory::class); - $factory->create($journal, $piggyBank); - } - - - $collection->push($journal); - // update recurring thing: - $recurrence->latest_date = $date; - $recurrence->save(); } return $collection; } + /** + * @param Recurrence $recurrence + * @param Carbon $date + * @return TransactionGroup|null + */ + private function handleOccurrence(Recurrence $recurrence, Carbon $date): ?TransactionGroup + { + Log::debug(sprintf('Now at date %s.', $date->format('Y-m-d'))); + if ($date->ne($this->date)) { + Log::debug(sprintf('%s is not not today (%s)', $date->format('Y-m-d'), $this->date->format('Y-m-d'))); + + return null; + } + Log::debug(sprintf('%s IS today (%s)', $date->format('Y-m-d'), $this->date->format('Y-m-d'))); + + // count created journals on THIS day. + $journalCount = $this->repository->getJournalCount($recurrence, $date, $date); + if ($journalCount > 0 && false === $this->force) { + Log::info(sprintf('Already created %d journal(s) for date %s', $journalCount, $date->format('Y-m-d'))); + + return null; + } + + if ($journalCount > 0 && true === $this->force) { + Log::warning(sprintf('Already created %d groups for date %s but FORCED to continue.', $journalCount, $date->format('Y-m-d'))); + } + + // create transaction array and send to factory. + $groupTitle = null; + if ($recurrence->recurrenceTransactions->count() > 1) { + /** @var RecurrenceTransaction $first */ + // @codeCoverageIgnoreStart + $first = $recurrence->recurrenceTransactions()->first(); + $groupTitle = $first->description; + // @codeCoverageIgnoreEnd + } + $array = [ + 'user' => $recurrence->user_id, + 'group_title' => $groupTitle, + 'transactions' => $this->getTransactionData($recurrence, $date), + ]; + /** @var TransactionGroup $group */ + $group = $this->groupRepository->store($array); + $this->created++; + Log::info(sprintf('Created new transaction group #%d', $group->id)); + + // link to piggy: + $this->linkGroupToPiggies($recurrence, $group); + + // trigger event: + event(new StoredTransactionGroup($group, $recurrence->apply_rules)); + + // update recurring thing: + $recurrence->latest_date = $date; + $recurrence->save(); + + return $group; + } + /** * Separate method that will loop all repetitions and do something with it. Will return * all created transaction journals. @@ -391,8 +359,6 @@ class CreateRecurringTransactions implements ShouldQueue * @param Recurrence $recurrence * * @return Collection - * - * @throws \FireflyIII\Exceptions\FireflyException */ private function handleRepetitions(Recurrence $recurrence): Collection { @@ -414,7 +380,7 @@ class CreateRecurringTransactions implements ShouldQueue Log::debug( sprintf( 'Calculated %d occurrences between %s and %s', - \count($occurrences), + count($occurrences), $recurrence->first_date->format('Y-m-d'), $includeWeekend->format('Y-m-d') ), $this->debugArray($occurrences) @@ -487,12 +453,13 @@ class CreateRecurringTransactions implements ShouldQueue // has repeated X times. $journalCount = $this->repository->getJournalCount($recurrence); - if (0 !== $recurrence->repetitions && $journalCount >= $recurrence->repetitions) { + if (0 !== $recurrence->repetitions && $journalCount >= $recurrence->repetitions && false === $this->force) { Log::info(sprintf('Recurrence #%d has run %d times, so will run no longer.', $recurrence->id, $recurrence->repetitions)); return false; } + // is no longer running if ($this->repeatUntilHasPassed($recurrence)) { Log::info( @@ -522,7 +489,7 @@ class CreateRecurringTransactions implements ShouldQueue } // already fired today (with success): - if ($this->hasFiredToday($recurrence)) { + if (false === $this->force && $this->hasFiredToday($recurrence)) { Log::info(sprintf('Recurrence #%d has already fired today. Skipped.', $recurrence->id)); return false; @@ -531,4 +498,37 @@ class CreateRecurringTransactions implements ShouldQueue return true; } + + /** + * @param Collection $recurrences + * @return Collection + */ + private function filterRecurrences(Collection $recurrences): Collection + { + return $recurrences->filter( + function (Recurrence $recurrence) { + return $this->validRecurrence($recurrence); + } + ); + } + + /*** + * @param Recurrence $recurrence + * @param TransactionGroup $group + */ + private function linkGroupToPiggies(Recurrence $recurrence, TransactionGroup $group): void + { + /** @var TransactionJournal $journal */ + foreach ($group->transactionJournals as $journal) { + // get piggy bank ID from meta data: + $piggyBank = $this->repository->getPiggyBank($recurrence); + if (null !== $piggyBank) { + /** @var PiggyBankEventFactory $factory */ + $factory = app(PiggyBankEventFactory::class); + $factory->create($journal, $piggyBank); + } + + } + + } } diff --git a/app/Jobs/ExecuteRuleGroupOnExistingTransactions.php b/app/Jobs/ExecuteRuleGroupOnExistingTransactions.php deleted file mode 100644 index ec1931df95..0000000000 --- a/app/Jobs/ExecuteRuleGroupOnExistingTransactions.php +++ /dev/null @@ -1,212 +0,0 @@ -. - */ -declare(strict_types=1); - -namespace FireflyIII\Jobs; - -use Carbon\Carbon; -use FireflyIII\Helpers\Collector\TransactionCollectorInterface; -use FireflyIII\Models\RuleGroup; -use FireflyIII\TransactionRules\Processor; -use FireflyIII\User; -use Illuminate\Contracts\Queue\ShouldQueue; -use Illuminate\Queue\InteractsWithQueue; -use Illuminate\Queue\SerializesModels; -use Illuminate\Support\Collection; - -/** - * Class ExecuteRuleGroupOnExistingTransactions. - */ -class ExecuteRuleGroupOnExistingTransactions extends Job implements ShouldQueue -{ - use InteractsWithQueue, SerializesModels; - - /** @var Collection Set of accounts */ - private $accounts; - /** @var Carbon The end date */ - private $endDate; - /** @var RuleGroup The rule group */ - private $ruleGroup; - /** @var Carbon The start date */ - private $startDate; - /** @var User The user */ - private $user; - - /** - * Create a new job instance. - * - * @param RuleGroup $ruleGroup - */ - public function __construct(RuleGroup $ruleGroup) - { - $this->ruleGroup = $ruleGroup; - } - - /** - * Get accounts. - * - * @return Collection - */ - public function getAccounts(): Collection - { - return $this->accounts; - } - - /** - * Set accounts. - * - * @param Collection $accounts - */ - public function setAccounts(Collection $accounts) - { - $this->accounts = $accounts; - } - - /** - * Get end date. - * - * @return \Carbon\Carbon - */ - public function getEndDate(): Carbon - { - return $this->endDate; - } - - /** - * Set end date. - * - * @param Carbon $date - */ - public function setEndDate(Carbon $date) - { - $this->endDate = $date; - } - - /** - * Get start date. - * - * @return \Carbon\Carbon - */ - public function getStartDate(): Carbon - { - return $this->startDate; - } - - /** - * Set start date. - * - * @param Carbon $date - */ - public function setStartDate(Carbon $date) - { - $this->startDate = $date; - } - - /** - * Get user. - * - * @return User - */ - public function getUser(): User - { - return $this->user; - } - - /** - * Set user. - * - * @param User $user - */ - public function setUser(User $user) - { - $this->user = $user; - } - - /** - * Execute the job. - * - * @throws \FireflyIII\Exceptions\FireflyException - */ - public function handle() - { - // Lookup all journals that match the parameters specified - $transactions = $this->collectJournals(); - - // Find processors for each rule within the current rule group - $processors = $this->collectProcessors(); - - // Execute the rules for each transaction - foreach ($processors as $processor) { - foreach ($transactions as $transaction) { - /** @var Processor $processor */ - $processor->handleTransaction($transaction); - - } - // Stop processing this group if the rule specifies 'stop_processing' - if ($processor->getRule()->stop_processing) { - break; - } - } - } - - /** - * Collect all journals that should be processed. - * - * @return Collection - */ - protected function collectJournals(): Collection - { - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setUser($this->user); - $collector->setAccounts($this->accounts)->setRange($this->startDate, $this->endDate); - - return $collector->getTransactions(); - } - - /** - * Collects a list of rule processors, one for each rule within the rule group. - * - * @return array - */ - protected function collectProcessors(): array - { - // Find all rules belonging to this rulegroup - $rules = $this->ruleGroup->rules() - ->leftJoin('rule_triggers', 'rules.id', '=', 'rule_triggers.rule_id') - ->where('rule_triggers.trigger_type', 'user_action') - ->where('rule_triggers.trigger_value', 'store-journal') - ->where('rules.active', 1) - ->get(['rules.*']); - - // Create a list of processors for these rules - return array_map( - function ($rule) { - /** @var Processor $processor */ - $processor = app(Processor::class); - $processor->make($rule); - - return $processor; - }, - $rules->all() - ); - } -} diff --git a/app/Jobs/ExecuteRuleOnExistingTransactions.php b/app/Jobs/ExecuteRuleOnExistingTransactions.php deleted file mode 100644 index a9476a36aa..0000000000 --- a/app/Jobs/ExecuteRuleOnExistingTransactions.php +++ /dev/null @@ -1,200 +0,0 @@ -. - */ -declare(strict_types=1); - -namespace FireflyIII\Jobs; - -use Carbon\Carbon; -use FireflyIII\Helpers\Collector\TransactionCollectorInterface; -use FireflyIII\Models\Rule; -use FireflyIII\TransactionRules\Processor; -use FireflyIII\User; -use Illuminate\Contracts\Queue\ShouldQueue; -use Illuminate\Queue\InteractsWithQueue; -use Illuminate\Queue\SerializesModels; -use Illuminate\Support\Collection; -use Log; - -/** - * Class ExecuteRuleOnExistingTransactions. - */ -class ExecuteRuleOnExistingTransactions extends Job implements ShouldQueue -{ - use InteractsWithQueue, SerializesModels; - - /** @var Collection The accounts */ - private $accounts; - /** @var Carbon The end date */ - private $endDate; - /** @var Rule The current rule */ - private $rule; - /** @var Carbon The start date */ - private $startDate; - /** @var User The user */ - private $user; - - /** - * Create a new job instance. - * - * @param Rule $rule - */ - public function __construct(Rule $rule) - { - $this->rule = $rule; - } - - /** - * Get accounts. - * - * @return Collection - */ - public function getAccounts(): Collection - { - return $this->accounts; - } - - /** - * Set accounts. - * - * @param Collection $accounts - */ - public function setAccounts(Collection $accounts) - { - $this->accounts = $accounts; - } - - /** - * Get end date. - * - * @return \Carbon\Carbon - */ - public function getEndDate(): Carbon - { - return $this->endDate; - } - - /** - * Set end date. - * - * @param Carbon $date - */ - public function setEndDate(Carbon $date) - { - $this->endDate = $date; - } - - /** - * Get rule. - * - * @return Rule - */ - public function getRule(): Rule - { - return $this->rule; - } - - /** - * Get start date. - * - * @return \Carbon\Carbon - */ - public function getStartDate(): Carbon - { - return $this->startDate; - } - - /** - * Set start date. - * - * @param Carbon $date - */ - public function setStartDate(Carbon $date) - { - $this->startDate = $date; - } - - /** - * Get user. - * - * @return User - */ - public function getUser(): User - { - return $this->user; - } - - /** - * Set user. - * - * @param User $user - */ - public function setUser(User $user) - { - $this->user = $user; - } - - /** - * Execute the job. - * - * @throws \FireflyIII\Exceptions\FireflyException - * - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - */ - public function handle() - { - // Lookup all journals that match the parameters specified - $transactions = $this->collectJournals(); - /** @var Processor $processor */ - $processor = app(Processor::class); - $processor->make($this->rule, true); - $hits = 0; - $misses = 0; - $total = 0; - // Execute the rules for each transaction - foreach ($transactions as $transaction) { - ++$total; - $result = $processor->handleTransaction($transaction); - if ($result) { - ++$hits; - } - if (!$result) { - ++$misses; - } - Log::info(sprintf('Current progress: %d Transactions. Hits: %d, misses: %d', $total, $hits, $misses)); - } - Log::info(sprintf('Total transactions: %d. Hits: %d, misses: %d', $total, $hits, $misses)); - } - - /** - * Collect all journals that should be processed. - * - * @return Collection - */ - protected function collectJournals(): Collection - { - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setUser($this->user); - $collector->setAccounts($this->accounts)->setRange($this->startDate, $this->endDate); - - return $collector->getTransactions(); - } -} diff --git a/app/Jobs/MailError.php b/app/Jobs/MailError.php index 33d40834b6..aeb2546115 100644 --- a/app/Jobs/MailError.php +++ b/app/Jobs/MailError.php @@ -32,6 +32,7 @@ use Mail; /** * Class MailError. + * @codeCoverageIgnore */ class MailError extends Job implements ShouldQueue { diff --git a/app/Mail/AccessTokenCreatedMail.php b/app/Mail/AccessTokenCreatedMail.php index 67690ef21b..65d702602e 100644 --- a/app/Mail/AccessTokenCreatedMail.php +++ b/app/Mail/AccessTokenCreatedMail.php @@ -30,6 +30,7 @@ use Illuminate\Queue\SerializesModels; /** * Class AccessTokenCreatedMail + * @codeCoverageIgnore */ class AccessTokenCreatedMail extends Mailable { diff --git a/app/Mail/AdminTestMail.php b/app/Mail/AdminTestMail.php index a87537597a..17e24ed50f 100644 --- a/app/Mail/AdminTestMail.php +++ b/app/Mail/AdminTestMail.php @@ -30,6 +30,7 @@ use Illuminate\Queue\SerializesModels; * Class AdminTestMail. * * Sends a test mail to administrators. + * @codeCoverageIgnore */ class AdminTestMail extends Mailable { diff --git a/app/Mail/ConfirmEmailChangeMail.php b/app/Mail/ConfirmEmailChangeMail.php index 780f840975..74d8c8b75e 100644 --- a/app/Mail/ConfirmEmailChangeMail.php +++ b/app/Mail/ConfirmEmailChangeMail.php @@ -30,6 +30,7 @@ use Illuminate\Queue\SerializesModels; * Class ConfirmEmailChangeMail * * Sends message to new address to confirm change. + * @codeCoverageIgnore */ class ConfirmEmailChangeMail extends Mailable { diff --git a/app/Mail/OAuthTokenCreatedMail.php b/app/Mail/OAuthTokenCreatedMail.php index 7b9553a543..f8729a4ce2 100644 --- a/app/Mail/OAuthTokenCreatedMail.php +++ b/app/Mail/OAuthTokenCreatedMail.php @@ -31,6 +31,7 @@ use Laravel\Passport\Client; /** * Class OAuthTokenCreatedMail + * @codeCoverageIgnore */ class OAuthTokenCreatedMail extends Mailable { diff --git a/app/Mail/RegisteredUser.php b/app/Mail/RegisteredUser.php index fd6d589b8c..c66955da2b 100644 --- a/app/Mail/RegisteredUser.php +++ b/app/Mail/RegisteredUser.php @@ -31,6 +31,7 @@ use Illuminate\Queue\SerializesModels; * Sends newly registered user an email message. * * Class RegisteredUser + * @codeCoverageIgnore */ class RegisteredUser extends Mailable { diff --git a/app/Mail/ReportNewJournalsMail.php b/app/Mail/ReportNewJournalsMail.php index 2d4120144b..5c38a2173a 100644 --- a/app/Mail/ReportNewJournalsMail.php +++ b/app/Mail/ReportNewJournalsMail.php @@ -31,6 +31,7 @@ use Illuminate\Support\Collection; * Class ReportNewJournalsMail. * * Sends a list of newly created journals to the user. + * @codeCoverageIgnore */ class ReportNewJournalsMail extends Mailable { diff --git a/app/Mail/RequestedNewPassword.php b/app/Mail/RequestedNewPassword.php index 5811a36a1f..bc102856e4 100644 --- a/app/Mail/RequestedNewPassword.php +++ b/app/Mail/RequestedNewPassword.php @@ -30,6 +30,7 @@ use Illuminate\Queue\SerializesModels; /** * Sends user link for new password. * Class RequestedNewPassword + * @codeCoverageIgnore */ class RequestedNewPassword extends Mailable { diff --git a/app/Mail/UndoEmailChangeMail.php b/app/Mail/UndoEmailChangeMail.php index 20e5b6ec4f..69deb68073 100644 --- a/app/Mail/UndoEmailChangeMail.php +++ b/app/Mail/UndoEmailChangeMail.php @@ -28,6 +28,7 @@ use Illuminate\Queue\SerializesModels; /** * Class UndoEmailChangeMail + * @codeCoverageIgnore */ class UndoEmailChangeMail extends Mailable { diff --git a/app/Models/Account.php b/app/Models/Account.php index 839e105b79..a1ceafd595 100644 --- a/app/Models/Account.php +++ b/app/Models/Account.php @@ -36,28 +36,53 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** * Class Account. * - * @property int $id - * @property string $name - * @property string $iban - * @property AccountType $accountType - * @property bool $active - * @property string $virtual_balance - * @property User $user - * @property string startBalance - * @property string endBalance - * @property string difference - * @property Carbon lastActivityDate - * @property Collection accountMeta - * @property bool encrypted - * @property int account_type_id - * @property Collection piggyBanks - * @property string $interest - * @property string $interestPeriod - * @property string accountTypeString - * @property Carbon created_at - * @property Carbon updated_at - * - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @property int $id + * @property string $name + * @property string $iban + * @property AccountType $accountType + * @property bool $active + * @property string $virtual_balance + * @property User $user + * @property string startBalance + * @property string endBalance + * @property string difference + * @property Carbon lastActivityDate + * @property Collection accountMeta + * @property bool encrypted + * @property int account_type_id + * @property Collection piggyBanks + * @property string $interest + * @property string $interestPeriod + * @property string accountTypeString + * @property Carbon created_at + * @property Carbon updated_at + * @SuppressWarnings (PHPMD.CouplingBetweenObjects) + * @property \Illuminate\Support\Carbon|null $deleted_at + * @property int $user_id + * @property-read string $edit_name + * @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\Note[] $notes + * @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\Transaction[] $transactions + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Account accountTypeIn($types) + * @method static bool|null forceDelete() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Account newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Account newQuery() + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Account onlyTrashed() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Account query() + * @method static bool|null restore() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Account whereAccountTypeId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Account whereActive($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Account whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Account whereDeletedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Account whereEncrypted($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Account whereIban($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Account whereId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Account whereName($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Account whereUpdatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Account whereUserId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Account whereVirtualBalance($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Account withTrashed() + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Account withoutTrashed() + * @mixin \Eloquent */ class Account extends Model { @@ -72,6 +97,7 @@ class Account extends Model = [ 'created_at' => 'datetime', 'updated_at' => 'datetime', + 'user_id' => 'integer', 'deleted_at' => 'datetime', 'active' => 'boolean', 'encrypted' => 'boolean', @@ -139,24 +165,6 @@ class Account extends Model return $name; } - /** - * Returns the opening balance. - * - * @return TransactionJournal - */ - public function getOpeningBalance(): TransactionJournal - { - $journal = TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') - ->where('transactions.account_id', $this->id) - ->transactionTypes([TransactionType::OPENING_BALANCE]) - ->first(['transaction_journals.*']); - if (null === $journal) { - return new TransactionJournal; - } - - return $journal; - } - /** * @codeCoverageIgnore * Get all of the notes. diff --git a/app/Models/AccountMeta.php b/app/Models/AccountMeta.php index ff36c2ec93..68448c391b 100644 --- a/app/Models/AccountMeta.php +++ b/app/Models/AccountMeta.php @@ -31,6 +31,20 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo; * @property string $data * @property string $name * @property int $account_id + * @property int $id + * @property \Illuminate\Support\Carbon|null $created_at + * @property \Illuminate\Support\Carbon|null $updated_at + * @property-read \FireflyIII\Models\Account $account + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\AccountMeta newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\AccountMeta newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\AccountMeta query() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\AccountMeta whereAccountId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\AccountMeta whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\AccountMeta whereData($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\AccountMeta whereId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\AccountMeta whereName($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\AccountMeta whereUpdatedAt($value) + * @mixin \Eloquent */ class AccountMeta extends Model { diff --git a/app/Models/AccountType.php b/app/Models/AccountType.php index ae4fd5369d..cc20e8f501 100644 --- a/app/Models/AccountType.php +++ b/app/Models/AccountType.php @@ -31,7 +31,16 @@ use Illuminate\Database\Eloquent\Relations\HasMany; * @property string $type * @method whereType(string $type) * @property int $id - * + * @property \Illuminate\Support\Carbon|null $created_at + * @property \Illuminate\Support\Carbon|null $updated_at + * @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\Account[] $accounts + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\AccountType newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\AccountType newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\AccountType query() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\AccountType whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\AccountType whereId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\AccountType whereUpdatedAt($value) + * @mixin \Eloquent */ class AccountType extends Model { diff --git a/app/Models/Attachment.php b/app/Models/Attachment.php index fa59a669cc..2f5fcc94ed 100644 --- a/app/Models/Attachment.php +++ b/app/Models/Attachment.php @@ -48,6 +48,33 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; * @property User $user * @property bool $uploaded * @property bool file_exists + * @property \Illuminate\Support\Carbon|null $deleted_at + * @property int $user_id + * @property int $attachable_id + * @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\Attachment[] $attachable + * @method static bool|null forceDelete() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Attachment newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Attachment newQuery() + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Attachment onlyTrashed() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Attachment query() + * @method static bool|null restore() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Attachment whereAttachableId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Attachment whereAttachableType($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Attachment whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Attachment whereDeletedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Attachment whereDescription($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Attachment whereFilename($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Attachment whereId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Attachment whereMd5($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Attachment whereMime($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Attachment whereSize($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Attachment whereTitle($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Attachment whereUpdatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Attachment whereUploaded($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Attachment whereUserId($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Attachment withTrashed() + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Attachment withoutTrashed() + * @mixin \Eloquent */ class Attachment extends Model { diff --git a/app/Models/AvailableBudget.php b/app/Models/AvailableBudget.php index 0276ba7084..9de4c31404 100644 --- a/app/Models/AvailableBudget.php +++ b/app/Models/AvailableBudget.php @@ -32,15 +32,35 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** * Class AvailableBudget. * - * @property int $id - * @property Carbon $created_at - * @property Carbon $updated_at - * @property User $user + * @property int $id + * @property Carbon $created_at + * @property Carbon $updated_at + * @property User $user * @property TransactionCurrency $transactionCurrency - * @property int $transaction_currency_id - * @property Carbon $start_date - * @property Carbon $end_date - * @property string $amount + * @property int $transaction_currency_id + * @property Carbon $start_date + * @property Carbon $end_date + * @property string $amount + * @property \Illuminate\Support\Carbon|null $deleted_at + * @property int $user_id + * @method static bool|null forceDelete() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\AvailableBudget newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\AvailableBudget newQuery() + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\AvailableBudget onlyTrashed() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\AvailableBudget query() + * @method static bool|null restore() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\AvailableBudget whereAmount($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\AvailableBudget whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\AvailableBudget whereDeletedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\AvailableBudget whereEndDate($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\AvailableBudget whereId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\AvailableBudget whereStartDate($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\AvailableBudget whereTransactionCurrencyId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\AvailableBudget whereUpdatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\AvailableBudget whereUserId($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\AvailableBudget withTrashed() + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\AvailableBudget withoutTrashed() + * @mixin \Eloquent */ class AvailableBudget extends Model { @@ -52,11 +72,12 @@ class AvailableBudget extends Model */ protected $casts = [ - 'created_at' => 'datetime', - 'updated_at' => 'datetime', - 'deleted_at' => 'datetime', - 'start_date' => 'date', - 'end_date' => 'date', + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + 'deleted_at' => 'datetime', + 'start_date' => 'date', + 'end_date' => 'date', + 'transaction_currency_id' => 'int', ]; /** @var array Fields that can be filled */ protected $fillable = ['user_id', 'transaction_currency_id', 'amount', 'start_date', 'end_date']; diff --git a/app/Models/Bill.php b/app/Models/Bill.php index d1ed5548bf..b28d7b0c8c 100644 --- a/app/Models/Bill.php +++ b/app/Models/Bill.php @@ -53,8 +53,39 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; * @property string $match * @property bool match_encrypted * @property bool name_encrypted - * - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @SuppressWarnings (PHPMD.CouplingBetweenObjects) + * @property \Illuminate\Support\Carbon|null $deleted_at + * @property int $user_id + * @property bool $name_encrypted + * @property bool $match_encrypted + * @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\Attachment[] $attachments + * @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\TransactionJournal[] $transactionJournals + * @method static bool|null forceDelete() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Bill newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Bill newQuery() + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Bill onlyTrashed() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Bill query() + * @method static bool|null restore() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Bill whereActive($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Bill whereAmountMax($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Bill whereAmountMin($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Bill whereAutomatch($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Bill whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Bill whereDate($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Bill whereDeletedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Bill whereId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Bill whereMatch($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Bill whereMatchEncrypted($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Bill whereName($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Bill whereNameEncrypted($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Bill whereRepeatFreq($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Bill whereSkip($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Bill whereTransactionCurrencyId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Bill whereUpdatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Bill whereUserId($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Bill withTrashed() + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Bill withoutTrashed() + * @mixin \Eloquent */ class Bill extends Model { diff --git a/app/Models/Budget.php b/app/Models/Budget.php index 018595217a..b4b370edf4 100644 --- a/app/Models/Budget.php +++ b/app/Models/Budget.php @@ -46,6 +46,31 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; * @property Carbon created_at * @property Carbon updated_at * @property User $user + * @property \Illuminate\Support\Carbon|null $created_at + * @property \Illuminate\Support\Carbon|null $updated_at + * @property \Illuminate\Support\Carbon|null $deleted_at + * @property bool $encrypted + * @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\BudgetLimit[] $budgetlimits + * @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\TransactionJournal[] $transactionJournals + * @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\Transaction[] $transactions + * @method static bool|null forceDelete() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Budget newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Budget newQuery() + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Budget onlyTrashed() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Budget query() + * @method static bool|null restore() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Budget whereActive($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Budget whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Budget whereDeletedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Budget whereEncrypted($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Budget whereId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Budget whereName($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Budget whereOrder($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Budget whereUpdatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Budget whereUserId($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Budget withTrashed() + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Budget withoutTrashed() + * @mixin \Eloquent */ class Budget extends Model { diff --git a/app/Models/BudgetLimit.php b/app/Models/BudgetLimit.php index 7acf1ccee9..f869bd86d8 100644 --- a/app/Models/BudgetLimit.php +++ b/app/Models/BudgetLimit.php @@ -41,6 +41,18 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; * @property string spent * @property int $transaction_currency_id * @property TransactionCurrency $transactionCurrency + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\BudgetLimit newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\BudgetLimit newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\BudgetLimit query() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\BudgetLimit whereAmount($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\BudgetLimit whereBudgetId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\BudgetLimit whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\BudgetLimit whereEndDate($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\BudgetLimit whereId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\BudgetLimit whereStartDate($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\BudgetLimit whereTransactionCurrencyId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\BudgetLimit whereUpdatedAt($value) + * @mixin \Eloquent */ class BudgetLimit extends Model { diff --git a/app/Models/Category.php b/app/Models/Category.php index d28edbfaa8..d12c122acc 100644 --- a/app/Models/Category.php +++ b/app/Models/Category.php @@ -42,6 +42,27 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; * @property User $user * @property Carbon $created_at * @property Carbon $updated_at + * @property \Illuminate\Support\Carbon|null $deleted_at + * @property int $user_id + * @property bool $encrypted + * @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\TransactionJournal[] $transactionJournals + * @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\Transaction[] $transactions + * @method static bool|null forceDelete() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Category newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Category newQuery() + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Category onlyTrashed() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Category query() + * @method static bool|null restore() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Category whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Category whereDeletedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Category whereEncrypted($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Category whereId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Category whereName($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Category whereUpdatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Category whereUserId($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Category withTrashed() + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Category withoutTrashed() + * @mixin \Eloquent */ class Category extends Model { diff --git a/app/Models/Configuration.php b/app/Models/Configuration.php index 771f9a86b2..f3ad2c4f50 100644 --- a/app/Models/Configuration.php +++ b/app/Models/Configuration.php @@ -30,6 +30,25 @@ use Illuminate\Database\Eloquent\SoftDeletes; * * @property string $data * @property string $name + * @property int $id + * @property \Illuminate\Support\Carbon|null $created_at + * @property \Illuminate\Support\Carbon|null $updated_at + * @property \Illuminate\Support\Carbon|null $deleted_at + * @method static bool|null forceDelete() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Configuration newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Configuration newQuery() + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Configuration onlyTrashed() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Configuration query() + * @method static bool|null restore() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Configuration whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Configuration whereData($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Configuration whereDeletedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Configuration whereId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Configuration whereName($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Configuration whereUpdatedAt($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Configuration withTrashed() + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Configuration withoutTrashed() + * @mixin \Eloquent */ class Configuration extends Model { diff --git a/app/Models/CurrencyExchangeRate.php b/app/Models/CurrencyExchangeRate.php index 6a50c0d874..0ef3320166 100644 --- a/app/Models/CurrencyExchangeRate.php +++ b/app/Models/CurrencyExchangeRate.php @@ -39,7 +39,24 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo; * @property Carbon $date * @property int $from_currency_id * @property int $to_currency_id - * + * @property string|null $deleted_at + * @property int $user_id + * @property float|null $user_rate + * @property-read \FireflyIII\User $user + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\CurrencyExchangeRate newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\CurrencyExchangeRate newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\CurrencyExchangeRate query() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\CurrencyExchangeRate whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\CurrencyExchangeRate whereDate($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\CurrencyExchangeRate whereDeletedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\CurrencyExchangeRate whereFromCurrencyId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\CurrencyExchangeRate whereId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\CurrencyExchangeRate whereRate($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\CurrencyExchangeRate whereToCurrencyId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\CurrencyExchangeRate whereUpdatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\CurrencyExchangeRate whereUserId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\CurrencyExchangeRate whereUserRate($value) + * @mixin \Eloquent */ class CurrencyExchangeRate extends Model { diff --git a/app/Models/ExportJob.php b/app/Models/ExportJob.php deleted file mode 100644 index 474d29f9da..0000000000 --- a/app/Models/ExportJob.php +++ /dev/null @@ -1,101 +0,0 @@ -. - */ -declare(strict_types=1); - -namespace FireflyIII\Models; - -use FireflyIII\User; -use Illuminate\Database\Eloquent\Model; -use Illuminate\Database\Eloquent\Relations\BelongsTo; -use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; - -/** - * Class ExportJob. - * - * @property User $user - * @property string $key - * @property int $user_id - * @property string status - * @property int id - */ -class ExportJob extends Model -{ - /** - * The attributes that should be casted to native types. - * - * @var array - */ - protected $casts - = [ - 'created_at' => 'datetime', - 'updated_at' => 'datetime', - ]; - - /** - * Route binder. Converts the key in the URL to the specified object (or throw 404). - * - * @param string $value - * - * @return ExportJob - * - * @throws NotFoundHttpException - */ - public static function routeBinder(string $value): ExportJob - { - if (auth()->check()) { - $key = trim($value); - /** @var User $user */ - $user = auth()->user(); - /** @var ExportJob $exportJob */ - $exportJob = $user->exportJobs()->where('key', $key)->first(); - if (null !== $exportJob) { - return $exportJob; - } - } - throw new NotFoundHttpException; - } - - /** - * Change the status of this export job. - * - * @param $status - * - * @deprecated - * @codeCoverageIgnore - */ - public function change($status): void - { - $this->status = $status; - $this->save(); - } - - /** - * Returns the user this objects belongs to. - * - * - * @return BelongsTo - * @codeCoverageIgnore - */ - public function user(): BelongsTo - { - return $this->belongsTo(User::class); - } -} diff --git a/app/Models/ImportJob.php b/app/Models/ImportJob.php index 90f5cefd3c..fb7aae31bd 100644 --- a/app/Models/ImportJob.php +++ b/app/Models/ImportJob.php @@ -48,6 +48,25 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; * @property int id * @property Carbon $created_at * @property Carbon $updated_at + * @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\Attachment[] $attachments + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\ImportJob newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\ImportJob newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\ImportJob query() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\ImportJob whereConfiguration($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\ImportJob whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\ImportJob whereErrors($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\ImportJob whereExtendedStatus($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\ImportJob whereFileType($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\ImportJob whereId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\ImportJob whereKey($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\ImportJob whereProvider($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\ImportJob whereStage($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\ImportJob whereStatus($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\ImportJob whereTagId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\ImportJob whereTransactions($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\ImportJob whereUpdatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\ImportJob whereUserId($value) + * @mixin \Eloquent */ class ImportJob extends Model { diff --git a/app/Models/LinkType.php b/app/Models/LinkType.php index 0e44074d61..66176b37a4 100644 --- a/app/Models/LinkType.php +++ b/app/Models/LinkType.php @@ -29,6 +29,8 @@ use Illuminate\Database\Eloquent\SoftDeletes; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** + * FireflyIII\Models\LinkType + * * @property int $journalCount * @property string $inward * @property string $outward @@ -38,7 +40,25 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; * @property Carbon $updated_at * @property int $id * Class LinkType - * + * @property \Illuminate\Support\Carbon|null $deleted_at + * @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\TransactionJournalLink[] $transactionJournalLinks + * @method static bool|null forceDelete() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\LinkType newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\LinkType newQuery() + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\LinkType onlyTrashed() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\LinkType query() + * @method static bool|null restore() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\LinkType whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\LinkType whereDeletedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\LinkType whereEditable($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\LinkType whereId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\LinkType whereInward($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\LinkType whereName($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\LinkType whereOutward($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\LinkType whereUpdatedAt($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\LinkType withTrashed() + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\LinkType withoutTrashed() + * @mixin \Eloquent */ class LinkType extends Model { diff --git a/app/Models/Note.php b/app/Models/Note.php index d67b00d82b..94732a1242 100644 --- a/app/Models/Note.php +++ b/app/Models/Note.php @@ -36,6 +36,26 @@ use Illuminate\Database\Eloquent\SoftDeletes; * @property string $text * @property string $title * @property int $noteable_id + * @property \Illuminate\Support\Carbon|null $deleted_at + * @property string $noteable_type + * @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\Note[] $noteable + * @method static bool|null forceDelete() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Note newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Note newQuery() + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Note onlyTrashed() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Note query() + * @method static bool|null restore() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Note whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Note whereDeletedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Note whereId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Note whereNoteableId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Note whereNoteableType($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Note whereText($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Note whereTitle($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Note whereUpdatedAt($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Note withTrashed() + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Note withoutTrashed() + * @mixin \Eloquent */ class Note extends Model { @@ -66,6 +86,7 @@ class Note extends Model /** * @param $value + * @codeCoverageIgnore */ public function setTextAttribute($value): void { diff --git a/app/Models/PiggyBank.php b/app/Models/PiggyBank.php index 2763b21ceb..e637dd6c63 100644 --- a/app/Models/PiggyBank.php +++ b/app/Models/PiggyBank.php @@ -45,7 +45,32 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; * @property bool $active * @property int $account_id * @property bool encrypted - * + * @property \Illuminate\Support\Carbon|null $deleted_at + * @property bool $encrypted + * @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\Note[] $notes + * @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\PiggyBankEvent[] $piggyBankEvents + * @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\PiggyBankRepetition[] $piggyBankRepetitions + * @method static bool|null forceDelete() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\PiggyBank newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\PiggyBank newQuery() + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\PiggyBank onlyTrashed() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\PiggyBank query() + * @method static bool|null restore() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\PiggyBank whereAccountId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\PiggyBank whereActive($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\PiggyBank whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\PiggyBank whereDeletedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\PiggyBank whereEncrypted($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\PiggyBank whereId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\PiggyBank whereName($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\PiggyBank whereOrder($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\PiggyBank whereStartdate($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\PiggyBank whereTargetamount($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\PiggyBank whereTargetdate($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\PiggyBank whereUpdatedAt($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\PiggyBank withTrashed() + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\PiggyBank withoutTrashed() + * @mixin \Eloquent */ class PiggyBank extends Model { diff --git a/app/Models/PiggyBankEvent.php b/app/Models/PiggyBankEvent.php index f2ef347af8..88097add6b 100644 --- a/app/Models/PiggyBankEvent.php +++ b/app/Models/PiggyBankEvent.php @@ -38,7 +38,17 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo; * @property string $amount * @property Carbon created_at * @property Carbon updated_at - * + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\PiggyBankEvent newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\PiggyBankEvent newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\PiggyBankEvent query() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\PiggyBankEvent whereAmount($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\PiggyBankEvent whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\PiggyBankEvent whereDate($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\PiggyBankEvent whereId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\PiggyBankEvent wherePiggyBankId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\PiggyBankEvent whereTransactionJournalId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\PiggyBankEvent whereUpdatedAt($value) + * @mixin \Eloquent */ class PiggyBankEvent extends Model { diff --git a/app/Models/PiggyBankRepetition.php b/app/Models/PiggyBankRepetition.php index 450b91addc..046950d561 100644 --- a/app/Models/PiggyBankRepetition.php +++ b/app/Models/PiggyBankRepetition.php @@ -33,6 +33,24 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo; * @property string $currentamount * @property Carbon $startdate * @property Carbon $targetdate + * @property int $id + * @property \Illuminate\Support\Carbon|null $created_at + * @property \Illuminate\Support\Carbon|null $updated_at + * @property int $piggy_bank_id + * @property-read \FireflyIII\Models\PiggyBank $piggyBank + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\PiggyBankRepetition newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\PiggyBankRepetition newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\PiggyBankRepetition onDates(\Carbon\Carbon $start, \Carbon\Carbon $target) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\PiggyBankRepetition query() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\PiggyBankRepetition relevantOnDate(\Carbon\Carbon $date) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\PiggyBankRepetition whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\PiggyBankRepetition whereCurrentamount($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\PiggyBankRepetition whereId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\PiggyBankRepetition wherePiggyBankId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\PiggyBankRepetition whereStartdate($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\PiggyBankRepetition whereTargetdate($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\PiggyBankRepetition whereUpdatedAt($value) + * @mixin \Eloquent */ class PiggyBankRepetition extends Model { diff --git a/app/Models/Preference.php b/app/Models/Preference.php index 6539762306..5e3853bc8f 100644 --- a/app/Models/Preference.php +++ b/app/Models/Preference.php @@ -37,6 +37,17 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; * @property Carbon $created_at * @property int $id * @property User user + * @property int $user_id + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Preference newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Preference newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Preference query() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Preference whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Preference whereData($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Preference whereId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Preference whereName($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Preference whereUpdatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Preference whereUserId($value) + * @mixin \Eloquent */ class Preference extends Model { diff --git a/app/Models/Recurrence.php b/app/Models/Recurrence.php index 3bdcc2c58d..7d54432d7b 100644 --- a/app/Models/Recurrence.php +++ b/app/Models/Recurrence.php @@ -57,7 +57,32 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; * @property \Illuminate\Support\Collection $recurrenceMeta * @property \Illuminate\Support\Collection $recurrenceTransactions * @property \FireflyIII\Models\TransactionType $transactionType - * + * @property \Illuminate\Support\Carbon|null $deleted_at + * @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\Note[] $notes + * @property-read \FireflyIII\Models\TransactionCurrency $transactionCurrency + * @method static bool|null forceDelete() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Recurrence newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Recurrence newQuery() + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Recurrence onlyTrashed() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Recurrence query() + * @method static bool|null restore() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Recurrence whereActive($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Recurrence whereApplyRules($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Recurrence whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Recurrence whereDeletedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Recurrence whereDescription($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Recurrence whereFirstDate($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Recurrence whereId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Recurrence whereLatestDate($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Recurrence whereRepeatUntil($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Recurrence whereRepetitions($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Recurrence whereTitle($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Recurrence whereTransactionTypeId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Recurrence whereUpdatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Recurrence whereUserId($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Recurrence withTrashed() + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Recurrence withoutTrashed() + * @mixin \Eloquent */ class Recurrence extends Model { diff --git a/app/Models/RecurrenceMeta.php b/app/Models/RecurrenceMeta.php index 2dd414c907..8f0a470334 100644 --- a/app/Models/RecurrenceMeta.php +++ b/app/Models/RecurrenceMeta.php @@ -33,6 +33,28 @@ use Illuminate\Database\Eloquent\SoftDeletes; * * @property string $name * @property string $value + * @property int $id + * @property \Illuminate\Support\Carbon|null $created_at + * @property \Illuminate\Support\Carbon|null $updated_at + * @property \Illuminate\Support\Carbon|null $deleted_at + * @property int $recurrence_id + * @property-read \FireflyIII\Models\Recurrence $recurrence + * @method static bool|null forceDelete() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RecurrenceMeta newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RecurrenceMeta newQuery() + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\RecurrenceMeta onlyTrashed() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RecurrenceMeta query() + * @method static bool|null restore() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RecurrenceMeta whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RecurrenceMeta whereDeletedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RecurrenceMeta whereId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RecurrenceMeta whereName($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RecurrenceMeta whereRecurrenceId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RecurrenceMeta whereUpdatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RecurrenceMeta whereValue($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\RecurrenceMeta withTrashed() + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\RecurrenceMeta withoutTrashed() + * @mixin \Eloquent */ class RecurrenceMeta extends Model { diff --git a/app/Models/RecurrenceRepetition.php b/app/Models/RecurrenceRepetition.php index fc72034c92..c8780cacfe 100644 --- a/app/Models/RecurrenceRepetition.php +++ b/app/Models/RecurrenceRepetition.php @@ -39,6 +39,26 @@ use Illuminate\Database\Eloquent\SoftDeletes; * @property \Carbon\Carbon $deleted_at * @property \Carbon\Carbon $updated_at * @property int $id + * @property int $recurrence_id + * @property-read \FireflyIII\Models\Recurrence $recurrence + * @method static bool|null forceDelete() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RecurrenceRepetition newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RecurrenceRepetition newQuery() + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\RecurrenceRepetition onlyTrashed() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RecurrenceRepetition query() + * @method static bool|null restore() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RecurrenceRepetition whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RecurrenceRepetition whereDeletedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RecurrenceRepetition whereId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RecurrenceRepetition whereRecurrenceId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RecurrenceRepetition whereRepetitionMoment($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RecurrenceRepetition whereRepetitionSkip($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RecurrenceRepetition whereRepetitionType($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RecurrenceRepetition whereUpdatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RecurrenceRepetition whereWeekend($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\RecurrenceRepetition withTrashed() + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\RecurrenceRepetition withoutTrashed() + * @mixin \Eloquent */ class RecurrenceRepetition extends Model { diff --git a/app/Models/RecurrenceTransaction.php b/app/Models/RecurrenceTransaction.php index 14812b9068..c47411b4e1 100644 --- a/app/Models/RecurrenceTransaction.php +++ b/app/Models/RecurrenceTransaction.php @@ -30,7 +30,6 @@ use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\SoftDeletes; /** - * * Class RecurrenceTransaction * * @property int $transaction_currency_id, @@ -47,6 +46,32 @@ use Illuminate\Database\Eloquent\SoftDeletes; * @property \Illuminate\Support\Collection $recurrenceTransactionMeta * @property int $id * @property Recurrence $recurrence + * @property \Illuminate\Support\Carbon|null $created_at + * @property \Illuminate\Support\Carbon|null $updated_at + * @property \Illuminate\Support\Carbon|null $deleted_at + * @property int $recurrence_id + * @property int $transaction_currency_id + * @method static bool|null forceDelete() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RecurrenceTransaction newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RecurrenceTransaction newQuery() + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\RecurrenceTransaction onlyTrashed() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RecurrenceTransaction query() + * @method static bool|null restore() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RecurrenceTransaction whereAmount($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RecurrenceTransaction whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RecurrenceTransaction whereDeletedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RecurrenceTransaction whereDescription($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RecurrenceTransaction whereDestinationId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RecurrenceTransaction whereForeignAmount($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RecurrenceTransaction whereForeignCurrencyId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RecurrenceTransaction whereId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RecurrenceTransaction whereRecurrenceId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RecurrenceTransaction whereSourceId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RecurrenceTransaction whereTransactionCurrencyId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RecurrenceTransaction whereUpdatedAt($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\RecurrenceTransaction withTrashed() + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\RecurrenceTransaction withoutTrashed() + * @mixin \Eloquent */ class RecurrenceTransaction extends Model { diff --git a/app/Models/RecurrenceTransactionMeta.php b/app/Models/RecurrenceTransactionMeta.php index 9f96b0fb5b..26edaa174e 100644 --- a/app/Models/RecurrenceTransactionMeta.php +++ b/app/Models/RecurrenceTransactionMeta.php @@ -33,6 +33,28 @@ use Illuminate\Database\Eloquent\SoftDeletes; * * @property string $name * @property string $value + * @property int $id + * @property \Illuminate\Support\Carbon|null $created_at + * @property \Illuminate\Support\Carbon|null $updated_at + * @property \Illuminate\Support\Carbon|null $deleted_at + * @property int $rt_id + * @property-read \FireflyIII\Models\RecurrenceTransaction $recurrenceTransaction + * @method static bool|null forceDelete() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RecurrenceTransactionMeta newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RecurrenceTransactionMeta newQuery() + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\RecurrenceTransactionMeta onlyTrashed() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RecurrenceTransactionMeta query() + * @method static bool|null restore() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RecurrenceTransactionMeta whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RecurrenceTransactionMeta whereDeletedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RecurrenceTransactionMeta whereId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RecurrenceTransactionMeta whereName($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RecurrenceTransactionMeta whereRtId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RecurrenceTransactionMeta whereUpdatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RecurrenceTransactionMeta whereValue($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\RecurrenceTransactionMeta withTrashed() + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\RecurrenceTransactionMeta withoutTrashed() + * @mixin \Eloquent */ class RecurrenceTransactionMeta extends Model { diff --git a/app/Models/Role.php b/app/Models/Role.php index 319629e02f..489e55e4c2 100644 --- a/app/Models/Role.php +++ b/app/Models/Role.php @@ -31,6 +31,21 @@ use Illuminate\Database\Eloquent\Relations\BelongsToMany; * * @property int $id * @property string $name + * @property \Illuminate\Support\Carbon|null $created_at + * @property \Illuminate\Support\Carbon|null $updated_at + * @property string|null $display_name + * @property string|null $description + * @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\User[] $users + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Role newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Role newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Role query() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Role whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Role whereDescription($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Role whereDisplayName($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Role whereId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Role whereName($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Role whereUpdatedAt($value) + * @mixin \Eloquent */ class Role extends Model { diff --git a/app/Models/Rule.php b/app/Models/Rule.php index c41cb2531a..e459eb4677 100644 --- a/app/Models/Rule.php +++ b/app/Models/Rule.php @@ -48,6 +48,29 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; * @property RuleGroup $ruleGroup * @property int $rule_group_id * @property string $description + * @property \Illuminate\Support\Carbon|null $deleted_at + * @property int $user_id + * @method static bool|null forceDelete() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Rule newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Rule newQuery() + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Rule onlyTrashed() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Rule query() + * @method static bool|null restore() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Rule whereActive($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Rule whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Rule whereDeletedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Rule whereDescription($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Rule whereId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Rule whereOrder($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Rule whereRuleGroupId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Rule whereStopProcessing($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Rule whereStrict($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Rule whereTitle($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Rule whereUpdatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Rule whereUserId($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Rule withTrashed() + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Rule withoutTrashed() + * @mixin \Eloquent */ class Rule extends Model { @@ -124,6 +147,7 @@ class Rule extends Model /** * @param $value + * @codeCoverageIgnore */ public function setDescriptionAttribute($value): void { diff --git a/app/Models/RuleAction.php b/app/Models/RuleAction.php index 0488e15806..0b7b2dd227 100644 --- a/app/Models/RuleAction.php +++ b/app/Models/RuleAction.php @@ -38,6 +38,20 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo; * @property bool $active * @property bool $stop_processing * @property Rule $rule + * @property int $rule_id + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RuleAction newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RuleAction newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RuleAction query() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RuleAction whereActionType($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RuleAction whereActionValue($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RuleAction whereActive($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RuleAction whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RuleAction whereId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RuleAction whereOrder($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RuleAction whereRuleId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RuleAction whereStopProcessing($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RuleAction whereUpdatedAt($value) + * @mixin \Eloquent */ class RuleAction extends Model { diff --git a/app/Models/RuleGroup.php b/app/Models/RuleGroup.php index 14f85f5491..1686ed5bc0 100644 --- a/app/Models/RuleGroup.php +++ b/app/Models/RuleGroup.php @@ -34,16 +34,37 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** * Class RuleGroup. * - * @property bool $active - * @property User $user - * @property Carbon $created_at - * @property Carbon $updated_at - * @property string $title - * @property string $text - * @property int $id - * @property int $order + * @property bool $active + * @property User $user + * @property Carbon $created_at + * @property Carbon $updated_at + * @property string $title + * @property string $text + * @property int $id + * @property int $order * @property Collection $rules - * @property string description + * @property string description + * @property \Illuminate\Support\Carbon|null $deleted_at + * @property int $user_id + * @method static bool|null forceDelete() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RuleGroup newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RuleGroup newQuery() + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\RuleGroup onlyTrashed() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RuleGroup query() + * @method static bool|null restore() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RuleGroup whereActive($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RuleGroup whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RuleGroup whereDeletedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RuleGroup whereDescription($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RuleGroup whereId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RuleGroup whereOrder($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RuleGroup whereTitle($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RuleGroup whereUpdatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RuleGroup whereUserId($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\RuleGroup withTrashed() + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\RuleGroup withoutTrashed() + * @property bool $stop_processing + * @mixin \Eloquent */ class RuleGroup extends Model { @@ -55,15 +76,16 @@ class RuleGroup extends Model */ protected $casts = [ - 'created_at' => 'datetime', - 'updated_at' => 'datetime', - 'deleted_at' => 'datetime', - 'active' => 'boolean', - 'order' => 'int', + 'created_at' => 'datetime', + 'updated_at' => 'datetime', + 'deleted_at' => 'datetime', + 'active' => 'boolean', + 'stop_processing' => 'boolean', + 'order' => 'int', ]; /** @var array Fields that can be filled */ - protected $fillable = ['user_id', 'order', 'title', 'description', 'active']; + protected $fillable = ['user_id', 'stop_processing', 'order', 'title', 'description', 'active']; /** * Route binder. Converts the key in the URL to the specified object (or throw 404). diff --git a/app/Models/RuleTrigger.php b/app/Models/RuleTrigger.php index e2b8422251..ea5d64f655 100644 --- a/app/Models/RuleTrigger.php +++ b/app/Models/RuleTrigger.php @@ -37,6 +37,21 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo; * @property int $order * @property bool $active * @property bool $stop_processing + * @property int $rule_id + * @property-read \FireflyIII\Models\Rule $rule + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RuleTrigger newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RuleTrigger newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RuleTrigger query() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RuleTrigger whereActive($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RuleTrigger whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RuleTrigger whereId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RuleTrigger whereOrder($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RuleTrigger whereRuleId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RuleTrigger whereStopProcessing($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RuleTrigger whereTriggerType($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RuleTrigger whereTriggerValue($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\RuleTrigger whereUpdatedAt($value) + * @mixin \Eloquent */ class RuleTrigger extends Model { diff --git a/app/Models/Tag.php b/app/Models/Tag.php index 3496f67125..9d52dd2e2e 100644 --- a/app/Models/Tag.php +++ b/app/Models/Tag.php @@ -46,6 +46,30 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; * @property string tagMode * @property Carbon created_at * @property Carbon updated_at + * @property \Illuminate\Support\Carbon|null $deleted_at + * @property int $user_id + * @property-read \FireflyIII\User $user + * @method static bool|null forceDelete() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Tag newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Tag newQuery() + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Tag onlyTrashed() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Tag query() + * @method static bool|null restore() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Tag whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Tag whereDate($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Tag whereDeletedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Tag whereDescription($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Tag whereId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Tag whereLatitude($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Tag whereLongitude($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Tag whereTag($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Tag whereTagMode($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Tag whereUpdatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Tag whereUserId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Tag whereZoomLevel($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Tag withTrashed() + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Tag withoutTrashed() + * @mixin \Eloquent */ class Tag extends Model { diff --git a/app/Models/Transaction.php b/app/Models/Transaction.php index 46a093d1c4..8591b1f2c0 100644 --- a/app/Models/Transaction.php +++ b/app/Models/Transaction.php @@ -101,7 +101,35 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; * @property Carbon created_at * @property Carbon updated_at * @property string foreign_currency_code - * @SuppressWarnings(PHPMD.TooManyPublicMethods) + * @SuppressWarnings (PHPMD.TooManyPublicMethods) + * @property \Illuminate\Support\Carbon|null $deleted_at + * @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\Budget[] $budgets + * @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\Category[] $categories + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Transaction after(\Carbon\Carbon $date) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Transaction before(\Carbon\Carbon $date) + * @method static bool|null forceDelete() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Transaction newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Transaction newQuery() + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Transaction onlyTrashed() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Transaction query() + * @method static bool|null restore() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Transaction transactionTypes($types) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Transaction whereAccountId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Transaction whereAmount($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Transaction whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Transaction whereDeletedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Transaction whereDescription($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Transaction whereForeignAmount($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Transaction whereForeignCurrencyId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Transaction whereId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Transaction whereIdentifier($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Transaction whereReconciled($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Transaction whereTransactionCurrencyId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Transaction whereTransactionJournalId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\Transaction whereUpdatedAt($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Transaction withTrashed() + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\Transaction withoutTrashed() + * @mixin \Eloquent */ class Transaction extends Model { @@ -154,30 +182,6 @@ class Transaction extends Model return false; } - /** - * Route binder. Converts the key in the URL to the specified object (or throw 404). - * - * @param string $value - * - * @return Transaction - * @throws NotFoundHttpException - */ - public static function routeBinder(string $value): Transaction - { - if (auth()->check()) { - $transactionId = (int)$value; - /** @var User $user */ - $user = auth()->user(); - /** @var Transaction $transaction */ - $transaction = $user->transactions()->where('transactions.id', $transactionId)->first(['transactions.*']); - if (null !== $transaction) { - return $transaction; - } - } - - throw new NotFoundHttpException; - } - /** * Get the account this object belongs to. diff --git a/app/Models/TransactionCurrency.php b/app/Models/TransactionCurrency.php index e8360fa28d..a5a2793bda 100644 --- a/app/Models/TransactionCurrency.php +++ b/app/Models/TransactionCurrency.php @@ -39,7 +39,29 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; * @property bool $enabled * @property Carbon $created_at * @property Carbon $updated_at - * + * @property \Illuminate\Support\Carbon|null $deleted_at + * @property string $name + * @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\BudgetLimit[] $budgetLimits + * @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\TransactionJournal[] $transactionJournals + * @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\Transaction[] $transactions + * @method static bool|null forceDelete() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionCurrency newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionCurrency newQuery() + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\TransactionCurrency onlyTrashed() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionCurrency query() + * @method static bool|null restore() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionCurrency whereCode($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionCurrency whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionCurrency whereDecimalPlaces($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionCurrency whereDeletedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionCurrency whereEnabled($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionCurrency whereId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionCurrency whereName($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionCurrency whereSymbol($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionCurrency whereUpdatedAt($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\TransactionCurrency withTrashed() + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\TransactionCurrency withoutTrashed() + * @mixin \Eloquent */ class TransactionCurrency extends Model { diff --git a/app/Models/TransactionGroup.php b/app/Models/TransactionGroup.php index 9d25ba44db..3a1fe2546f 100644 --- a/app/Models/TransactionGroup.php +++ b/app/Models/TransactionGroup.php @@ -25,13 +25,42 @@ namespace FireflyIII\Models; use FireflyIII\User; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; -use Illuminate\Database\Eloquent\Relations\BelongsToMany; +use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\SoftDeletes; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** * Class TransactionGroup. + * + * @property int $id + * @property \Illuminate\Support\Carbon|null $created_at + * @property \Illuminate\Support\Carbon|null $updated_at + * @property \Illuminate\Support\Carbon|null $deleted_at + * @property int $user_id + * @property string|null $title + * @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\TransactionJournal[] $transactionJournals + * @property-read \FireflyIII\User $user + * @method static bool|null forceDelete() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionGroup newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionGroup newQuery() + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\TransactionGroup onlyTrashed() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionGroup query() + * @method static bool|null restore() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionGroup whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionGroup whereDeletedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionGroup whereId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionGroup whereTitle($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionGroup whereUpdatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionGroup whereUserId($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\TransactionGroup withTrashed() + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\TransactionGroup withoutTrashed() + * @mixin \Eloquent + * @property string amount + * @property string foreign_amount + * @property int transaction_group_id + * @property int transaction_journal_id + * @property string transaction_group_title */ class TransactionGroup extends Model { @@ -44,15 +73,16 @@ class TransactionGroup extends Model */ protected $casts = [ + 'id' => 'integer', 'created_at' => 'datetime', 'updated_at' => 'datetime', 'deleted_at' => 'datetime', 'title' => 'string', + 'date' => 'datetime', ]; /** @var array Fields that can be filled */ - protected $fillable - = ['user_id', 'title']; + protected $fillable = ['user_id', 'title']; /** * Route binder. Converts the key in the URL to the specified object (or throw 404). @@ -69,8 +99,9 @@ class TransactionGroup extends Model /** @var User $user */ $user = auth()->user(); /** @var TransactionGroup $group */ - $group = $user->transactionGroups()->where('transaction_groups.id', $groupId) - ->first(['transaction_groups.*']); + $group = $user->transactionGroups() + ->with(['transactionJournals','transactionJournals.transactions']) + ->where('transaction_groups.id', $groupId)->first(['transaction_groups.*']); if (null !== $group) { return $group; } @@ -81,11 +112,11 @@ class TransactionGroup extends Model /** * @codeCoverageIgnore - * @return BelongsToMany + * @return HasMany */ - public function transactionJournals(): BelongsToMany + public function transactionJournals(): HasMany { - return $this->belongsToMany(TransactionJournal::class); + return $this->hasMany(TransactionJournal::class); } /** diff --git a/app/Models/TransactionJournal.php b/app/Models/TransactionJournal.php index 6e90acd9d1..adaa9ed71f 100644 --- a/app/Models/TransactionJournal.php +++ b/app/Models/TransactionJournal.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace FireflyIII\Models; use Carbon\Carbon; +use FireflyIII\Exceptions\FireflyException; use FireflyIII\User; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Builder as EloquentBuilder; @@ -39,35 +40,74 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** * Class TransactionJournal. * - * @property User $user - * @property int $bill_id - * @property Collection $categories - * @property bool $completed - * @property string $description - * @property int $transaction_type_id - * @property int transaction_currency_id - * @property TransactionCurrency $transactionCurrency - * @property Collection $tags - * @property int user_id - * @property Collection transactions - * @property int transaction_count - * @property Carbon interest_date - * @property Carbon book_date - * @property Carbon process_date - * @property bool encrypted - * @property int order - * @property int budget_id - * @property string period_marker - * @property Carbon $date - * @property string $transaction_type_type - * @property int $id - * @property TransactionType $transactionType - * @property Collection budgets - * @property Bill $bill - * @property Collection transactionJournalMeta - * - * @SuppressWarnings(PHPMD.TooManyPublicMethods) - * @SuppressWarnings(PHPMD.CouplingBetweenObjects) + * @property User $user + * @property int $bill_id + * @property Collection $categories + * @property bool $completed + * @property string $description + * @property int $transaction_type_id + * @property int transaction_currency_id + * @property TransactionCurrency $transactionCurrency + * @property Collection $tags + * @property int user_id + * @property Collection transactions + * @property int transaction_count + * @property Carbon interest_date + * @property Carbon book_date + * @property Carbon process_date + * @property bool encrypted + * @property int order + * @property int budget_id + * @property string period_marker + * @property Carbon $date + * @property string $transaction_type_type + * @property int $id + * @property TransactionType $transactionType + * @property Collection budgets + * @property Bill $bill + * @property Collection transactionJournalMeta + * @property TransactionGroup transactionGroup + * @property int transaction_group_id + * @SuppressWarnings (PHPMD.TooManyPublicMethods) + * @SuppressWarnings (PHPMD.CouplingBetweenObjects) + * @property \Illuminate\Support\Carbon|null $created_at + * @property \Illuminate\Support\Carbon|null $updated_at + * @property \Illuminate\Support\Carbon|null $deleted_at + * @property int $tag_count + * @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\Attachment[] $attachments + * @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\Note[] $notes + * @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\PiggyBankEvent[] $piggyBankEvents + * @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\TransactionJournalLink[] $sourceJournalLinks + * @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\Category[] $transactionGroups + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionJournal after(\Carbon\Carbon $date) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionJournal before(\Carbon\Carbon $date) + * @method static bool|null forceDelete() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionJournal newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionJournal newQuery() + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\TransactionJournal onlyTrashed() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionJournal query() + * @method static bool|null restore() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionJournal transactionTypes($types) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionJournal whereBillId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionJournal whereBookDate($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionJournal whereCompleted($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionJournal whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionJournal whereDate($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionJournal whereDeletedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionJournal whereDescription($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionJournal whereEncrypted($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionJournal whereId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionJournal whereInterestDate($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionJournal whereOrder($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionJournal whereProcessDate($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionJournal whereTagCount($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionJournal whereTransactionCurrencyId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionJournal whereTransactionTypeId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionJournal whereUpdatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionJournal whereUserId($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\TransactionJournal withTrashed() + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\TransactionJournal withoutTrashed() + * @mixin \Eloquent */ class TransactionJournal extends Model { @@ -95,14 +135,14 @@ class TransactionJournal extends Model /** @var array Fields that can be filled */ protected $fillable - = ['user_id', 'transaction_type_id', 'bill_id', 'interest_date', 'book_date', 'process_date', - 'transaction_currency_id', 'description', 'completed', - 'date', 'rent_date', 'encrypted', 'tag_count',]; + = ['user_id', 'transaction_type_id', 'bill_id', 'tag_count','transaction_currency_id', 'description', 'completed', 'order', + 'date']; /** @var array Hidden from view */ protected $hidden = ['encrypted']; /** * Checks if tables are joined. + * @codeCoverageIgnore * * @param Builder $query * @param string $table @@ -131,6 +171,7 @@ class TransactionJournal extends Model * * @return TransactionJournal * @throws NotFoundHttpException + * @throws FireflyException */ public static function routeBinder(string $value): TransactionJournal { @@ -139,8 +180,7 @@ class TransactionJournal extends Model /** @var User $user */ $user = auth()->user(); /** @var TransactionJournal $journal */ - $journal = $user->transactionJournals()->where('transaction_journals.id', $journalId) - ->first(['transaction_journals.*']); + $journal = $user->transactionJournals()->where('transaction_journals.id', $journalId)->first(['transaction_journals.*']); if (null !== $journal) { return $journal; } @@ -186,6 +226,7 @@ class TransactionJournal extends Model } /** + * @codeCoverageIgnore * @return bool */ public function isDeposit(): bool @@ -291,7 +332,7 @@ class TransactionJournal extends Model if (!self::isJoined($query, 'transaction_types')) { $query->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id'); } - if (\count($types) > 0) { + if (count($types) > 0) { $query->whereIn('transaction_types.type', $types); } } @@ -325,11 +366,11 @@ class TransactionJournal extends Model /** * @codeCoverageIgnore - * @return BelongsToMany + * @return BelongsTo */ - public function transactionGroups(): BelongsToMany + public function transactionGroup(): BelongsTo { - return $this->belongsToMany(Category::class); + return $this->belongsTo(TransactionGroup::class); } /** diff --git a/app/Models/TransactionJournalLink.php b/app/Models/TransactionJournalLink.php index 2a1e588b88..981290c6ea 100644 --- a/app/Models/TransactionJournalLink.php +++ b/app/Models/TransactionJournalLink.php @@ -41,6 +41,18 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; * @property int $link_type_id * @property int $source_id * @property int $destination_id + * @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\Note[] $notes + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionJournalLink newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionJournalLink newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionJournalLink query() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionJournalLink whereComment($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionJournalLink whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionJournalLink whereDestinationId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionJournalLink whereId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionJournalLink whereLinkTypeId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionJournalLink whereSourceId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionJournalLink whereUpdatedAt($value) + * @mixin \Eloquent */ class TransactionJournalLink extends Model { diff --git a/app/Models/TransactionJournalMeta.php b/app/Models/TransactionJournalMeta.php index 0eb222f6c3..6706d299f3 100644 --- a/app/Models/TransactionJournalMeta.php +++ b/app/Models/TransactionJournalMeta.php @@ -34,6 +34,27 @@ use Illuminate\Database\Eloquent\SoftDeletes; * @property TransactionJournal $transactionJournal * @property string $data * @property int $id + * @property \Illuminate\Support\Carbon|null $created_at + * @property \Illuminate\Support\Carbon|null $updated_at + * @property string $hash + * @property \Illuminate\Support\Carbon|null $deleted_at + * @method static bool|null forceDelete() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionJournalMeta newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionJournalMeta newQuery() + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\TransactionJournalMeta onlyTrashed() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionJournalMeta query() + * @method static bool|null restore() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionJournalMeta whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionJournalMeta whereData($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionJournalMeta whereDeletedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionJournalMeta whereHash($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionJournalMeta whereId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionJournalMeta whereName($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionJournalMeta whereTransactionJournalId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionJournalMeta whereUpdatedAt($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\TransactionJournalMeta withTrashed() + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\TransactionJournalMeta withoutTrashed() + * @mixin \Eloquent */ class TransactionJournalMeta extends Model { @@ -63,7 +84,7 @@ class TransactionJournalMeta extends Model */ public function getDataAttribute($value) { - return json_decode($value); + return json_decode($value, false); } /** diff --git a/app/Models/TransactionType.php b/app/Models/TransactionType.php index 75c0f35b3d..bc7c565bdc 100644 --- a/app/Models/TransactionType.php +++ b/app/Models/TransactionType.php @@ -32,6 +32,24 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; * * @property string $type * @property int $id + * @property \Illuminate\Support\Carbon|null $created_at + * @property \Illuminate\Support\Carbon|null $updated_at + * @property \Illuminate\Support\Carbon|null $deleted_at + * @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\TransactionJournal[] $transactionJournals + * @method static bool|null forceDelete() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionType newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionType newQuery() + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\TransactionType onlyTrashed() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionType query() + * @method static bool|null restore() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionType whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionType whereDeletedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionType whereId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionType whereType($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\Models\TransactionType whereUpdatedAt($value) + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\TransactionType withTrashed() + * @method static \Illuminate\Database\Query\Builder|\FireflyIII\Models\TransactionType withoutTrashed() + * @mixin \Eloquent */ class TransactionType extends Model { diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index c775be7825..4afe6a89c7 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -28,8 +28,8 @@ use FireflyIII\Events\RegisteredUser; use FireflyIII\Events\RequestedNewPassword; use FireflyIII\Events\RequestedReportOnJournals; use FireflyIII\Events\RequestedVersionCheckStatus; -use FireflyIII\Events\StoredTransactionJournal; -use FireflyIII\Events\UpdatedTransactionJournal; +use FireflyIII\Events\StoredTransactionGroup; +use FireflyIII\Events\UpdatedTransactionGroup; use FireflyIII\Events\UserChangedEmail; use FireflyIII\Mail\OAuthTokenCreatedMail; use FireflyIII\Models\PiggyBank; @@ -46,11 +46,11 @@ use Session; /** * Class EventServiceProvider. + * @codeCoverageIgnore */ class EventServiceProvider extends ServiceProvider { /** - * @codeCoverageIgnore * The event listener mappings for the application. * * @var array @@ -89,12 +89,12 @@ class EventServiceProvider extends ServiceProvider 'FireflyIII\Handlers\Events\AdminEventHandler@sendTestMessage', ], // is a Transaction Journal related event. - StoredTransactionJournal::class => [ - 'FireflyIII\Handlers\Events\StoredJournalEventHandler@processRules', + StoredTransactionGroup::class => [ + 'FireflyIII\Handlers\Events\StoredGroupEventHandler@processRules', ], // is a Transaction Journal related event. - UpdatedTransactionJournal::class => [ - 'FireflyIII\Handlers\Events\UpdatedJournalEventHandler@processRules', + UpdatedTransactionGroup::class => [ + 'FireflyIII\Handlers\Events\UpdatedGroupEventHandler@processRules', ], // API related events: AccessTokenCreated::class => [ @@ -103,7 +103,6 @@ class EventServiceProvider extends ServiceProvider ]; /** - * @codeCoverageIgnore * Register any events for your application. */ public function boot(): void @@ -119,7 +118,7 @@ class EventServiceProvider extends ServiceProvider { // in case of repeated piggy banks and/or other problems. PiggyBank::created( - function (PiggyBank $piggyBank) { + static function (PiggyBank $piggyBank) { $repetition = new PiggyBankRepetition; $repetition->piggyBank()->associate($piggyBank); $repetition->startdate = $piggyBank->startdate; @@ -129,7 +128,7 @@ class EventServiceProvider extends ServiceProvider } ); Client::created( - function (Client $oauthClient) { + static function (Client $oauthClient) { /** @var UserRepositoryInterface $repository */ $repository = app(UserRepositoryInterface::class); $user = $repository->findNull((int)$oauthClient->user_id); diff --git a/app/Providers/FireflyServiceProvider.php b/app/Providers/FireflyServiceProvider.php index fe10390d62..d297c39294 100644 --- a/app/Providers/FireflyServiceProvider.php +++ b/app/Providers/FireflyServiceProvider.php @@ -23,16 +23,14 @@ declare(strict_types=1); namespace FireflyIII\Providers; use FireflyIII\Exceptions\FireflyException; -use FireflyIII\Export\ExpandedProcessor; -use FireflyIII\Export\ProcessorInterface; use FireflyIII\Generator\Chart\Basic\ChartJsGenerator; use FireflyIII\Generator\Chart\Basic\GeneratorInterface; use FireflyIII\Helpers\Attachments\AttachmentHelper; use FireflyIII\Helpers\Attachments\AttachmentHelperInterface; use FireflyIII\Helpers\Chart\MetaPieChart; use FireflyIII\Helpers\Chart\MetaPieChartInterface; -use FireflyIII\Helpers\FiscalHelper; -use FireflyIII\Helpers\FiscalHelperInterface; +use FireflyIII\Helpers\Fiscal\FiscalHelper; +use FireflyIII\Helpers\Fiscal\FiscalHelperInterface; use FireflyIII\Helpers\Help\Help; use FireflyIII\Helpers\Help\HelpInterface; use FireflyIII\Helpers\Report\BalanceReportHelper; @@ -45,12 +43,14 @@ use FireflyIII\Helpers\Report\PopupReport; use FireflyIII\Helpers\Report\PopupReportInterface; use FireflyIII\Helpers\Report\ReportHelper; use FireflyIII\Helpers\Report\ReportHelperInterface; +use FireflyIII\Repositories\TransactionType\TransactionTypeRepository; +use FireflyIII\Repositories\TransactionType\TransactionTypeRepositoryInterface; use FireflyIII\Repositories\User\UserRepository; use FireflyIII\Repositories\User\UserRepositoryInterface; use FireflyIII\Services\Currency\ExchangeRateInterface; use FireflyIII\Services\IP\IpifyOrg; use FireflyIII\Services\IP\IPRetrievalInterface; -use FireflyIII\Services\Password\PwndVerifierV2; +use FireflyIII\Services\Password\PwndVerifierV3; use FireflyIII\Services\Password\Verifier; use FireflyIII\Support\Amount; use FireflyIII\Support\ExpandedForm; @@ -59,13 +59,9 @@ use FireflyIII\Support\Navigation; use FireflyIII\Support\Preferences; use FireflyIII\Support\Steam; use FireflyIII\Support\Twig\AmountFormat; +use FireflyIII\Support\Twig\Extension\TransactionGroupTwig; use FireflyIII\Support\Twig\General; -use FireflyIII\Support\Twig\Journal; -use FireflyIII\Support\Twig\Loader\AccountLoader; -use FireflyIII\Support\Twig\Loader\TransactionJournalLoader; -use FireflyIII\Support\Twig\Loader\TransactionLoader; use FireflyIII\Support\Twig\Rule; -use FireflyIII\Support\Twig\Transaction; use FireflyIII\Support\Twig\Translation; use FireflyIII\Validation\FireflyValidator; use Illuminate\Foundation\Application; @@ -96,17 +92,13 @@ class FireflyServiceProvider extends ServiceProvider } ); $config = app('config'); - //Twig::addExtension(new Functions($config)); - Twig::addRuntimeLoader(new TransactionLoader); - Twig::addRuntimeLoader(new AccountLoader); - Twig::addRuntimeLoader(new TransactionJournalLoader); + Twig::addExtension(new Functions($config)); Twig::addExtension(new General); - Twig::addExtension(new Journal); + Twig::addExtension(new TransactionGroupTwig); Twig::addExtension(new Translation); - Twig::addExtension(new Transaction); Twig::addExtension(new Rule); Twig::addExtension(new AmountFormat); - //Twig::addExtension(new Twig_Extension_Debug); + Twig::addExtension(new Twig_Extension_Debug); } /** @@ -173,9 +165,8 @@ class FireflyServiceProvider extends ServiceProvider ); // other generators - // export: - $this->app->bind(ProcessorInterface::class, ExpandedProcessor::class); $this->app->bind(UserRepositoryInterface::class, UserRepository::class); + $this->app->bind(TransactionTypeRepositoryInterface::class, TransactionTypeRepository::class); $this->app->bind(AttachmentHelperInterface::class, AttachmentHelper::class); // more generators: @@ -192,7 +183,7 @@ class FireflyServiceProvider extends ServiceProvider $this->app->bind(ExchangeRateInterface::class, $class); // password verifier thing - $this->app->bind(Verifier::class, PwndVerifierV2::class); + $this->app->bind(Verifier::class, PwndVerifierV3::class); // IP thing: $this->app->bind(IPRetrievalInterface::class, IpifyOrg::class); diff --git a/app/Providers/ExportJobServiceProvider.php b/app/Providers/ImportServiceProvider.php similarity index 60% rename from app/Providers/ExportJobServiceProvider.php rename to app/Providers/ImportServiceProvider.php index 262c561f54..81154dbb71 100644 --- a/app/Providers/ExportJobServiceProvider.php +++ b/app/Providers/ImportServiceProvider.php @@ -1,7 +1,7 @@ exportJob(); - $this->importJob(); - } - - /** - * Register export job. - */ - private function exportJob(): void - { - $this->app->bind( - ExportJobRepositoryInterface::class, - function (Application $app) { - /** @var ExportJobRepository $repository */ - $repository = app(ExportJobRepository::class); - if ($app->auth->check()) { - $repository->setUser(auth()->user()); - } - - return $repository; - } - ); - } - - /** - * Register import job. - */ - private function importJob(): void { $this->app->bind( ImportJobRepositoryInterface::class, function (Application $app) { - /** @var ImportJobRepository $repository */ + /** @var ImportJobRepositoryInterface $repository */ $repository = app(ImportJobRepository::class); if ($app->auth->check()) { $repository->setUser(auth()->user()); diff --git a/app/Providers/JournalServiceProvider.php b/app/Providers/JournalServiceProvider.php index 89bec86527..3b64273b14 100644 --- a/app/Providers/JournalServiceProvider.php +++ b/app/Providers/JournalServiceProvider.php @@ -22,10 +22,12 @@ declare(strict_types=1); namespace FireflyIII\Providers; -use FireflyIII\Helpers\Collector\TransactionCollector; -use FireflyIII\Helpers\Collector\TransactionCollectorInterface; +use FireflyIII\Helpers\Collector\GroupCollector; +use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Repositories\Journal\JournalRepository; use FireflyIII\Repositories\Journal\JournalRepositoryInterface; +use FireflyIII\Repositories\TransactionGroup\TransactionGroupRepository; +use FireflyIII\Repositories\TransactionGroup\TransactionGroupRepositoryInterface; use Illuminate\Foundation\Application; use Illuminate\Support\ServiceProvider; @@ -48,19 +50,20 @@ class JournalServiceProvider extends ServiceProvider public function register(): void { $this->registerRepository(); - $this->registerCollector(); + $this->registerGroupRepository(); + $this->registerGroupCollector(); } /** - * Register the collector. + * */ - private function registerCollector(): void + private function registerGroupCollector(): void { $this->app->bind( - TransactionCollectorInterface::class, + GroupCollectorInterface::class, function (Application $app) { - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollector::class); + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollector::class); if ($app->auth->check()) { $collector->setUser(auth()->user()); } @@ -70,6 +73,25 @@ class JournalServiceProvider extends ServiceProvider ); } + /** + * Register group repos. + */ + private function registerGroupRepository(): void + { + $this->app->bind( + TransactionGroupRepositoryInterface::class, + function (Application $app) { + /** @var TransactionGroupRepositoryInterface $repository */ + $repository = app(TransactionGroupRepository::class); + if ($app->auth->check()) { + $repository->setUser(auth()->user()); + } + + return $repository; + } + ); + } + /** * Register repository. */ diff --git a/app/Repositories/Account/AccountRepository.php b/app/Repositories/Account/AccountRepository.php index 89848b5fe0..9967f08b79 100644 --- a/app/Repositories/Account/AccountRepository.php +++ b/app/Repositories/Account/AccountRepository.php @@ -28,6 +28,7 @@ use FireflyIII\Factory\AccountFactory; use FireflyIII\Models\Account; use FireflyIII\Models\AccountType; use FireflyIII\Models\TransactionCurrency; +use FireflyIII\Models\TransactionGroup; use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionType; use FireflyIII\Services\Internal\Destroy\AccountDestroyService; @@ -53,24 +54,13 @@ class AccountRepository implements AccountRepositoryInterface public function __construct() { if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } - - /** - * @param array $types - * - * @return int - */ - public function count(array $types): int - { - return $this->user->accounts()->accountTypeIn($types)->count(); - } - /** * Moved here from account CRUD. * - * @param Account $account + * @param Account $account * @param Account|null $moveTo * * @return bool @@ -86,9 +76,20 @@ class AccountRepository implements AccountRepositoryInterface return true; } + + /** + * @param array $types + * + * @return int + */ + public function count(array $types): int + { + return $this->user->accounts()->accountTypeIn($types)->count(); + } + /** * @param string $number - * @param array $types + * @param array $types * * @return Account|null */ @@ -96,10 +97,10 @@ class AccountRepository implements AccountRepositoryInterface { $query = $this->user->accounts() ->leftJoin('account_meta', 'account_meta.account_id', '=', 'accounts.id') - ->where('account_meta.name', 'accountNumber') + ->where('account_meta.name', 'account_number') ->where('account_meta.data', json_encode($number)); - if (\count($types) > 0) { + if (count($types) > 0) { $query->leftJoin('account_types', 'accounts.account_type_id', '=', 'account_types.id'); $query->whereIn('account_types.type', $types); } @@ -115,7 +116,7 @@ class AccountRepository implements AccountRepositoryInterface /** * @param string $iban - * @param array $types + * @param array $types * * @return Account|null */ @@ -123,11 +124,13 @@ class AccountRepository implements AccountRepositoryInterface { $query = $this->user->accounts()->where('iban', '!=', '')->whereNotNull('iban'); - if (\count($types) > 0) { + if (count($types) > 0) { $query->leftJoin('account_types', 'accounts.account_type_id', '=', 'account_types.id'); $query->whereIn('account_types.type', $types); } + // TODO a loop like this is no longer necessary + $accounts = $query->get(['accounts.*']); /** @var Account $account */ foreach ($accounts as $account) { @@ -141,7 +144,7 @@ class AccountRepository implements AccountRepositoryInterface /** * @param string $name - * @param array $types + * @param array $types * * @return Account|null */ @@ -149,13 +152,16 @@ class AccountRepository implements AccountRepositoryInterface { $query = $this->user->accounts(); - if (\count($types) > 0) { + if (count($types) > 0) { $query->leftJoin('account_types', 'accounts.account_type_id', '=', 'account_types.id'); $query->whereIn('account_types.type', $types); } Log::debug(sprintf('Searching for account named "%s" (of user #%d) of the following type(s)', $name, $this->user->id), ['types' => $types]); $accounts = $query->get(['accounts.*']); + + // TODO no longer need to loop like this + /** @var Account $account */ foreach ($accounts as $account) { if ($account->name === $name) { @@ -226,16 +232,13 @@ class AccountRepository implements AccountRepositoryInterface /** @var Collection $result */ $query = $this->user->accounts(); - if (\count($accountIds) > 0) { + if (count($accountIds) > 0) { $query->whereIn('accounts.id', $accountIds); } + $query->orderBy('accounts.active', 'DESC'); + $query->orderBy('accounts.name', 'ASC'); $result = $query->get(['accounts.*']); - $result = $result->sortBy( - function (Account $account) { - return strtolower($account->name); - } - ); return $result; } @@ -249,16 +252,13 @@ class AccountRepository implements AccountRepositoryInterface { /** @var Collection $result */ $query = $this->user->accounts(); - if (\count($types) > 0) { + if (count($types) > 0) { $query->accountTypeIn($types); } - + $query->orderBy('accounts.active', 'DESC'); + $query->orderBy('accounts.name', 'ASC'); $result = $query->get(['accounts.*']); - $result = $result->sortBy( - function (Account $account) { - return strtolower($account->name); - } - ); + return $result; } @@ -273,19 +273,16 @@ class AccountRepository implements AccountRepositoryInterface /** @var Collection $result */ $query = $this->user->accounts()->with( ['accountmeta' => function (HasMany $query) { - $query->where('name', 'accountRole'); + $query->where('name', 'account_role'); }] ); - if (\count($types) > 0) { + if (count($types) > 0) { $query->accountTypeIn($types); } $query->where('active', 1); + $query->orderBy('accounts.account_type_id', 'ASC'); + $query->orderBy('accounts.name', 'ASC'); $result = $query->get(['accounts.*']); - $result = $result->sortBy( - function (Account $account) { - return sprintf('%02d', $account->account_type_id) . strtolower($account->name); - } - ); return $result; } @@ -306,40 +303,18 @@ class AccountRepository implements AccountRepositoryInterface return $factory->findOrCreate('Cash account', $type->type); } - /** - * @param $account - * - * @return string - */ - public function getInterestPerDay(Account $account): string - { - $interest = $this->getMetaValue($account, 'interest'); - $interestPeriod = $this->getMetaValue($account, 'interest_period'); - Log::debug(sprintf('Start with interest of %s percent', $interest)); - - // calculate - if ('monthly' === $interestPeriod) { - $interest = bcdiv(bcmul($interest, '12'), '365'); // per year - Log::debug(sprintf('Interest is now (monthly to daily) %s percent', $interest)); - } - if ('yearly' === $interestPeriod) { - $interest = bcdiv($interest, '365'); // per year - Log::debug(sprintf('Interest is now (yearly to daily) %s percent', $interest)); - } - - return $interest; - } - /** * Return meta value for account. Null if not found. * * @param Account $account - * @param string $field + * @param string $field * * @return null|string */ public function getMetaValue(Account $account, string $field): ?string { + // TODO no longer need to loop like this + foreach ($account->accountMeta as $meta) { if ($meta->name === $field) { return (string)$meta->data; @@ -438,6 +413,9 @@ class AccountRepository implements AccountRepositoryInterface /** @var AccountType $type */ $type = AccountType::where('type', AccountType::RECONCILIATION)->first(); $accounts = $this->user->accounts()->where('account_type_id', $type->id)->get(); + + // TODO no longer need to loop like this + /** @var Account $current */ foreach ($accounts as $current) { if ($current->name === $name) { @@ -452,18 +430,6 @@ class AccountRepository implements AccountRepositoryInterface return $account; } - /** - * @param Account $account - * - * @return bool - */ - public function isAsset(Account $account): bool - { - $type = $account->accountType->type; - - return AccountType::ASSET === $type || AccountType::DEFAULT === $type; - } - /** * @param Account $account * @@ -471,48 +437,7 @@ class AccountRepository implements AccountRepositoryInterface */ public function isLiability(Account $account): bool { - return \in_array($account->accountType->type, [AccountType::CREDITCARD, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE], true); - } - - /** - * Returns the date of the very last transaction in this account. - * - * @param Account $account - * - * @return TransactionJournal|null - */ - public function latestJournal(Account $account): ?TransactionJournal - { - $first = $account->transactions() - ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') - ->orderBy('transaction_journals.date', 'DESC') - ->orderBy('transaction_journals.order', 'ASC') - ->where('transaction_journals.user_id', $this->user->id) - ->orderBy('transaction_journals.id', 'DESC') - ->first(['transaction_journals.id']); - if (null !== $first) { - return TransactionJournal::find((int)$first->id); - } - - return null; - } - - /** - * Returns the date of the very last transaction in this account. - * - * @param Account $account - * - * @return Carbon|null - */ - public function latestJournalDate(Account $account): ?Carbon - { - $result = null; - $journal = $this->latestJournal($account); - if (null !== $journal) { - $result = $journal->date; - } - - return $result; + return in_array($account->accountType->type, [AccountType::CREDITCARD, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE], true); } /** @@ -558,19 +483,21 @@ class AccountRepository implements AccountRepositoryInterface /** * @param string $query - * @param array $types + * @param array $types * * @return Collection */ public function searchAccount(string $query, array $types): Collection { - $dbQuery = $this->user->accounts(); - $search = sprintf('%%%s%%', $query); - if (\count($types) > 0) { + $dbQuery = $this->user->accounts()->with(['accountType']); + if ('' !== $query) { + $search = sprintf('%%%s%%', $query); + $dbQuery->where('name', 'LIKE', $search); + } + if (count($types) > 0) { $dbQuery->leftJoin('account_types', 'accounts.account_type_id', '=', 'account_types.id'); $dbQuery->whereIn('account_types.type', $types); } - $dbQuery->where('name', 'LIKE', $search); return $dbQuery->get(['accounts.*']); } @@ -587,7 +514,7 @@ class AccountRepository implements AccountRepositoryInterface * @param array $data * * @return Account - * @throws \FireflyIII\Exceptions\FireflyException + * @throws FireflyException */ public function store(array $data): Account { @@ -600,10 +527,10 @@ class AccountRepository implements AccountRepositoryInterface /** * @param Account $account - * @param array $data + * @param array $data * * @return Account - * @throws \FireflyIII\Exceptions\FireflyException + * @throws FireflyException * @throws FireflyException * @throws FireflyException */ @@ -615,4 +542,32 @@ class AccountRepository implements AccountRepositoryInterface return $account; } + + /** + * @param Account $account + * @return TransactionJournal|null + */ + public function getOpeningBalance(Account $account): ?TransactionJournal + { + return TransactionJournal + ::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') + ->where('transactions.account_id', $account->id) + ->transactionTypes([TransactionType::OPENING_BALANCE]) + ->first(['transaction_journals.*']); + } + + /** + * @param Account $account + * @return TransactionGroup|null + */ + public function getOpeningBalanceGroup(Account $account): ?TransactionGroup + { + $journal = $this->getOpeningBalance($account); + $group = null; + if (null !== $journal) { + $group = $journal->transactionGroup; + } + + return $group; + } } diff --git a/app/Repositories/Account/AccountRepositoryInterface.php b/app/Repositories/Account/AccountRepositoryInterface.php index 7d0382ca10..d9ceecb2e1 100644 --- a/app/Repositories/Account/AccountRepositoryInterface.php +++ b/app/Repositories/Account/AccountRepositoryInterface.php @@ -26,6 +26,7 @@ use Carbon\Carbon; use FireflyIII\Models\Account; use FireflyIII\Models\AccountType; use FireflyIII\Models\TransactionCurrency; +use FireflyIII\Models\TransactionGroup; use FireflyIII\Models\TransactionJournal; use FireflyIII\User; use Illuminate\Support\Collection; @@ -37,6 +38,20 @@ use Illuminate\Support\Collection; interface AccountRepositoryInterface { + /** + * @param Account $account + * + * @return TransactionJournal|null + * + */ + public function getOpeningBalance(Account $account): ?TransactionJournal; + + /** + * @param Account $account + * @return TransactionGroup|null + */ + public function getOpeningBalanceGroup(Account $account): ?TransactionGroup; + /** * Moved here from account CRUD. * @@ -49,7 +64,7 @@ interface AccountRepositoryInterface /** * Moved here from account CRUD. * - * @param Account $account + * @param Account $account * @param Account|null $moveTo * * @return bool @@ -60,7 +75,7 @@ interface AccountRepositoryInterface * Find by account number. Is used. * * @param string $number - * @param array $types + * @param array $types * * @return Account|null */ @@ -68,7 +83,7 @@ interface AccountRepositoryInterface /** * @param string $iban - * @param array $types + * @param array $types * * @return Account|null */ @@ -76,7 +91,7 @@ interface AccountRepositoryInterface /** * @param string $name - * @param array $types + * @param array $types * * @return Account|null */ @@ -138,18 +153,11 @@ interface AccountRepositoryInterface */ public function getCashAccount(): Account; - /** - * @param $account - * - * @return string - */ - public function getInterestPerDay(Account $account): string; - /** * Return meta value for account. Null if not found. * * @param Account $account - * @param string $field + * @param string $field * * @return null|string */ @@ -198,12 +206,6 @@ interface AccountRepositoryInterface */ public function getReconciliation(Account $account): ?Account; - /** - * @param Account $account - * - * @return bool - */ - public function isAsset(Account $account): bool; /** * @param Account $account @@ -212,23 +214,6 @@ interface AccountRepositoryInterface */ public function isLiability(Account $account): bool; - /** - * Returns the date of the very first transaction in this account. - * - * @param Account $account - * - * @return TransactionJournal|null - */ - public function latestJournal(Account $account): ?TransactionJournal; - - /** - * Returns the date of the very last transaction in this account. - * - * @param Account $account - * - * @return Carbon|null - */ - public function latestJournalDate(Account $account): ?Carbon; /** * Returns the date of the very first transaction in this account. @@ -250,7 +235,7 @@ interface AccountRepositoryInterface /** * @param string $query - * @param array $types + * @param array $types * * @return Collection */ @@ -270,7 +255,7 @@ interface AccountRepositoryInterface /** * @param Account $account - * @param array $data + * @param array $data * * @return Account */ diff --git a/app/Repositories/Account/AccountTasker.php b/app/Repositories/Account/AccountTasker.php index 3ea1993058..f4ee5c1086 100644 --- a/app/Repositories/Account/AccountTasker.php +++ b/app/Repositories/Account/AccountTasker.php @@ -23,9 +23,8 @@ declare(strict_types=1); namespace FireflyIII\Repositories\Account; use Carbon\Carbon; -use FireflyIII\Helpers\Collector\TransactionCollectorInterface; +use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Models\Account; -use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; use FireflyIII\User; @@ -46,14 +45,14 @@ class AccountTasker implements AccountTaskerInterface public function __construct() { if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } /** * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end + * @param Carbon $start + * @param Carbon $end * * @return array * @SuppressWarnings(PHPMD.ExcessiveMethodLength) @@ -119,8 +118,8 @@ class AccountTasker implements AccountTaskerInterface } /** - * @param Carbon $start - * @param Carbon $end + * @param Carbon $start + * @param Carbon $end * @param Collection $accounts * * @return array @@ -130,23 +129,15 @@ class AccountTasker implements AccountTaskerInterface // get all expenses for the given accounts in the given period! // also transfers! // get all transactions: - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); + + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setAccounts($accounts)->setRange($start, $end); $collector->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER]) - ->withOpposingAccount(); - $transactions = $collector->getTransactions(); - $transactions = $transactions->filter( - function (Transaction $transaction) { - // return negative amounts only. - if (bccomp($transaction->transaction_amount, '0') === -1) { - return $transaction; - } - - return false; - } - ); - $expenses = $this->groupByOpposing($transactions); + ->withAccountInformation(); + $journals = $collector->getExtractedJournals(); + $expenses = $this->groupByDestination($journals); // sort the result // Obtain a list of columns @@ -161,8 +152,8 @@ class AccountTasker implements AccountTaskerInterface } /** - * @param Carbon $start - * @param Carbon $end + * @param Carbon $start + * @param Carbon $end * @param Collection $accounts * * @return array @@ -172,23 +163,14 @@ class AccountTasker implements AccountTaskerInterface // get all expenses for the given accounts in the given period! // also transfers! // get all transactions: - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); + + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setAccounts($accounts)->setRange($start, $end); $collector->setTypes([TransactionType::DEPOSIT, TransactionType::TRANSFER]) - ->withOpposingAccount(); - $transactions = $collector->getTransactions(); - $transactions = $transactions->filter( - function (Transaction $transaction) { - // return positive amounts only. - if (1 === bccomp($transaction->transaction_amount, '0')) { - return $transaction; - } - - return false; - } - ); - $income = $this->groupByOpposing($transactions); + ->withAccountInformation(); + $income = $this->groupByDestination($collector->getExtractedJournals()); // sort the result // Obtain a list of columns @@ -211,12 +193,12 @@ class AccountTasker implements AccountTaskerInterface } /** - * @param Collection $transactions + * @param array $array * * @return array * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ - private function groupByOpposing(Collection $transactions): array + private function groupByDestination(array $array): array { $defaultCurrency = app('amount')->getDefaultCurrencyByUser($this->user); /** @var CurrencyRepositoryInterface $currencyRepos */ @@ -226,19 +208,19 @@ class AccountTasker implements AccountTaskerInterface $countAccounts = []; // if count remains 0 use original name, not the name with the currency. - /** @var Transaction $transaction */ - foreach ($transactions as $transaction) { - $opposingId = (int)$transaction->opposing_account_id; - $currencyId = (int)$transaction->transaction_currency_id; + /** @var array $journal */ + foreach ($array as $journal) { + $opposingId = (int)$journal['destination_account_id']; + $currencyId = (int)$journal['currency_id']; $key = sprintf('%s-%s', $opposingId, $currencyId); - $name = sprintf('%s (%s)', $transaction->opposing_account_name, $transaction->transaction_currency_code); + $name = sprintf('%s (%s)', $journal['destination_account_name'], $journal['currency_name']); $countAccounts[$opposingId] = isset($countAccounts[$opposingId]) ? $countAccounts[$opposingId] + 1 : 1; if (!isset($expenses[$key])) { $currencies[$currencyId] = $currencies[$currencyId] ?? $currencyRepos->findNull($currencyId); $expenses[$key] = [ 'id' => $opposingId, 'name' => $name, - 'original' => $transaction->opposing_account_name, + 'original' => $journal['destination_account_name'], 'sum' => '0', 'average' => '0', 'currencies' => [], @@ -246,8 +228,8 @@ class AccountTasker implements AccountTaskerInterface 'count' => 0, ]; } - $expenses[$key]['currencies'][] = (int)$transaction->transaction_currency_id; - $expenses[$key]['sum'] = bcadd($expenses[$key]['sum'], $transaction->transaction_amount); + $expenses[$key]['currencies'][] = (int)$journal['currency_id']; + $expenses[$key]['sum'] = bcadd($expenses[$key]['sum'], $journal['amount']); ++$expenses[$key]['count']; } // do averages: @@ -261,8 +243,8 @@ class AccountTasker implements AccountTaskerInterface if ($expenses[$key]['count'] > 1) { $expenses[$key]['average'] = bcdiv($expenses[$key]['sum'], (string)$expenses[$key]['count']); } - $expenses[$key]['currencies'] = \count(array_unique($expenses[$key]['currencies'])); - $expenses[$key]['all_currencies'] = \count($currencies); + $expenses[$key]['currencies'] = count(array_unique($expenses[$key]['currencies'])); + $expenses[$key]['all_currencies'] = count($currencies); } return $expenses; diff --git a/app/Repositories/Account/AccountTaskerInterface.php b/app/Repositories/Account/AccountTaskerInterface.php index 3c8e27a810..7364170bb8 100644 --- a/app/Repositories/Account/AccountTaskerInterface.php +++ b/app/Repositories/Account/AccountTaskerInterface.php @@ -33,16 +33,16 @@ interface AccountTaskerInterface { /** * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end + * @param Carbon $start + * @param Carbon $end * * @return array */ public function getAccountReport(Collection $accounts, Carbon $start, Carbon $end): array; /** - * @param Carbon $start - * @param Carbon $end + * @param Carbon $start + * @param Carbon $end * @param Collection $accounts * * @return array @@ -50,8 +50,8 @@ interface AccountTaskerInterface public function getExpenseReport(Carbon $start, Carbon $end, Collection $accounts): array; /** - * @param Carbon $start - * @param Carbon $end + * @param Carbon $start + * @param Carbon $end * @param Collection $accounts * * @return array diff --git a/app/Repositories/Attachment/AttachmentRepository.php b/app/Repositories/Attachment/AttachmentRepository.php index a237739281..7976c8c0fa 100644 --- a/app/Repositories/Attachment/AttachmentRepository.php +++ b/app/Repositories/Attachment/AttachmentRepository.php @@ -51,7 +51,7 @@ class AttachmentRepository implements AttachmentRepositoryInterface public function __construct() { if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } @@ -90,17 +90,6 @@ class AttachmentRepository implements AttachmentRepositoryInterface return $disk->exists($attachment->fileName()); } - /** - * @param int $attachmentId - * - * @return Attachment|null - */ - public function findWithoutUser(int $attachmentId): ?Attachment - { - - return Attachment::find($attachmentId); - } - /** * @return Collection */ @@ -109,24 +98,6 @@ class AttachmentRepository implements AttachmentRepositoryInterface return $this->user->attachments()->get(); } - /** - * @param Carbon $start - * @param Carbon $end - * - * @return Collection - */ - public function getBetween(Carbon $start, Carbon $end): Collection - { - $query = $this->user - ->attachments() - ->leftJoin('transaction_journals', 'attachments.attachable_id', '=', 'transaction_journals.id') - ->where('transaction_journals.date', '>=', $start->format('Y-m-d')) - ->where('transaction_journals.date', '<=', $end->format('Y-m-d')) - ->get(['attachments.*']); - - return $query; - } - /** * @param Attachment $attachment * diff --git a/app/Repositories/Attachment/AttachmentRepositoryInterface.php b/app/Repositories/Attachment/AttachmentRepositoryInterface.php index b59378c58d..653640a4f0 100644 --- a/app/Repositories/Attachment/AttachmentRepositoryInterface.php +++ b/app/Repositories/Attachment/AttachmentRepositoryInterface.php @@ -48,26 +48,11 @@ interface AttachmentRepositoryInterface */ public function exists(Attachment $attachment): bool; - /** - * @param int $attachmentId - * - * @return Attachment|null - */ - public function findWithoutUser(int $attachmentId): ?Attachment; - /** * @return Collection */ public function get(): Collection; - /** - * @param Carbon $start - * @param Carbon $end - * - * @return Collection - */ - public function getBetween(Carbon $start, Carbon $end): Collection; - /** * @param Attachment $attachment * diff --git a/app/Repositories/Bill/BillRepository.php b/app/Repositories/Bill/BillRepository.php index f7888eb4ce..228193dd05 100644 --- a/app/Repositories/Bill/BillRepository.php +++ b/app/Repositories/Bill/BillRepository.php @@ -54,7 +54,7 @@ class BillRepository implements BillRepositoryInterface public function __construct() { if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } @@ -86,6 +86,37 @@ class BillRepository implements BillRepositoryInterface return $this->user->bills()->find($billId); } + /** + * Find bill by parameters. + * + * @param int|null $billId + * @param string|null $billName + * + * @return Bill|null + */ + public function findBill(?int $billId, ?string $billName): ?Bill + { + if (null !== $billId) { + $searchResult = $this->find((int)$billId); + if (null !== $searchResult) { + Log::debug(sprintf('Found bill based on #%d, will return it.', $billId)); + + return $searchResult; + } + } + if (null !== $billName) { + $searchResult = $this->findByName((string)$billName); + if (null !== $searchResult) { + Log::debug(sprintf('Found bill based on "%s", will return it.', $billName)); + + return $searchResult; + } + } + Log::debug('Found nothing'); + + return null; + } + /** * Find a bill by name. * @@ -97,6 +128,8 @@ class BillRepository implements BillRepositoryInterface { $bills = $this->user->bills()->get(['bills.*']); + // TODO no longer need to loop like this + /** @var Bill $bill */ foreach ($bills as $bill) { if ($bill->name === $name) { @@ -115,8 +148,8 @@ class BillRepository implements BillRepositoryInterface /** @var Collection $set */ $set = $this->user->bills() ->where('active', 1) - ->get(['bills.*', DB::raw('((bills.amount_min + bills.amount_max) / 2) AS expectedAmount'),]) - ->sortBy('name'); + ->orderBy('bills.name', 'ASC') + ->get(['bills.*', DB::raw('((bills.amount_min + bills.amount_max) / 2) AS expectedAmount'),]); return $set; } @@ -139,15 +172,7 @@ class BillRepository implements BillRepositoryInterface public function getBills(): Collection { /** @var Collection $set */ - $set = $this->user->bills()->orderBy('name', 'ASC')->get(); - - $set = $set->sortBy( - function (Bill $bill) { - $int = $bill->active ? 0 : 1; - - return $int . strtolower($bill->name); - } - ); + $set = $this->user->bills()->orderBy('active', 'DESC')->orderBy('name', 'ASC')->get(); return $set; } @@ -179,17 +204,11 @@ class BillRepository implements BillRepositoryInterface ) ->whereIn('transactions.account_id', $ids) ->whereNull('transaction_journals.deleted_at') + ->orderBy('bills.active', 'DESC') + ->orderBy('bills.name', 'ASC') ->groupBy($fields) ->get($fields); - $set = $set->sortBy( - function (Bill $bill) { - $int = $bill->active ? 0 : 1; - - return $int . strtolower($bill->name); - } - ); - return $set; } @@ -526,9 +545,9 @@ class BillRepository implements BillRepositoryInterface * Link a set of journals to a bill. * * @param Bill $bill - * @param Collection $transactions + * @param array $transactions */ - public function linkCollectionToBill(Bill $bill, Collection $transactions): void + public function linkCollectionToBill(Bill $bill, array $transactions): void { /** @var Transaction $transaction */ foreach ($transactions as $transaction) { @@ -559,12 +578,12 @@ class BillRepository implements BillRepositoryInterface } // find the most recent date for this bill NOT in the future. Cache this date: $start = clone $bill->date; - Log::debug('nextDateMatch: Start is ' . $start->format('Y-m-d')); + //Log::debug('nextDateMatch: Start is ' . $start->format('Y-m-d')); while ($start < $date) { - Log::debug(sprintf('$start (%s) < $date (%s)', $start->format('Y-m-d'), $date->format('Y-m-d'))); + //Log::debug(sprintf('$start (%s) < $date (%s)', $start->format('Y-m-d'), $date->format('Y-m-d'))); $start = app('navigation')->addPeriod($start, $bill->repeat_freq, $bill->skip); - Log::debug('Start is now ' . $start->format('Y-m-d')); + //Log::debug('Start is now ' . $start->format('Y-m-d')); } $end = app('navigation')->addPeriod($start, $bill->repeat_freq, $bill->skip); diff --git a/app/Repositories/Bill/BillRepositoryInterface.php b/app/Repositories/Bill/BillRepositoryInterface.php index e90abc44e8..310c4addf5 100644 --- a/app/Repositories/Bill/BillRepositoryInterface.php +++ b/app/Repositories/Bill/BillRepositoryInterface.php @@ -49,6 +49,16 @@ interface BillRepositoryInterface */ public function find(int $billId): ?Bill; + /** + * Find bill by parameters. + * + * @param int|null $billId + * @param string|null $billName + * + * @return Bill|null + */ + public function findBill(?int $billId, ?string $billName): ?Bill; + /** * Find a bill by name. * @@ -212,9 +222,9 @@ interface BillRepositoryInterface * Link a set of journals to a bill. * * @param Bill $bill - * @param Collection $journals + * @param array $transactions */ - public function linkCollectionToBill(Bill $bill, Collection $journals): void; + public function linkCollectionToBill(Bill $bill, array $transactions): void; /** * Given a bill and a date, this method will tell you at which moment this bill expects its next diff --git a/app/Repositories/Budget/BudgetRepository.php b/app/Repositories/Budget/BudgetRepository.php index e079b4edaa..bb612da44c 100644 --- a/app/Repositories/Budget/BudgetRepository.php +++ b/app/Repositories/Budget/BudgetRepository.php @@ -26,14 +26,13 @@ use Carbon\Carbon; use Exception; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Factory\TransactionCurrencyFactory; -use FireflyIII\Helpers\Collector\TransactionCollectorInterface; +use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Models\AccountType; use FireflyIII\Models\AvailableBudget; use FireflyIII\Models\Budget; use FireflyIII\Models\BudgetLimit; use FireflyIII\Models\RuleAction; use FireflyIII\Models\RuleTrigger; -use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionCurrency; use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Account\AccountRepositoryInterface; @@ -62,7 +61,7 @@ class BudgetRepository implements BudgetRepositoryInterface public function __construct() { if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } @@ -97,46 +96,12 @@ class BudgetRepository implements BudgetRepositoryInterface return $avg; } - /** - * @return bool - * @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's 5. - */ - public function cleanupBudgets(): bool - { - // delete limits with amount 0: - try { - BudgetLimit::where('amount', 0)->delete(); - } catch (Exception $e) { - Log::debug(sprintf('Could not delete budget limit: %s', $e->getMessage())); - } - Budget::where('order', 0)->update(['order' => 100]); - - // do the clean up by hand because Sqlite can be tricky with this. - $budgetLimits = BudgetLimit::orderBy('created_at', 'DESC')->get(['id', 'budget_id', 'start_date', 'end_date']); - $count = []; - /** @var BudgetLimit $budgetLimit */ - foreach ($budgetLimits as $budgetLimit) { - $key = $budgetLimit->budget_id . '-' . $budgetLimit->start_date->format('Y-m-d') . $budgetLimit->end_date->format('Y-m-d'); - if (isset($count[$key])) { - // delete it! - try { - BudgetLimit::find($budgetLimit->id)->delete(); - } catch (Exception $e) { - Log::debug(sprintf('Could not delete budget limit: %s', $e->getMessage())); - } - } - $count[$key] = true; - } - - return true; - } - /** * This method collects various info on budgets, used on the budget page and on the index. * * @param Collection $budgets - * @param Carbon $start - * @param Carbon $end + * @param Carbon $start + * @param Carbon $end * * @return array * @@ -178,6 +143,95 @@ class BudgetRepository implements BudgetRepositoryInterface return $return; } + /** + * @param Collection $budgets + * @param Collection $accounts + * @param Carbon $start + * @param Carbon $end + * + * @return string + */ + public function spentInPeriod(Collection $budgets, Collection $accounts, Carbon $start, Carbon $end): string + { + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + + $collector->setUser($this->user); + $collector->setRange($start, $end)->setBudgets($budgets)->withBudgetInformation(); + + if ($accounts->count() > 0) { + $collector->setAccounts($accounts); + } + + return $collector->getSum(); + + } + + /** + * @param Budget $budget + * @param Carbon $start + * @param Carbon $end + * + * @return Collection + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + public function getBudgetLimits(Budget $budget, Carbon $start = null, Carbon $end = null): Collection + { + if (null === $end && null === $start) { + return $budget->budgetlimits()->orderBy('budget_limits.start_date', 'DESC')->get(['budget_limits.*']); + } + if (null === $end xor null === $start) { + $query = $budget->budgetlimits()->orderBy('budget_limits.start_date', 'DESC'); + // one of the two is null + if (null !== $end) { + // end date must be before $end. + $query->where('end_date', '<=', $end->format('Y-m-d 00:00:00')); + } + if (null !== $start) { + // start date must be after $start. + $query->where('start_date', '>=', $start->format('Y-m-d 00:00:00')); + } + $set = $query->get(['budget_limits.*']); + + return $set; + } + + // when both dates are set: + $set = $budget->budgetlimits() + ->where( + function (Builder $q5) use ($start, $end) { + $q5->where( + function (Builder $q1) use ($start, $end) { + // budget limit ends within period + $q1->where( + function (Builder $q2) use ($start, $end) { + $q2->where('budget_limits.end_date', '>=', $start->format('Y-m-d 00:00:00')); + $q2->where('budget_limits.end_date', '<=', $end->format('Y-m-d 00:00:00')); + } + ) + // budget limit start within period + ->orWhere( + function (Builder $q3) use ($start, $end) { + $q3->where('budget_limits.start_date', '>=', $start->format('Y-m-d 00:00:00')); + $q3->where('budget_limits.start_date', '<=', $end->format('Y-m-d 00:00:00')); + } + ); + } + ) + ->orWhere( + function (Builder $q4) use ($start, $end) { + // or start is before start AND end is after end. + $q4->where('budget_limits.start_date', '<=', $start->format('Y-m-d 00:00:00')); + $q4->where('budget_limits.end_date', '>=', $end->format('Y-m-d 00:00:00')); + } + ); + } + )->orderBy('budget_limits.start_date', 'DESC')->get(['budget_limits.*']); + + return $set; + } + /** * @param Budget $budget * @@ -218,6 +272,29 @@ class BudgetRepository implements BudgetRepositoryInterface } } + /** + * @param int|null $budgetId + * @param string|null $budgetName + * + * @return Budget|null + */ + public function findBudget(?int $budgetId, ?string $budgetName): ?Budget + { + Log::debug('Now in findBudget()'); + Log::debug(sprintf('Searching for budget with ID #%d...', $budgetId)); + $result = $this->findNull((int)$budgetId); + if (null === $result && null !== $budgetName && '' !== $budgetName) { + Log::debug(sprintf('Searching for budget with name %s...', $budgetName)); + $result = $this->findByName((string)$budgetName); + } + if (null !== $result) { + Log::debug(sprintf('Found budget #%d: %s', $result->id, $result->name)); + } + Log::debug(sprintf('Found result is null? %s', var_export(null === $result, true))); + + return $result; + } + /** * Find a budget or return NULL * @@ -234,6 +311,23 @@ class BudgetRepository implements BudgetRepositoryInterface return $this->user->budgets()->find($budgetId); } + /** + * Find budget by name. + * + * @param string|null $name + * + * @return Budget|null + */ + public function findByName(?string $name): ?Budget + { + if (null === $name) { + return null; + } + $query = sprintf('%%%s%%', $name); + + return $this->user->budgets()->where('name', 'LIKE', $query)->first(); + } + /** * This method returns the oldest journal or transaction date known to this budget. * Will cache result. @@ -271,16 +365,10 @@ class BudgetRepository implements BudgetRepositoryInterface { /** @var Collection $set */ $set = $this->user->budgets()->where('active', 1) + ->orderBy('order', 'DESC') + ->orderBy('name', 'ASC') ->get(); - $set = $set->sortBy( - function (Budget $budget) { - $str = str_pad((string)$budget->order, 4, '0', STR_PAD_LEFT) . strtolower($budget->name); - - return $str; - } - ); - return $set; } @@ -360,8 +448,8 @@ class BudgetRepository implements BudgetRepositoryInterface /** * @param TransactionCurrency $currency - * @param Carbon $start - * @param Carbon $end + * @param Carbon $start + * @param Carbon $end * * @return string */ @@ -379,6 +467,8 @@ class BudgetRepository implements BudgetRepositoryInterface return $amount; } + /** @noinspection MoreThanThreeArgumentsInspection */ + /** * @param Carbon $start * @param Carbon $end @@ -399,127 +489,21 @@ class BudgetRepository implements BudgetRepositoryInterface return $return; } - /** - * Returns all available budget objects. - * - * @return Collection - */ - public function getAvailableBudgets(): Collection - { - return $this->user->availableBudgets()->get(); - } - - /** - * Calculate the average amount in the budgets available in this period. - * Grouped by day. - * - * @param Carbon $start - * @param Carbon $end - * - * @return string - */ - public function getAverageAvailable(Carbon $start, Carbon $end): string - { - /** @var Collection $list */ - $list = $this->user->availableBudgets() - ->where('start_date', '>=', $start->format('Y-m-d 00:00:00')) - ->where('end_date', '<=', $end->format('Y-m-d 00:00:00')) - ->get(); - if (0 === $list->count()) { - return '0'; - } - $total = '0'; - $days = 0; - /** @var AvailableBudget $availableBudget */ - foreach ($list as $availableBudget) { - $total = bcadd($availableBudget->amount, $total); - $days += $availableBudget->start_date->diffInDays($availableBudget->end_date); - } - - return bcdiv($total, (string)$days); - } - - /** @noinspection MoreThanThreeArgumentsInspection */ - - /** - * @param Budget $budget - * @param Carbon $start - * @param Carbon $end - * - * @return Collection - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - */ - public function getBudgetLimits(Budget $budget, Carbon $start = null, Carbon $end = null): Collection - { - if (null === $end && null === $start) { - return $budget->budgetlimits()->orderBy('budget_limits.start_date', 'DESC')->get(['budget_limits.*']); - } - if (null === $end xor null === $start) { - $query = $budget->budgetlimits()->orderBy('budget_limits.start_date', 'DESC'); - // one of the two is null - if (null !== $end) { - // end date must be before $end. - $query->where('end_date', '<=', $end->format('Y-m-d 00:00:00')); - } - if (null !== $start) { - // start date must be after $start. - $query->where('start_date', '>=', $start->format('Y-m-d 00:00:00')); - } - $set = $query->get(['budget_limits.*']); - - return $set; - } - - // when both dates are set: - $set = $budget->budgetlimits() - ->where( - function (Builder $q5) use ($start, $end) { - $q5->where( - function (Builder $q1) use ($start, $end) { - // budget limit ends within period - $q1->where( - function (Builder $q2) use ($start, $end) { - $q2->where('budget_limits.end_date', '>=', $start->format('Y-m-d 00:00:00')); - $q2->where('budget_limits.end_date', '<=', $end->format('Y-m-d 00:00:00')); - } - ) - // budget limit start within period - ->orWhere( - function (Builder $q3) use ($start, $end) { - $q3->where('budget_limits.start_date', '>=', $start->format('Y-m-d 00:00:00')); - $q3->where('budget_limits.start_date', '<=', $end->format('Y-m-d 00:00:00')); - } - ); - } - ) - ->orWhere( - function (Builder $q4) use ($start, $end) { - // or start is before start AND end is after end. - $q4->where('budget_limits.start_date', '<=', $start->format('Y-m-d 00:00:00')); - $q4->where('budget_limits.end_date', '>=', $end->format('Y-m-d 00:00:00')); - } - ); - } - )->orderBy('budget_limits.start_date', 'DESC')->get(['budget_limits.*']); - - return $set; - } - /** * This method is being used to generate the budget overview in the year/multi-year report. Its used * in both the year/multi-year budget overview AND in the accompanying chart. * * @param Collection $budgets * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end + * @param Carbon $start + * @param Carbon $end * * @return array */ public function getBudgetPeriodReport(Collection $budgets, Collection $accounts, Carbon $start, Carbon $end): array { $carbonFormat = Navigation::preferredCarbonFormat($start, $end); + $data = []; // prep data array: /** @var Budget $budget */ @@ -532,20 +516,19 @@ class BudgetRepository implements BudgetRepositoryInterface } // get all transactions: - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); $collector->setAccounts($accounts)->setRange($start, $end); $collector->setBudgets($budgets); - $transactions = $collector->getTransactions(); + $journals = $collector->getExtractedJournals(); // loop transactions: - /** @var Transaction $transaction */ - foreach ($transactions as $transaction) { - $budgetId = max((int)$transaction->transaction_journal_budget_id, (int)$transaction->transaction_budget_id); - $date = $transaction->date->format($carbonFormat); - $data[$budgetId]['entries'][$date] = bcadd($data[$budgetId]['entries'][$date] ?? '0', $transaction->transaction_amount); + /** @var array $journal */ + foreach ($journals as $journal) { + $budgetId = (int)$journal['budget_id']; + $date = $journal['date']->format($carbonFormat); + $data[$budgetId]['entries'][$date] = bcadd($data[$budgetId]['entries'][$date] ?? '0', $journal['amount']); } - return $data; } @@ -555,19 +538,14 @@ class BudgetRepository implements BudgetRepositoryInterface public function getBudgets(): Collection { /** @var Collection $set */ - $set = $this->user->budgets()->get(); - - $set = $set->sortBy( - function (Budget $budget) { - $str = str_pad((string)$budget->order, 4, '0', STR_PAD_LEFT) . strtolower($budget->name); - - return $str; - } - ); + $set = $this->user->budgets()->orderBy('order', 'DESC') + ->orderBy('name', 'ASC')->get(); return $set; } + /** @noinspection MoreThanThreeArgumentsInspection */ + /** * Get all budgets with these ID's. * @@ -586,15 +564,8 @@ class BudgetRepository implements BudgetRepositoryInterface public function getInactiveBudgets(): Collection { /** @var Collection $set */ - $set = $this->user->budgets()->where('active', 0)->get(); - - $set = $set->sortBy( - function (Budget $budget) { - $str = str_pad((string)$budget->order, 4, '0', STR_PAD_LEFT) . strtolower($budget->name); - - return $str; - } - ); + $set = $this->user->budgets()->orderBy('order', 'DESC') + ->orderBy('name', 'ASC')->where('active', 0)->get(); return $set; } @@ -603,33 +574,36 @@ class BudgetRepository implements BudgetRepositoryInterface /** * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end + * @param Carbon $start + * @param Carbon $end * * @return array */ public function getNoBudgetPeriodReport(Collection $accounts, Carbon $start, Carbon $end): array { $carbonFormat = Navigation::preferredCarbonFormat($start, $end); - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); + + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setAccounts($accounts)->setRange($start, $end); $collector->setTypes([TransactionType::WITHDRAWAL]); $collector->withoutBudget(); - $transactions = $collector->getTransactions(); - $result = [ + $journals = $collector->getExtractedJournals(); + $result = [ 'entries' => [], 'name' => (string)trans('firefly.no_budget'), 'sum' => '0', ]; - foreach ($transactions as $transaction) { - $date = $transaction->date->format($carbonFormat); + /** @var array $journal */ + foreach ($journals as $journal) { + $date = $journal['date']->format($carbonFormat); if (!isset($result['entries'][$date])) { $result['entries'][$date] = '0'; } - $result['entries'][$date] = bcadd($result['entries'][$date], $transaction->transaction_amount); + $result['entries'][$date] = bcadd($result['entries'][$date], $journal['amount']); } return $result; @@ -642,18 +616,20 @@ class BudgetRepository implements BudgetRepositoryInterface */ public function searchBudget(string $query): Collection { - $query = sprintf('%%%s%%', $query); - return $this->user->budgets()->where('name', 'LIKE', $query)->get(); + $search = $this->user->budgets(); + if ('' !== $query) { + $search->where('name', 'LIKE', sprintf('%%%s%%', $query)); + } + + return $search->get(); } - /** @noinspection MoreThanThreeArgumentsInspection */ - /** * @param TransactionCurrency $currency - * @param Carbon $start - * @param Carbon $end - * @param string $amount + * @param Carbon $start + * @param Carbon $end + * @param string $amount * * @return AvailableBudget */ @@ -678,7 +654,7 @@ class BudgetRepository implements BudgetRepositoryInterface /** * @param Budget $budget - * @param int $order + * @param int $order */ public function setBudgetOrder(Budget $budget, int $order): void { @@ -697,73 +673,56 @@ class BudgetRepository implements BudgetRepositoryInterface /** * @param Collection $budgets * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end - * - * @return string - */ - public function spentInPeriod(Collection $budgets, Collection $accounts, Carbon $start, Carbon $end): string - { - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setUser($this->user); - $collector->setRange($start, $end)->setBudgets($budgets)->withBudgetInformation(); - - if ($accounts->count() > 0) { - $collector->setAccounts($accounts); - } - if (0 === $accounts->count()) { - $collector->setAllAssetAccounts(); - } - - $set = $collector->getTransactions(); - - return (string)$set->sum('transaction_amount'); - } - - /** - * @param Collection $budgets - * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end + * @param Carbon $start + * @param Carbon $end + * TODO refactor me. * * @return array */ public function spentInPeriodMc(Collection $budgets, Collection $accounts, Carbon $start, Carbon $end): array { - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); $collector->setUser($this->user); $collector->setRange($start, $end)->setBudgets($budgets)->withBudgetInformation(); if ($accounts->count() > 0) { $collector->setAccounts($accounts); } - if (0 === $accounts->count()) { - $collector->setAllAssetAccounts(); - } - - $set = $collector->getTransactions(); + // TODO possible candidate for getExtractedGroups + $set = $collector->getGroups(); $return = []; $total = []; $currencies = []; - /** @var Transaction $transaction */ - foreach ($set as $transaction) { - $code = $transaction->transaction_currency_code; - if (!isset($currencies[$code])) { - $currencies[$code] = $transaction->transactionCurrency; + /** @var array $group */ + foreach ($set as $group) { + /** @var array $transaction */ + foreach ($group['transactions'] as $transaction) { + $code = $transaction['currency_code']; + if (!isset($currencies[$code])) { + $currencies[$code] = [ + 'id' => $transaction['currency_id'], + 'decimal_places' => $transaction['currency_decimal_places'], + 'code' => $transaction['currency_code'], + 'symbol' => $transaction['currency_symbol'], + ]; + } + $total[$code] = isset($total[$code]) ? bcadd($total[$code], $transaction['amount']) : $transaction['amount']; } - $total[$code] = isset($total[$code]) ? bcadd($total[$code], $transaction->transaction_amount) : $transaction->transaction_amount; } + /** + * @var string $code + * @var string $spent + */ foreach ($total as $code => $spent) { /** @var TransactionCurrency $currency */ $currency = $currencies[$code]; $return[] = [ - 'currency_id' => $currency->id, + 'currency_id' => $currency['id'], 'currency_code' => $code, - 'currency_symbol' => $currency->symbol, - 'currency_decimal_places' => $currency->decimal_places, - 'amount' => round($spent, $currency->decimal_places), + 'currency_symbol' => $currency['symbol'], + 'currency_decimal_places' => $currency['decimal_places'], + 'amount' => round($spent, $currency['decimal_places']), ]; } @@ -772,81 +731,70 @@ class BudgetRepository implements BudgetRepositoryInterface /** * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end + * @param Carbon $start + * @param Carbon $end * * @return string */ public function spentInPeriodWoBudget(Collection $accounts, Carbon $start, Carbon $end): string { - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); $collector->setUser($this->user); - $collector->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])->withoutBudget(); + $collector->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL]) + ->withoutBudget(); if ($accounts->count() > 0) { $collector->setAccounts($accounts); } - if (0 === $accounts->count()) { - $collector->setAllAssetAccounts(); - } - $set = $collector->getTransactions(); - $set = $set->filter( - function (Transaction $transaction) { - if (bccomp($transaction->transaction_amount, '0') === -1) { - return $transaction; - } - - return null; - } - ); - - return (string)$set->sum('transaction_amount'); + return $collector->getSum(); } /** * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end + * @param Carbon $start + * @param Carbon $end * * @return array */ public function spentInPeriodWoBudgetMc(Collection $accounts, Carbon $start, Carbon $end): array { - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setUser($this->user); $collector->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])->withoutBudget(); if ($accounts->count() > 0) { $collector->setAccounts($accounts); } - if (0 === $accounts->count()) { - $collector->setAllAssetAccounts(); - } - - $set = $collector->getTransactions(); + $journals = $collector->getExtractedJournals(); $return = []; $total = []; $currencies = []; - /** @var Transaction $transaction */ - foreach ($set as $transaction) { - $code = $transaction->transaction_currency_code; + /** @var array $journal */ + foreach ($journals as $journal) { + $code = $journal['currency_code']; if (!isset($currencies[$code])) { - $currencies[$code] = $transaction->transactionCurrency; + $currencies[$code] = [ + 'id' => $journal['currency_id'], + 'name' => $journal['currency_name'], + 'symbol' => $journal['currency_symbol'], + 'decimal_places' => $journal['currency_decimal_places'], + ]; } - $total[$code] = isset($total[$code]) ? bcadd($total[$code], $transaction->transaction_amount) : $transaction->transaction_amount; + $total[$code] = isset($total[$code]) ? bcadd($total[$code], $journal['amount']) : $journal['amount']; } foreach ($total as $code => $spent) { /** @var TransactionCurrency $currency */ $currency = $currencies[$code]; $return[] = [ - 'currency_id' => $currency->id, + 'currency_id' => $currency['id'], 'currency_code' => $code, - 'currency_symbol' => $currency->symbol, - 'currency_decimal_places' => $currency->decimal_places, - 'amount' => round($spent, $currency->decimal_places), + 'currency_symbol' => $currency['symbol'], + 'currency_decimal_places' => $currency['decimal_places'], + 'amount' => round($spent, $currency['decimal_places']), ]; } @@ -871,6 +819,8 @@ class BudgetRepository implements BudgetRepositoryInterface return $newBudget; } + /** @noinspection MoreThanThreeArgumentsInspection */ + /** * @param array $data * @@ -915,11 +865,43 @@ class BudgetRepository implements BudgetRepositoryInterface return $limit; } - /** @noinspection MoreThanThreeArgumentsInspection */ + /** + * @return bool + * @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's 5. + */ + public function cleanupBudgets(): bool + { + // delete limits with amount 0: + try { + BudgetLimit::where('amount', 0)->delete(); + } catch (Exception $e) { + Log::debug(sprintf('Could not delete budget limit: %s', $e->getMessage())); + } + Budget::where('order', 0)->update(['order' => 100]); + + // do the clean up by hand because Sqlite can be tricky with this. + $budgetLimits = BudgetLimit::orderBy('created_at', 'DESC')->get(['id', 'budget_id', 'start_date', 'end_date']); + $count = []; + /** @var BudgetLimit $budgetLimit */ + foreach ($budgetLimits as $budgetLimit) { + $key = $budgetLimit->budget_id . '-' . $budgetLimit->start_date->format('Y-m-d') . $budgetLimit->end_date->format('Y-m-d'); + if (isset($count[$key])) { + // delete it! + try { + BudgetLimit::find($budgetLimit->id)->delete(); + } catch (Exception $e) { + Log::debug(sprintf('Could not delete budget limit: %s', $e->getMessage())); + } + } + $count[$key] = true; + } + + return true; + } /** * @param Budget $budget - * @param array $data + * @param array $data * * @return Budget */ @@ -938,7 +920,7 @@ class BudgetRepository implements BudgetRepositoryInterface /** * @param AvailableBudget $availableBudget - * @param array $data + * @param array $data * * @return AvailableBudget * @throws FireflyException @@ -967,7 +949,7 @@ class BudgetRepository implements BudgetRepositoryInterface /** * @param BudgetLimit $budgetLimit - * @param array $data + * @param array $data * * @return BudgetLimit * @throws Exception @@ -1072,24 +1054,52 @@ class BudgetRepository implements BudgetRepositoryInterface } /** - * @param string $oldName - * @param string $newName + * Returns all available budget objects. + * + * @param Carbon|null $start + * @param Carbon|null $end + * @return Collection + * */ - private function updateRuleActions(string $oldName, string $newName): void + public function getAvailableBudgetsByDate(?Carbon $start, ?Carbon $end): Collection { - $types = ['set_budget',]; - $actions = RuleAction::leftJoin('rules', 'rules.id', '=', 'rule_actions.rule_id') - ->where('rules.user_id', $this->user->id) - ->whereIn('rule_actions.action_type', $types) - ->where('rule_actions.action_value', $oldName) - ->get(['rule_actions.*']); - Log::debug(sprintf('Found %d actions to update.', $actions->count())); - /** @var RuleAction $action */ - foreach ($actions as $action) { - $action->action_value = $newName; - $action->save(); - Log::debug(sprintf('Updated action %d: %s', $action->id, $action->action_value)); + $query = $this->user->availableBudgets(); + + if (null !== $start) { + $query->where('start_date', '>=', $start->format('Y-m-d H:i:s')); } + if (null !== $end) { + $query->where('emd_date', '<=', $end->format('Y-m-d H:i:s')); + } + + return $query->get(); + } + + /** + * Returns all available budget objects. + * + * @param TransactionCurrency $currency + * @return Collection + */ + public function getAvailableBudgetsByCurrency(TransactionCurrency $currency): Collection + { + return $this->user->availableBudgets()->where('transaction_currency_id', $currency->id)->get(); + } + + /** + * @param TransactionCurrency $currency + * @param Carbon $start + * @param Carbon $end + * + * @return Collection + */ + public function getAllBudgetLimitsByCurrency(TransactionCurrency $currency, Carbon $start = null, Carbon $end = null): Collection + { + return $this->getAllBudgetLimits($start, $end)->filter( + function (BudgetLimit $budgetLimit) use ($currency) { + return $budgetLimit->transaction_currency_id === $currency->id; + } + ); } /** @@ -1112,4 +1122,25 @@ class BudgetRepository implements BudgetRepositoryInterface Log::debug(sprintf('Updated trigger %d: %s', $trigger->id, $trigger->trigger_value)); } } + + /** + * @param string $oldName + * @param string $newName + */ + private function updateRuleActions(string $oldName, string $newName): void + { + $types = ['set_budget',]; + $actions = RuleAction::leftJoin('rules', 'rules.id', '=', 'rule_actions.rule_id') + ->where('rules.user_id', $this->user->id) + ->whereIn('rule_actions.action_type', $types) + ->where('rule_actions.action_value', $oldName) + ->get(['rule_actions.*']); + Log::debug(sprintf('Found %d actions to update.', $actions->count())); + /** @var RuleAction $action */ + foreach ($actions as $action) { + $action->action_value = $newName; + $action->save(); + Log::debug(sprintf('Updated action %d: %s', $action->id, $action->action_value)); + } + } } diff --git a/app/Repositories/Budget/BudgetRepositoryInterface.php b/app/Repositories/Budget/BudgetRepositoryInterface.php index d61c5ed5ee..0a59706dd8 100644 --- a/app/Repositories/Budget/BudgetRepositoryInterface.php +++ b/app/Repositories/Budget/BudgetRepositoryInterface.php @@ -35,6 +35,7 @@ use Illuminate\Support\Collection; */ interface BudgetRepositoryInterface { + /** * A method that returns the amount of money budgeted per day for this budget, * on average. @@ -54,8 +55,8 @@ interface BudgetRepositoryInterface * This method collects various info on budgets, used on the budget page and on the index. * * @param Collection $budgets - * @param Carbon $start - * @param Carbon $end + * @param Carbon $start + * @param Carbon $end * * @return array */ @@ -80,6 +81,23 @@ interface BudgetRepositoryInterface */ public function destroyBudgetLimit(BudgetLimit $budgetLimit): void; + /** + * @param int|null $budgetId + * @param string|null $budgetName + * + * @return Budget|null + */ + public function findBudget(?int $budgetId, ?string $budgetName): ?Budget; + + /** + * Find budget by name. + * + * @param string|null $name + * + * @return Budget|null + */ + public function findByName(?string $name): ?Budget; + /** * @param int|null $budgetId * @@ -112,8 +130,17 @@ interface BudgetRepositoryInterface /** * @param TransactionCurrency $currency - * @param Carbon $start - * @param Carbon $end + * @param Carbon $start + * @param Carbon $end + * + * @return Collection + */ + public function getAllBudgetLimitsByCurrency(TransactionCurrency $currency, Carbon $start = null, Carbon $end = null): Collection; + + /** + * @param TransactionCurrency $currency + * @param Carbon $start + * @param Carbon $end * * @return string */ @@ -130,20 +157,20 @@ interface BudgetRepositoryInterface /** * Returns all available budget objects. * + * @param Carbon|null $start + * @param Carbon|null $end * @return Collection + * */ - public function getAvailableBudgets(): Collection; + public function getAvailableBudgetsByDate(?Carbon $start, ?Carbon $end): Collection; /** - * Calculate the average amount in the budgets available in this period. - * Grouped by day. + * Returns all available budget objects. * - * @param Carbon $start - * @param Carbon $end - * - * @return string + * @param TransactionCurrency $currency + * @return Collection */ - public function getAverageAvailable(Carbon $start, Carbon $end): string; + public function getAvailableBudgetsByCurrency(TransactionCurrency $currency): Collection; /** * @param Budget $budget @@ -157,8 +184,8 @@ interface BudgetRepositoryInterface /** * @param Collection $budgets * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end + * @param Carbon $start + * @param Carbon $end * * @return array */ @@ -187,8 +214,8 @@ interface BudgetRepositoryInterface /** * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end + * @param Carbon $start + * @param Carbon $end * * @return array */ @@ -203,9 +230,9 @@ interface BudgetRepositoryInterface /** * @param TransactionCurrency $currency - * @param Carbon $start - * @param Carbon $end - * @param string $amount + * @param Carbon $start + * @param Carbon $end + * @param string $amount * * @return AvailableBudget */ @@ -213,7 +240,7 @@ interface BudgetRepositoryInterface /** * @param Budget $budget - * @param int $order + * @param int $order */ public function setBudgetOrder(Budget $budget, int $order): void; @@ -227,8 +254,8 @@ interface BudgetRepositoryInterface /** * @param Collection $budgets * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end + * @param Carbon $start + * @param Carbon $end * * @return string */ @@ -241,8 +268,8 @@ interface BudgetRepositoryInterface * * @param Collection $budgets * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end + * @param Carbon $start + * @param Carbon $end * * @return array */ @@ -250,8 +277,8 @@ interface BudgetRepositoryInterface /** * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end + * @param Carbon $start + * @param Carbon $end * * @return string */ @@ -259,8 +286,8 @@ interface BudgetRepositoryInterface /** * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end + * @param Carbon $start + * @param Carbon $end * * @return array */ @@ -283,7 +310,7 @@ interface BudgetRepositoryInterface /** * @param Budget $budget - * @param array $data + * @param array $data * * @return Budget */ @@ -291,7 +318,7 @@ interface BudgetRepositoryInterface /** * @param AvailableBudget $availableBudget - * @param array $data + * @param array $data * * @return AvailableBudget */ @@ -299,7 +326,7 @@ interface BudgetRepositoryInterface /** * @param BudgetLimit $budgetLimit - * @param array $data + * @param array $data * * @return BudgetLimit */ diff --git a/app/Repositories/Category/CategoryRepository.php b/app/Repositories/Category/CategoryRepository.php index 24f695623b..d44fdffe42 100644 --- a/app/Repositories/Category/CategoryRepository.php +++ b/app/Repositories/Category/CategoryRepository.php @@ -24,9 +24,8 @@ namespace FireflyIII\Repositories\Category; use Carbon\Carbon; use FireflyIII\Factory\CategoryFactory; -use FireflyIII\Helpers\Collector\TransactionCollectorInterface; +use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Models\Category; -use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionType; use FireflyIII\Services\Internal\Destroy\CategoryDestroyService; use FireflyIII\Services\Internal\Update\CategoryUpdateService; @@ -53,7 +52,7 @@ class CategoryRepository implements CategoryRepositoryInterface public function __construct() { if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } @@ -77,8 +76,8 @@ class CategoryRepository implements CategoryRepositoryInterface /** * @param Collection $categories * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end + * @param Carbon $start + * @param Carbon $end * * @return string */ @@ -86,34 +85,30 @@ class CategoryRepository implements CategoryRepositoryInterface { $set = $this->earnedInPeriodCollection($categories, $accounts, $start, $end); - return (string)$set->sum('transaction_amount'); + return $this->sumJournals($set); } /** @noinspection MoreThanThreeArgumentsInspection */ /** * @param Collection $categories * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end + * @param Carbon $start + * @param Carbon $end * - * @return Collection + * @return array */ - public function earnedInPeriodCollection(Collection $categories, Collection $accounts, Carbon $start, Carbon $end): Collection + public function earnedInPeriodCollection(Collection $categories, Collection $accounts, Carbon $start, Carbon $end): array { - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); $collector->setUser($this->user); if (0 !== $accounts->count()) { $collector->setAccounts($accounts); } - if (0 === $accounts->count()) { - $collector->setAllAssetAccounts(); - } - $collector->setRange($start, $end)->setTypes([TransactionType::DEPOSIT])->setCategories($categories); - return $collector->getTransactions(); + return $collector->getExtractedJournals(); } /** @@ -122,50 +117,37 @@ class CategoryRepository implements CategoryRepositoryInterface * Get me the amount earned in this period, grouped per currency, where no category was set. * * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end + * @param Carbon $start + * @param Carbon $end * * @return array */ public function earnedInPeriodPcWoCategory(Collection $accounts, Carbon $start, Carbon $end): array { - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setUser($this->user); $collector->setRange($start, $end)->setTypes([TransactionType::DEPOSIT])->withoutCategory(); if ($accounts->count() > 0) { $collector->setAccounts($accounts); } - if (0 === $accounts->count()) { - $collector->setAllAssetAccounts(); - } + $journals = $collector->getExtractedJournals(); + $return = []; - $set = $collector->getTransactions(); - $set = $set->filter( - function (Transaction $transaction) { - if (bccomp($transaction->transaction_amount, '0') === 1) { - return $transaction; - } - - return null; - } - ); - - $return = []; - /** @var Transaction $transaction */ - foreach ($set as $transaction) { - $currencyId = $transaction->transaction_currency_id; + foreach ($journals as $journal) { + $currencyId = (int)$journal['currency_id']; if (!isset($return[$currencyId])) { $return[$currencyId] = [ - 'spent' => '0', + 'earned' => '0', 'currency_id' => $currencyId, - 'currency_symbol' => $transaction->transaction_currency_symbol, - 'currency_code' => $transaction->transaction_currency_code, - 'currency_decimal_places' => $transaction->transaction_currency_dp, + 'currency_symbol' => $journal['currency_symbol'], + 'currency_code' => $journal['currency_code'], + 'currency_decimal_places' => $journal['currency_decimal_places'], ]; } - $return[$currencyId]['spent'] = bcadd($return[$currencyId]['spent'], $transaction->transaction_amount); + $return[$currencyId]['earned'] = bcadd($return[$currencyId]['earned'], $journal['amount']); } return $return; @@ -174,15 +156,16 @@ class CategoryRepository implements CategoryRepositoryInterface /** * @param Collection $categories * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end + * @param Carbon $start + * @param Carbon $end * * @return array */ public function earnedInPeriodPerCurrency(Collection $categories, Collection $accounts, Carbon $start, Carbon $end): array { - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setUser($this->user); $collector->setRange($start, $end)->setTypes([TransactionType::DEPOSIT]); @@ -196,20 +179,12 @@ class CategoryRepository implements CategoryRepositoryInterface if ($accounts->count() > 0) { $collector->setAccounts($accounts); } - if (0 === $accounts->count()) { - $collector->setAllAssetAccounts(); - } - - $set = $collector->getTransactions(); - $return = []; - /** @var Transaction $transaction */ - foreach ($set as $transaction) { - $jrnlCatId = (int)$transaction->transaction_journal_category_id; - $transCatId = (int)$transaction->transaction_category_id; - $categoryId = max($jrnlCatId, $transCatId); - $currencyId = (int)$transaction->transaction_currency_id; - $name = $transaction->transaction_category_name; - $name = '' === (string)$name ? $transaction->transaction_journal_category_name : $name; + $journals = $collector->getExtractedJournals(); + $return = []; + foreach ($journals as $journal) { + $categoryId = (int)$journal['category_id']; + $currencyId = (int)$journal['currency_id']; + $name = $journal['category_name']; // make array for category: if (!isset($return[$categoryId])) { $return[$categoryId] = [ @@ -221,35 +196,56 @@ class CategoryRepository implements CategoryRepositoryInterface $return[$categoryId]['earned'][$currencyId] = [ 'earned' => '0', 'currency_id' => $currencyId, - 'currency_symbol' => $transaction->transaction_currency_symbol, - 'currency_code' => $transaction->transaction_currency_code, - 'currency_decimal_places' => $transaction->transaction_currency_dp, + 'currency_symbol' => $journal['currency_symbol'], + 'currency_code' => $journal['currency_code'], + 'currency_decimal_places' => $journal['currency_decimal_places'], ]; } $return[$categoryId]['earned'][$currencyId]['earned'] - = bcadd($return[$categoryId]['earned'][$currencyId]['earned'], $transaction->transaction_amount); + = bcadd($return[$categoryId]['earned'][$currencyId]['earned'], $journal['amount']); } return $return; } /** - * Find a category. + * Returns a list of all the categories belonging to a user. * - * @param string $name + * @return Collection + */ + public function getCategories(): Collection + { + /** @var Collection $set */ + $set = $this->user->categories()->orderBy('name', 'ASC')->get(); + + return $set; + } + + /** + * @param int|null $categoryId + * @param string|null $categoryName * * @return Category|null */ - public function findByName(string $name): ?Category + public function findCategory(?int $categoryId, ?string $categoryName): ?Category { - $categories = $this->user->categories()->get(['categories.*']); - foreach ($categories as $category) { - if ($category->name === $name) { - return $category; + Log::debug('Now in findCategory()'); + Log::debug(sprintf('Searching for category with ID #%d...', $categoryId)); + $result = $this->findNull((int)$categoryId); + if (null === $result) { + Log::debug(sprintf('Searching for category with name %s...', $categoryName)); + $result = $this->findByName((string)$categoryName); + if (null === $result && '' !== (string)$categoryName) { + // create it! + $result = $this->store(['name' => $categoryName]); } } + if (null !== $result) { + Log::debug(sprintf('Found category #%d: %s', $result->id, $result->name)); + } + Log::debug(sprintf('Found category result is null? %s', var_export(null === $result, true))); - return null; + return $result; } /** @@ -264,6 +260,46 @@ class CategoryRepository implements CategoryRepositoryInterface return $this->user->categories()->find($categoryId); } + /** @noinspection MoreThanThreeArgumentsInspection */ + + /** + * Find a category. + * + * @param string $name + * + * @return Category|null + */ + public function findByName(string $name): ?Category + { + $categories = $this->user->categories()->get(['categories.*']); + + // TODO no longer need to loop like this + + foreach ($categories as $category) { + if ($category->name === $name) { + return $category; + } + } + + return null; + } + + /** + * @param array $data + * + * @return Category + */ + public function store(array $data): Category + { + /** @var CategoryFactory $factory */ + $factory = app(CategoryFactory::class); + $factory->setUser($this->user); + + return $factory->findOrCreate(null, $data['name']); + } + + /** @noinspection MoreThanThreeArgumentsInspection */ + /** * @param Category $category * @@ -292,8 +328,6 @@ class CategoryRepository implements CategoryRepositoryInterface return $firstJournalDate; } - /** @noinspection MoreThanThreeArgumentsInspection */ - /** * Get all categories with ID's. * @@ -307,27 +341,7 @@ class CategoryRepository implements CategoryRepositoryInterface } /** - * Returns a list of all the categories belonging to a user. - * - * @return Collection - */ - public function getCategories(): Collection - { - /** @var Collection $set */ - $set = $this->user->categories()->orderBy('name', 'ASC')->get(); - $set = $set->sortBy( - function (Category $category) { - return strtolower($category->name); - } - ); - - return $set; - } - - /** @noinspection MoreThanThreeArgumentsInspection */ - - /** - * @param Category $category + * @param Category $category * @param Collection $accounts * * @return Carbon|null @@ -356,11 +370,13 @@ class CategoryRepository implements CategoryRepositoryInterface return $lastJournalDate; } + /** @noinspection MoreThanThreeArgumentsInspection */ + /** * @param Collection $categories * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end + * @param Carbon $start + * @param Carbon $end * * @return array */ @@ -379,61 +395,59 @@ class CategoryRepository implements CategoryRepositoryInterface } // get all transactions: - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setAccounts($accounts)->setRange($start, $end); $collector->setCategories($categories)->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER]) - ->withOpposingAccount(); - $transactions = $collector->getTransactions(); + ->withAccountInformation()->withCategoryInformation(); + $journals = $collector->getExtractedJournals(); // loop transactions: - /** @var Transaction $transaction */ - foreach ($transactions as $transaction) { - // if positive, skip: - if (1 === bccomp($transaction->transaction_amount, '0')) { - continue; - } - $categoryId = max((int)$transaction->transaction_journal_category_id, (int)$transaction->transaction_category_id); - $date = $transaction->date->format($carbonFormat); - $data[$categoryId]['entries'][$date] = bcadd($data[$categoryId]['entries'][$date] ?? '0', $transaction->transaction_amount); + + foreach ($journals as $journal) { + $categoryId = (int)$journal['category_id']; + $date = $journal['date']->format($carbonFormat); + $data[$categoryId]['entries'][$date] = bcadd($data[$categoryId]['entries'][$date] ?? '0', $journal['amount']); } return $data; } + /** @noinspection MoreThanThreeArgumentsInspection */ + /** * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end + * @param Carbon $start + * @param Carbon $end * * @return array */ public function periodExpensesNoCategory(Collection $accounts, Carbon $start, Carbon $end): array { $carbonFormat = Navigation::preferredCarbonFormat($start, $end); - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setAccounts($accounts)->setRange($start, $end)->withOpposingAccount(); + + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + + $collector->setAccounts($accounts)->setRange($start, $end)->withAccountInformation(); $collector->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER]); $collector->withoutCategory(); - $transactions = $collector->getTransactions(); - $result = [ + $journals = $collector->getExtractedJournals(); + $result = [ 'entries' => [], 'name' => (string)trans('firefly.no_category'), 'sum' => '0', ]; - foreach ($transactions as $transaction) { - // if positive, skip: - if (1 === bccomp($transaction->transaction_amount, '0')) { - continue; - } - $date = $transaction->date->format($carbonFormat); + /** @var array $journal */ + foreach ($journals as $journal) { + $date = $journal['date']->format($carbonFormat); if (!isset($result['entries'][$date])) { $result['entries'][$date] = '0'; } - $result['entries'][$date] = bcadd($result['entries'][$date], $transaction->transaction_amount); + $result['entries'][$date] = bcadd($result['entries'][$date], $journal['amount']); } return $result; @@ -444,8 +458,8 @@ class CategoryRepository implements CategoryRepositoryInterface /** * @param Collection $categories * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end + * @param Carbon $start + * @param Carbon $end * * @return array */ @@ -464,34 +478,29 @@ class CategoryRepository implements CategoryRepositoryInterface } // get all transactions: - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setAccounts($accounts)->setRange($start, $end); $collector->setCategories($categories)->setTypes([TransactionType::DEPOSIT, TransactionType::TRANSFER]) - ->withOpposingAccount(); - $transactions = $collector->getTransactions(); + ->withAccountInformation(); + $journals = $collector->getExtractedJournals(); // loop transactions: - /** @var Transaction $transaction */ - foreach ($transactions as $transaction) { - // if negative, skip: - if (bccomp($transaction->transaction_amount, '0') === -1) { - continue; - } - $categoryId = max((int)$transaction->transaction_journal_category_id, (int)$transaction->transaction_category_id); - $date = $transaction->date->format($carbonFormat); - $data[$categoryId]['entries'][$date] = bcadd($data[$categoryId]['entries'][$date] ?? '0', $transaction->transaction_amount); + /** @var array $journal */ + foreach ($journals as $journal) { + $categoryId = (int)$journal['category_id']; + $date = $journal['date']->format($carbonFormat); + $data[$categoryId]['entries'][$date] = bcadd($data[$categoryId]['entries'][$date] ?? '0', $journal['amount']); } return $data; } - /** @noinspection MoreThanThreeArgumentsInspection */ - /** * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end + * @param Carbon $start + * @param Carbon $end * * @return array */ @@ -499,29 +508,28 @@ class CategoryRepository implements CategoryRepositoryInterface { Log::debug('Now in periodIncomeNoCategory()'); $carbonFormat = Navigation::preferredCarbonFormat($start, $end); - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setAccounts($accounts)->setRange($start, $end)->withOpposingAccount(); + + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + + $collector->setAccounts($accounts)->setRange($start, $end)->withAccountInformation(); $collector->setTypes([TransactionType::DEPOSIT, TransactionType::TRANSFER]); $collector->withoutCategory(); - $transactions = $collector->getTransactions(); - $result = [ + $journals = $collector->getExtractedJournals(); + $result = [ 'entries' => [], 'name' => (string)trans('firefly.no_category'), 'sum' => '0', ]; Log::debug('Looping transactions..'); - foreach ($transactions as $transaction) { - // if negative, skip: - if (bccomp($transaction->transaction_amount, '0') === -1) { - continue; - } - $date = $transaction->date->format($carbonFormat); + + foreach ($journals as $journal) { + $date = $journal['date']->format($carbonFormat); if (!isset($result['entries'][$date])) { $result['entries'][$date] = '0'; } - $result['entries'][$date] = bcadd($result['entries'][$date], $transaction->transaction_amount); + $result['entries'][$date] = bcadd($result['entries'][$date], $journal['amount']); } Log::debug('Done looping transactions..'); Log::debug('Finished periodIncomeNoCategory()'); @@ -529,8 +537,6 @@ class CategoryRepository implements CategoryRepositoryInterface return $result; } - /** @noinspection MoreThanThreeArgumentsInspection */ - /** * @param string $query * @@ -538,9 +544,12 @@ class CategoryRepository implements CategoryRepositoryInterface */ public function searchCategory(string $query): Collection { - $query = sprintf('%%%s%%', $query); + $search = $this->user->categories(); + if ('' !== $query) { + $search->where('name', 'LIKE', sprintf('%%%s%%', $query)); + } - return $this->user->categories()->where('name', 'LIKE', $query)->get(); + return $search->get(); } /** @@ -554,42 +563,40 @@ class CategoryRepository implements CategoryRepositoryInterface /** * @param Collection $categories * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end + * @param Carbon $start + * @param Carbon $end * * @return string */ public function spentInPeriod(Collection $categories, Collection $accounts, Carbon $start, Carbon $end): string { - $set = $this->spentInPeriodCollection($categories, $accounts, $start, $end); + $array = $this->spentInPeriodCollection($categories, $accounts, $start, $end); - - return (string)$set->sum('transaction_amount'); + return $this->sumJournals($array); } /** * @param Collection $categories * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end + * @param Carbon $start + * @param Carbon $end * - * @return Collection + * @return array */ - public function spentInPeriodCollection(Collection $categories, Collection $accounts, Carbon $start, Carbon $end): Collection + public function spentInPeriodCollection(Collection $categories, Collection $accounts, Carbon $start, Carbon $end): array { - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + + $collector->setUser($this->user); $collector->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])->setCategories($categories); if ($accounts->count() > 0) { $collector->setAccounts($accounts); } - if (0 === $accounts->count()) { - $collector->setAllAssetAccounts(); - } - return $collector->getTransactions(); + return $collector->getExtractedJournals(); } /** @@ -598,50 +605,38 @@ class CategoryRepository implements CategoryRepositoryInterface * Get me the amount spent in this period, grouped per currency, where no category was set. * * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end + * @param Carbon $start + * @param Carbon $end * * @return array */ public function spentInPeriodPcWoCategory(Collection $accounts, Carbon $start, Carbon $end): array { - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setUser($this->user); $collector->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])->withoutCategory(); if ($accounts->count() > 0) { $collector->setAccounts($accounts); } - if (0 === $accounts->count()) { - $collector->setAllAssetAccounts(); - } - - $set = $collector->getTransactions(); - $set = $set->filter( - function (Transaction $transaction) { - if (bccomp($transaction->transaction_amount, '0') === -1) { - return $transaction; - } - - return null; - } - ); + $set = $collector->getExtractedJournals(); $return = []; - /** @var Transaction $transaction */ - foreach ($set as $transaction) { - $currencyId = $transaction->transaction_currency_id; + /** @var array $journal */ + foreach ($set as $journal) { + $currencyId = (int)$journal['currency_id']; if (!isset($return[$currencyId])) { $return[$currencyId] = [ 'spent' => '0', 'currency_id' => $currencyId, - 'currency_symbol' => $transaction->transaction_currency_symbol, - 'currency_code' => $transaction->transaction_currency_code, - 'currency_decimal_places' => $transaction->transaction_currency_dp, + 'currency_symbol' => $journal['currency_symbol'], + 'currency_code' => $journal['currency_code'], + 'currency_decimal_places' => $journal['currency_decimal_places'], ]; } - $return[$currencyId]['spent'] = bcadd($return[$currencyId]['spent'], $transaction->transaction_amount); + $return[$currencyId]['spent'] = bcadd($return[$currencyId]['spent'], $journal['amount']); } return $return; @@ -650,15 +645,15 @@ class CategoryRepository implements CategoryRepositoryInterface /** * @param Collection $categories * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end + * @param Carbon $start + * @param Carbon $end * * @return array */ public function spentInPeriodPerCurrency(Collection $categories, Collection $accounts, Carbon $start, Carbon $end): array { - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); $collector->setUser($this->user); $collector->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL]); @@ -672,20 +667,14 @@ class CategoryRepository implements CategoryRepositoryInterface if ($accounts->count() > 0) { $collector->setAccounts($accounts); } - if (0 === $accounts->count()) { - $collector->setAllAssetAccounts(); - } - $set = $collector->getTransactions(); + $set = $collector->getExtractedJournals(); $return = []; - /** @var Transaction $transaction */ - foreach ($set as $transaction) { - $jrnlCatId = (int)$transaction->transaction_journal_category_id; - $transCatId = (int)$transaction->transaction_category_id; - $categoryId = max($jrnlCatId, $transCatId); - $currencyId = (int)$transaction->transaction_currency_id; - $name = $transaction->transaction_category_name; - $name = '' === (string)$name ? $transaction->transaction_journal_category_name : $name; + /** @var array $journal */ + foreach ($set as $journal) { + $categoryId = (int)$journal['category_id']; + $currencyId = (int)$journal['currency_id']; + $name = $journal['category_name']; // make array for category: if (!isset($return[$categoryId])) { @@ -698,13 +687,13 @@ class CategoryRepository implements CategoryRepositoryInterface $return[$categoryId]['spent'][$currencyId] = [ 'spent' => '0', 'currency_id' => $currencyId, - 'currency_symbol' => $transaction->transaction_currency_symbol, - 'currency_code' => $transaction->transaction_currency_code, - 'currency_decimal_places' => $transaction->transaction_currency_dp, + 'currency_symbol' => $journal['currency_symbol'], + 'currency_code' => $journal['currency_code'], + 'currency_decimal_places' => $journal['currency_decimal_places'], ]; } $return[$categoryId]['spent'][$currencyId]['spent'] - = bcadd($return[$categoryId]['spent'][$currencyId]['spent'], $transaction->transaction_amount); + = bcadd($return[$categoryId]['spent'][$currencyId]['spent'], $journal['amount']); } return $return; @@ -712,56 +701,29 @@ class CategoryRepository implements CategoryRepositoryInterface /** * @param Collection $accounts - * @param Carbon $start - * @param Carbon $end + * @param Carbon $start + * @param Carbon $end * * @return string */ public function spentInPeriodWithoutCategory(Collection $accounts, Carbon $start, Carbon $end): string { - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setUser($this->user); $collector->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])->withoutCategory(); if ($accounts->count() > 0) { $collector->setAccounts($accounts); } - if (0 === $accounts->count()) { - $collector->setAllAssetAccounts(); - } - $set = $collector->getTransactions(); - $set = $set->filter( - function (Transaction $transaction) { - if (bccomp($transaction->transaction_amount, '0') === -1) { - return $transaction; - } - - return null; - } - ); - - return (string)$set->sum('transaction_amount'); - } - - /** - * @param array $data - * - * @return Category - */ - public function store(array $data): Category - { - /** @var CategoryFactory $factory */ - $factory = app(CategoryFactory::class); - $factory->setUser($this->user); - - return $factory->findOrCreate(null, $data['name']); + return $collector->getSum(); } /** * @param Category $category - * @param array $data + * @param array $data * * @return Category */ @@ -773,6 +735,22 @@ class CategoryRepository implements CategoryRepositoryInterface return $service->update($category, $data); } + /** + * @param array $journals + * @return string + */ + private function sumJournals(array $journals): string + { + $sum = '0'; + /** @var array $journal */ + foreach ($journals as $journal) { + $amount = (string)$journal['amount']; + $sum = bcadd($sum, $amount); + } + + return $sum; + } + /** * @param Category $category * @@ -811,7 +789,7 @@ class CategoryRepository implements CategoryRepositoryInterface } /** - * @param Category $category + * @param Category $category * @param Collection $accounts * * @return Carbon|null @@ -835,10 +813,11 @@ class CategoryRepository implements CategoryRepositoryInterface } /** - * @param Category $category + * @param Category $category * @param Collection $accounts * * @return Carbon|null + * @throws \Exception */ private function getLastTransactionDate(Category $category, Collection $accounts): ?Carbon { diff --git a/app/Repositories/Category/CategoryRepositoryInterface.php b/app/Repositories/Category/CategoryRepositoryInterface.php index edd4a33597..b30dc1704c 100644 --- a/app/Repositories/Category/CategoryRepositoryInterface.php +++ b/app/Repositories/Category/CategoryRepositoryInterface.php @@ -33,6 +33,14 @@ use Illuminate\Support\Collection; interface CategoryRepositoryInterface { + /** + * @param int|null $categoryId + * @param string|null $categoryName + * + * @return Category|null + */ + public function findCategory( ?int $categoryId, ?string $categoryName): ?Category; + /** * @param Category $category * @@ -58,9 +66,9 @@ interface CategoryRepositoryInterface * @param Carbon $start * @param Carbon $end * - * @return Collection + * @return array */ - public function earnedInPeriodCollection(Collection $categories, Collection $accounts, Carbon $start, Carbon $end): Collection; + public function earnedInPeriodCollection(Collection $categories, Collection $accounts, Carbon $start, Carbon $end): array; /** @noinspection MoreThanThreeArgumentsInspection */ @@ -214,9 +222,9 @@ interface CategoryRepositoryInterface * @param Carbon $start * @param Carbon $end * - * @return Collection + * @return array */ - public function spentInPeriodCollection(Collection $categories, Collection $accounts, Carbon $start, Carbon $end): Collection; + public function spentInPeriodCollection(Collection $categories, Collection $accounts, Carbon $start, Carbon $end): array; /** * A very cryptic method name that means: diff --git a/app/Repositories/Currency/CurrencyRepository.php b/app/Repositories/Currency/CurrencyRepository.php index 8a6575f39d..0091587cc9 100644 --- a/app/Repositories/Currency/CurrencyRepository.php +++ b/app/Repositories/Currency/CurrencyRepository.php @@ -25,9 +25,12 @@ namespace FireflyIII\Repositories\Currency; use Carbon\Carbon; use FireflyIII\Factory\TransactionCurrencyFactory; use FireflyIII\Models\AccountMeta; +use FireflyIII\Models\AvailableBudget; +use FireflyIII\Models\Bill; use FireflyIII\Models\BudgetLimit; use FireflyIII\Models\CurrencyExchangeRate; use FireflyIII\Models\Preference; +use FireflyIII\Models\RecurrenceTransaction; use FireflyIII\Models\TransactionCurrency; use FireflyIII\Repositories\User\UserRepositoryInterface; use FireflyIII\Services\Internal\Destroy\CurrencyDestroyService; @@ -51,7 +54,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface public function __construct() { if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } @@ -62,7 +65,8 @@ class CurrencyRepository implements CurrencyRepositoryInterface */ public function countJournals(TransactionCurrency $currency): int { - return $currency->transactions()->count(); + return $currency->transactions()->count() + $currency->transactionJournals()->count(); + } /** @@ -75,14 +79,14 @@ class CurrencyRepository implements CurrencyRepositoryInterface Log::debug(sprintf('Now in currencyInUse() for #%d ("%s")', $currency->id, $currency->code)); $countJournals = $this->countJournals($currency); if ($countJournals > 0) { - Log::debug(sprintf('Count journals is %d, return true.', $countJournals)); + Log::info(sprintf('Count journals is %d, return true.', $countJournals)); return true; } // is the only currency left if (1 === $this->getAll()->count()) { - Log::debug('Is the last currency in the system, return true. ', $countJournals); + Log::info('Is the last currency in the system, return true. '); return true; } @@ -90,7 +94,41 @@ class CurrencyRepository implements CurrencyRepositoryInterface // is being used in accounts: $meta = AccountMeta::where('name', 'currency_id')->where('data', json_encode((string)$currency->id))->count(); if ($meta > 0) { - Log::debug(sprintf('Used in %d accounts as currency_id, return true. ', $meta)); + Log::info(sprintf('Used in %d accounts as currency_id, return true. ', $meta)); + + return true; + } + + // is being used in bills: + $bills = Bill::where('transaction_currency_id', $currency->id)->count(); + if ($bills > 0) { + Log::info(sprintf('Used in %d bills as currency, return true. ', $bills)); + + return true; + } + + // is being used in recurring transactions + $recurringAmount = RecurrenceTransaction::where('transaction_currency_id', $currency->id)->count(); + $recurringForeign = RecurrenceTransaction::where('foreign_currency_id', $currency->id)->count(); + + if ($recurringAmount > 0 || $recurringForeign > 0) { + Log::info(sprintf('Used in %d recurring transactions as (foreign) currency id, return true. ', $recurringAmount + $recurringForeign)); + + return true; + } + + // is being used in accounts (as integer) + $meta = AccountMeta::where('name', 'currency_id')->where('data', json_encode((int)$currency->id))->count(); + if ($meta > 0) { + Log::info(sprintf('Used in %d accounts as currency_id, return true. ', $meta)); + + return true; + } + + // is being used in available budgets + $availableBudgets = AvailableBudget::where('transaction_currency_id', $currency->id)->count(); + if ($availableBudgets > 0) { + Log::info(sprintf('Used in %d available budgets as currency, return true. ', $availableBudgets)); return true; } @@ -98,7 +136,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface // is being used in budget limits $budgetLimit = BudgetLimit::where('transaction_currency_id', $currency->id)->count(); if ($budgetLimit > 0) { - Log::debug(sprintf('Used in %d budget limits as currency, return true. ', $budgetLimit)); + Log::info(sprintf('Used in %d budget limits as currency, return true. ', $budgetLimit)); return true; } @@ -106,19 +144,19 @@ class CurrencyRepository implements CurrencyRepositoryInterface // is the default currency for the user or the system $defaultCode = app('preferences')->getForUser($this->user, 'currencyPreference', config('firefly.default_currency', 'EUR'))->data; if ($currency->code === $defaultCode) { - Log::debug('Is the default currency of the user, return true.'); + Log::info('Is the default currency of the user, return true.'); return true; } // is the default currency for the system - $defaultSystemCode = config('firefly.default_currency', 'EUR'); - $result = $currency->code === $defaultSystemCode; - if (true === $result) { - Log::debug('Is the default currency of the SYSTEM, return true.'); - - return true; - } + // $defaultSystemCode = config('firefly.default_currency', 'EUR'); + // $result = $currency->code === $defaultSystemCode; + // if (true === $result) { + // Log::info('Is the default currency of the SYSTEM, return true.'); + // + // return true; + // } Log::debug('Currency is not used, return false.'); return false; @@ -195,8 +233,8 @@ class CurrencyRepository implements CurrencyRepositoryInterface * * @param string $currencyCode * - * @deprecated * @return TransactionCurrency|null + * @deprecated */ public function findByCodeNull(string $currencyCode): ?TransactionCurrency { @@ -221,8 +259,8 @@ class CurrencyRepository implements CurrencyRepositoryInterface * * @param string $currencyName * - * @deprecated * @return TransactionCurrency + * @deprecated */ public function findByNameNull(string $currencyName): ?TransactionCurrency { @@ -247,22 +285,76 @@ class CurrencyRepository implements CurrencyRepositoryInterface * * @param string $currencySymbol * - * @deprecated * @return TransactionCurrency + * @deprecated */ public function findBySymbolNull(string $currencySymbol): ?TransactionCurrency { return TransactionCurrency::whereSymbol($currencySymbol)->first(); } + /** + * Find by object, ID or code. Returns user default or system default. + * + * @param int|null $currencyId + * @param string|null $currencyCode + * + * @return TransactionCurrency|null + */ + public function findCurrency(?int $currencyId, ?string $currencyCode): TransactionCurrency + { + $result = $this->findCurrencyNull($currencyId, $currencyCode); + + if (null === $result) { + Log::debug('Grabbing default currency for this user...'); + $result = app('amount')->getDefaultCurrencyByUser($this->user); + } + + if (null === $result) { + Log::debug('Grabbing EUR as fallback.'); + $result = $this->findByCode('EUR'); + } + Log::debug(sprintf('Final result: %s', $result->code)); + if (false === $result->enabled) { + Log::debug(sprintf('Also enabled currency %s', $result->code)); + $this->enable($result); + } + + return $result; + } + + /** + * Find by object, ID or code. Returns NULL if nothing found. + * + * @param int|null $currencyId + * @param string|null $currencyCode + * + * @return TransactionCurrency|null + */ + public function findCurrencyNull(?int $currencyId, ?string $currencyCode): ?TransactionCurrency + { + Log::debug('Now in findCurrencyNull()'); + $result = $this->find((int)$currencyId); + if (null === $result) { + Log::debug(sprintf('Searching for currency with code %s...', $currencyCode)); + $result = $this->findByCode((string)$currencyCode); + } + if (null !== $result && false === $result->enabled) { + Log::debug(sprintf('Also enabled currency %s', $result->code)); + $this->enable($result); + } + + return $result; + } + /** * Find by ID, return NULL if not found. * Used in Import Currency! * * @param int $currencyId * - * @deprecated * @return TransactionCurrency|null + * @deprecated */ public function findNull(int $currencyId): ?TransactionCurrency { @@ -315,7 +407,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface * * @param TransactionCurrency $fromCurrency * @param TransactionCurrency $toCurrency - * @param Carbon $date + * @param Carbon $date * * @return CurrencyExchangeRate|null */ @@ -384,7 +476,7 @@ class CurrencyRepository implements CurrencyRepositoryInterface /** * @param TransactionCurrency $currency - * @param array $data + * @param array $data * * @return TransactionCurrency */ @@ -395,4 +487,18 @@ class CurrencyRepository implements CurrencyRepositoryInterface return $service->update($currency, $data); } + + /** + * @param string $search + * @return Collection + */ + public function searchCurrency(string $search): Collection + { + $query = TransactionCurrency::where('enabled', 1); + if ('' !== $search) { + $query->where('name', 'LIKE', sprintf('%%%s%%', $search)); + } + + return $query->get(); + } } diff --git a/app/Repositories/Currency/CurrencyRepositoryInterface.php b/app/Repositories/Currency/CurrencyRepositoryInterface.php index 0adb767196..9dc3105287 100644 --- a/app/Repositories/Currency/CurrencyRepositoryInterface.php +++ b/app/Repositories/Currency/CurrencyRepositoryInterface.php @@ -34,6 +34,12 @@ use Illuminate\Support\Collection; */ interface CurrencyRepositoryInterface { + /** + * @param string $search + * @return Collection + */ + public function searchCurrency(string $search): Collection; + /** * @param TransactionCurrency $currency * @@ -132,6 +138,26 @@ interface CurrencyRepositoryInterface */ public function findBySymbolNull(string $currencySymbol): ?TransactionCurrency; + /** + * Find by object, ID or code. Returns user default or system default. + * + * @param int|null $currencyId + * @param string|null $currencyCode + * + * @return TransactionCurrency|null + */ + public function findCurrency(?int $currencyId, ?string $currencyCode): TransactionCurrency; + + /** + * Find by object, ID or code. Returns NULL if nothing found. + * + * @param int|null $currencyId + * @param string|null $currencyCode + * + * @return TransactionCurrency|null + */ + public function findCurrencyNull(?int $currencyId, ?string $currencyCode): ?TransactionCurrency; + /** * Find by ID, return NULL if not found. * diff --git a/app/Repositories/ExportJob/ExportJobRepository.php b/app/Repositories/ExportJob/ExportJobRepository.php deleted file mode 100644 index e11c1b88f6..0000000000 --- a/app/Repositories/ExportJob/ExportJobRepository.php +++ /dev/null @@ -1,147 +0,0 @@ -. - */ -declare(strict_types=1); - -namespace FireflyIII\Repositories\ExportJob; - -use FireflyIII\Models\ExportJob; -use FireflyIII\User; -use Illuminate\Contracts\Filesystem\FileNotFoundException; -use Illuminate\Support\Facades\Storage; -use Illuminate\Support\Str; -use Log; - -/** - * Class ExportJobRepository. - */ -class ExportJobRepository implements ExportJobRepositoryInterface -{ - /** @var User */ - private $user; - - /** - * Constructor. - */ - public function __construct() - { - if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); - } - } - - /** - * @param ExportJob $job - * @param string $status - * - * @return bool - */ - public function changeStatus(ExportJob $job, string $status): bool - { - Log::debug(sprintf('Change status of job #%d to "%s"', $job->id, $status)); - $job->status = $status; - $job->save(); - - return true; - } - - /** - * @return ExportJob|null - */ - public function create(): ?ExportJob - { - $count = 0; - while ($count < 30) { - $key = Str::random(12); - $existing = $this->findByKey($key); - if (null === $existing) { - $exportJob = new ExportJob; - $exportJob->user()->associate($this->user); - $exportJob->key = Str::random(12); - $exportJob->status = 'export_status_never_started'; - $exportJob->save(); - - // breaks the loop: - - return $exportJob; - } - ++$count; - } - - return null; - } - - /** - * @param ExportJob $job - * - * @return bool - */ - public function exists(ExportJob $job): bool - { - $disk = Storage::disk('export'); - $file = $job->key . '.zip'; - - return $disk->exists($file); - } - - /** - * @param string $key - * - * @return ExportJob|null - */ - public function findByKey(string $key): ?ExportJob - { - /** @var ExportJob $result */ - $result = $this->user->exportJobs()->where('key', $key)->first(['export_jobs.*']); - if (null === $result) { - return null; - } - - return $result; - } - - /** - * @param ExportJob $job - * - * @return string - */ - public function getContent(ExportJob $job): string - { - $disk = Storage::disk('export'); - $file = $job->key . '.zip'; - - try { - $content = $disk->get($file); - } catch (FileNotFoundException $e) { - Log::warning(sprintf('File not found: %s', $e->getMessage())); - $content = ''; - } - - return $content; - } - - /** - * @param User $user - */ - public function setUser(User $user): void - { - $this->user = $user; - } -} diff --git a/app/Repositories/ExportJob/ExportJobRepositoryInterface.php b/app/Repositories/ExportJob/ExportJobRepositoryInterface.php deleted file mode 100644 index 89be429ccf..0000000000 --- a/app/Repositories/ExportJob/ExportJobRepositoryInterface.php +++ /dev/null @@ -1,71 +0,0 @@ -. - */ -declare(strict_types=1); - -namespace FireflyIII\Repositories\ExportJob; - -use FireflyIII\Models\ExportJob; -use FireflyIII\User; - -/** - * Interface ExportJobRepositoryInterface. - */ -interface ExportJobRepositoryInterface -{ - /** - * @param ExportJob $job - * @param string $status - * - * @return bool - */ - public function changeStatus(ExportJob $job, string $status): bool; - - /** - * @return ExportJob|null - */ - public function create(): ?ExportJob; - - /** - * @param ExportJob $job - * - * @return bool - */ - public function exists(ExportJob $job): bool; - - /** - * @param string $key - * - * @return ExportJob|null - */ - public function findByKey(string $key): ?ExportJob; - - /** - * @param ExportJob $job - * - * @return string - */ - public function getContent(ExportJob $job): string; - - /** - * @param User $user - */ - public function setUser(User $user); -} diff --git a/app/Repositories/ImportJob/ImportJobRepository.php b/app/Repositories/ImportJob/ImportJobRepository.php index 9fcf7cc050..bd8bf775f7 100644 --- a/app/Repositories/ImportJob/ImportJobRepository.php +++ b/app/Repositories/ImportJob/ImportJobRepository.php @@ -55,7 +55,7 @@ class ImportJobRepository implements ImportJobRepositoryInterface $this->uploadDisk = Storage::disk('upload'); if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } @@ -91,9 +91,9 @@ class ImportJobRepository implements ImportJobRepositoryInterface Log::debug(sprintf('Now in appendTransactions(%s)', $job->key)); $existingTransactions = $this->getTransactions($job); $new = array_merge($existingTransactions, $transactions); - Log::debug(sprintf('Old transaction count: %d', \count($existingTransactions))); - Log::debug(sprintf('To be added transaction count: %d', \count($transactions))); - Log::debug(sprintf('New count: %d', \count($new))); + Log::debug(sprintf('Old transaction count: %d', count($existingTransactions))); + Log::debug(sprintf('To be added transaction count: %d', count($transactions))); + Log::debug(sprintf('New count: %d', count($new))); $this->setTransactions($job, $new); return $job; @@ -224,7 +224,7 @@ class ImportJobRepository implements ImportJobRepositoryInterface public function getExtendedStatus(ImportJob $job): array { $status = $job->extended_status; - if (\is_array($status)) { + if (is_array($status)) { return $status; } @@ -333,7 +333,7 @@ class ImportJobRepository implements ImportJobRepositoryInterface $json = Crypt::encrypt(json_encode($transactions)); // set count for easy access - $array = ['count' => \count($transactions)]; + $array = ['count' => count($transactions)]; $job->transactions = $array; $job->save(); // store file. @@ -386,7 +386,7 @@ class ImportJobRepository implements ImportJobRepositoryInterface $attachment->md5 = md5($content); $attachment->filename = $name; $attachment->mime = 'plain/txt'; - $attachment->size = \strlen($content); + $attachment->size = strlen($content); $attachment->uploaded = false; $attachment->save(); $encrypted = Crypt::encrypt($content); @@ -467,4 +467,14 @@ class ImportJobRepository implements ImportJobRepositoryInterface return $size > $this->maxUploadSize; } + + /** + * @param ImportJob $job + * + * @return int + */ + public function countByTag(ImportJob $job): int + { + return $job->tag->transactionJournals->count(); + } } diff --git a/app/Repositories/ImportJob/ImportJobRepositoryInterface.php b/app/Repositories/ImportJob/ImportJobRepositoryInterface.php index 8f33c31ed6..980421554e 100644 --- a/app/Repositories/ImportJob/ImportJobRepositoryInterface.php +++ b/app/Repositories/ImportJob/ImportJobRepositoryInterface.php @@ -62,6 +62,13 @@ interface ImportJobRepositoryInterface */ public function countTransactions(ImportJob $job): int; + /** + * @param ImportJob $job + * + * @return int + */ + public function countByTag(ImportJob $job): int; + /** * @param string $importProvider * diff --git a/app/Repositories/Journal/JournalRepository.php b/app/Repositories/Journal/JournalRepository.php index 1f4bd93eed..df607ffa80 100644 --- a/app/Repositories/Journal/JournalRepository.php +++ b/app/Repositories/Journal/JournalRepository.php @@ -23,28 +23,27 @@ declare(strict_types=1); namespace FireflyIII\Repositories\Journal; use Carbon\Carbon; +use DB; use Exception; -use FireflyIII\Exceptions\FireflyException; -use FireflyIII\Factory\TransactionJournalFactory; -use FireflyIII\Factory\TransactionJournalMetaFactory; -use FireflyIII\Helpers\Collector\TransactionCollectorInterface; -use FireflyIII\Helpers\Filter\InternalTransferFilter; use FireflyIII\Models\Account; use FireflyIII\Models\AccountType; use FireflyIII\Models\Note; use FireflyIII\Models\PiggyBankEvent; use FireflyIII\Models\Transaction; +use FireflyIII\Models\TransactionGroup; use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionJournalLink; use FireflyIII\Models\TransactionJournalMeta; use FireflyIII\Models\TransactionType; use FireflyIII\Services\Internal\Destroy\JournalDestroyService; +use FireflyIII\Services\Internal\Destroy\TransactionGroupDestroyService; use FireflyIII\Services\Internal\Update\JournalUpdateService; use FireflyIII\Support\CacheProperties; use FireflyIII\User; use Illuminate\Support\Collection; use Illuminate\Support\MessageBag; use Log; +use stdClass; /** * Class JournalRepository. @@ -55,6 +54,8 @@ use Log; */ class JournalRepository implements JournalRepositoryInterface { + + /** @var User */ private $user; @@ -64,16 +65,34 @@ class JournalRepository implements JournalRepositoryInterface public function __construct() { if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } + /** + * Search in journal descriptions. + * + * @param string $search + * @return Collection + */ + public function searchJournalDescriptions(string $search): Collection + { + $query = $this->user->transactionJournals() + ->orderBy('date', 'DESC'); + if ('' !== $query) { + $query->where('description', 'LIKE', sprintf('%%%s%%', $search)); + } + + return $query->get(); + } + /** @noinspection MoreThanThreeArgumentsInspection */ + /** * @param TransactionJournal $journal - * @param TransactionType $type - * @param Account $source - * @param Account $destination + * @param TransactionType $type + * @param Account $source + * @param Account $destination * * @return MessageBag * @SuppressWarnings(PHPMD.CyclomaticComplexity) @@ -135,29 +154,25 @@ class JournalRepository implements JournalRepositoryInterface } /** - * @param TransactionJournal $journal + * @param TransactionGroup $transactionGroup * - * @return int */ - public function countTransactions(TransactionJournal $journal): int + public function destroyGroup(TransactionGroup $transactionGroup): void { - return $journal->transactions()->count(); + /** @var TransactionGroupDestroyService $service */ + $service = app(TransactionGroupDestroyService::class); + $service->destroy($transactionGroup); } /** * @param TransactionJournal $journal * - * @return bool - * - */ - public function destroy(TransactionJournal $journal): bool + public function destroyJournal(TransactionJournal $journal): void { /** @var JournalDestroyService $service */ $service = app(JournalDestroyService::class); $service->destroy($journal); - - return true; } /** @@ -177,7 +192,7 @@ class JournalRepository implements JournalRepositoryInterface $result = TransactionJournalMeta::withTrashed() ->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id') ->where('hash', $hashOfHash) - ->where('name', 'importHashV2') + ->where('name', 'import_hash_v2') ->first(['journal_meta.*']); if (null === $result) { Log::debug('Result is null'); @@ -198,23 +213,6 @@ class JournalRepository implements JournalRepositoryInterface return $this->user->transactionJournals()->where('id', $journalId)->first(); } - /** - * @param Transaction $transaction - * - * @return Transaction|null - */ - public function findOpposingTransaction(Transaction $transaction): ?Transaction - { - $opposing = Transaction::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') - ->where('transaction_journals.user_id', $this->user->id) - ->where('transactions.transaction_journal_id', $transaction->transaction_journal_id) - ->where('transactions.identifier', $transaction->identifier) - ->where('amount', bcmul($transaction->amount, '-1')) - ->first(['transactions.*']); - - return $opposing; - } - /** * @param int $transactionid * @@ -277,13 +275,16 @@ class JournalRepository implements JournalRepositoryInterface } /** - * @param Transaction $transaction + * Get all attachments connected to the transaction group. + * + * @param TransactionJournal $transactionJournal * * @return Collection */ - public function getAttachmentsByTr(Transaction $transaction): Collection + public function getAttachmentsByJournal(TransactionJournal $transactionJournal): Collection { - return $transaction->transactionJournal->attachments()->get(); + // TODO: Implement getAttachmentsByJournal() method. + throw new NotImplementedException; } /** @@ -320,6 +321,28 @@ class JournalRepository implements JournalRepositoryInterface return 0; } + /** + * Return the ID of the category linked to the journal (if any) or to the transactions (if any). + * + * @param TransactionJournal $journal + * + * @return int + */ + public function getJournalCategoryId(TransactionJournal $journal): int + { + $category = $journal->categories()->first(); + if (null !== $category) { + return $category->id; + } + /** @noinspection NullPointerExceptionInspection */ + $category = $journal->transactions()->first()->categories()->first(); + if (null !== $category) { + return $category->id; + } + + return 0; + } + /** * Return the name of the category linked to the journal (if any) or to the transactions (if any). * @@ -347,7 +370,7 @@ class JournalRepository implements JournalRepositoryInterface * otherwise look for meta field and return that one. * * @param TransactionJournal $journal - * @param null|string $field + * @param null|string $field * * @return string * @SuppressWarnings(PHPMD.CyclomaticComplexity) @@ -379,11 +402,40 @@ class JournalRepository implements JournalRepositoryInterface return ''; } + /** + * Return Carbon value of a meta field (or NULL). + * + * @param TransactionJournal $journal + * @param string $field + * + * @return null|Carbon + */ + public function getMetaDate(TransactionJournal $journal, string $field): ?Carbon + { + $cache = new CacheProperties; + $cache->addProperty('journal-meta-updated'); + $cache->addProperty($journal->id); + $cache->addProperty($field); + + if ($cache->has()) { + return new Carbon($cache->get()); // @codeCoverageIgnore + } + + $entry = $journal->transactionJournalMeta()->where('name', $field)->first(); + if (null === $entry) { + return null; + } + $value = new Carbon($entry->data); + $cache->store($entry->data); + + return $value; + } + /** * Return a list of all destination accounts related to journal. * * @param TransactionJournal $journal - * @param bool $useCache + * @param bool $useCache * * @return Collection */ @@ -411,7 +463,7 @@ class JournalRepository implements JournalRepositoryInterface * Return a list of all source accounts related to journal. * * @param TransactionJournal $journal - * @param bool $useCache + * @param bool $useCache * * @return Collection */ @@ -459,6 +511,16 @@ class JournalRepository implements JournalRepositoryInterface return $amount; } + /** + * Return all journals without a group, used in an upgrade routine. + * + * @return array + */ + public function getJournalsWithoutGroup(): array + { + return TransactionJournal::whereNull('transaction_group_id')->get(['id', 'user_id'])->toArray(); + } + /** * @param TransactionJournalLink $link * @@ -476,40 +538,11 @@ class JournalRepository implements JournalRepositoryInterface return ''; } - /** - * Return Carbon value of a meta field (or NULL). - * - * @param TransactionJournal $journal - * @param string $field - * - * @return null|Carbon - */ - public function getMetaDate(TransactionJournal $journal, string $field): ?Carbon - { - $cache = new CacheProperties; - $cache->addProperty('journal-meta-updated'); - $cache->addProperty($journal->id); - $cache->addProperty($field); - - if ($cache->has()) { - return new Carbon($cache->get()); // @codeCoverageIgnore - } - - $entry = $journal->transactionJournalMeta()->where('name', $field)->first(); - if (null === $entry) { - return null; - } - $value = new Carbon($entry->data); - $cache->store($entry->data); - - return $value; - } - /** * Return string value of a meta date (or NULL). * * @param TransactionJournal $journal - * @param string $field + * @param string $field * * @return null|string */ @@ -527,7 +560,7 @@ class JournalRepository implements JournalRepositoryInterface * Return value of a meta field (or NULL) as a string. * * @param TransactionJournal $journal - * @param string $field + * @param string $field * * @return null|string * @SuppressWarnings(PHPMD.CyclomaticComplexity) @@ -550,7 +583,7 @@ class JournalRepository implements JournalRepositoryInterface $value = $entry->data; - if (\is_array($value)) { + if (is_array($value)) { $return = implode(',', $value); $cache->store($return); @@ -606,13 +639,29 @@ class JournalRepository implements JournalRepositoryInterface } /** - * @param Transaction $transaction + * Returns all journals with more than 2 transactions. Should only return empty collections + * in Firefly III > v4.8.0. * * @return Collection */ - public function getPiggyBankEventsbyTr(Transaction $transaction): Collection + public function getSplitJournals(): Collection { - return $transaction->transactionJournal->piggyBankEvents()->get(); + $query = TransactionJournal + ::leftJoin('transactions', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') + ->groupBy('transaction_journals.id'); + $result = $query->get(['transaction_journals.id as id', DB::raw('count(transactions.id) as transaction_count')]); + $journalIds = []; + /** @var stdClass $row */ + foreach ($result as $row) { + if ((int)$row->transaction_count > 2) { + $journalIds[] = (int)$row->id; + } + } + $journalIds = array_unique($journalIds); + + return TransactionJournal + ::with(['transactions']) + ->whereIn('id', $journalIds)->get(); } /** @@ -639,41 +688,6 @@ class JournalRepository implements JournalRepositoryInterface return $journal->transactionType->type; } - /** - * @return Collection - */ - public function getTransactionTypes(): Collection - { - return TransactionType::orderBy('type', 'ASC')->get(); - } - - /** - * @param array $transactionIds - * - * @return Collection - */ - public function getTransactionsById(array $transactionIds): Collection - { - $journalIds = Transaction::whereIn('id', $transactionIds)->get(['transaction_journal_id'])->pluck('transaction_journal_id')->toArray(); - $journals = new Collection; - foreach ($journalIds as $journalId) { - $result = $this->findNull((int)$journalId); - if (null !== $result) { - $journals->push($result); - } - } - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setUser($this->user); - $collector->setAllAssetAccounts(); - $collector->removeFilter(InternalTransferFilter::class); - //$collector->addFilter(TransferFilter::class); - - $collector->setJournals($journals)->withOpposingAccount(); - - return $collector->getTransactions(); - } - /** * Will tell you if journal is reconciled or not. * @@ -692,6 +706,18 @@ class JournalRepository implements JournalRepositoryInterface return false; } + /** + * @param int $transactionId + */ + public function reconcileById(int $journalId): void + { + /** @var TransactionJournal $journal */ + $journal = $this->user->transactionJournals()->find($journalId); + if (null !== $journal) { + $journal->transactions()->update(['reconciled' => true]); + } + } + /** * @param Transaction $transaction * @@ -718,47 +744,8 @@ class JournalRepository implements JournalRepositoryInterface } /** - * @param int $transactionId - * - * @return bool - */ - public function reconcileById(int $transactionId): bool - { - /** @var Transaction $transaction */ - $transaction = $this->user->transactions()->find($transactionId); - if (null !== $transaction) { - return $this->reconcile($transaction); - } - - return false; - } - - /** - * Set meta field for journal that contains a date. - * * @param TransactionJournal $journal - * @param string $name - * @param Carbon $date - * - * @return void - */ - public function setMetaDate(TransactionJournal $journal, string $name, Carbon $date): void - { - /** @var TransactionJournalMetaFactory $factory */ - $factory = app(TransactionJournalMetaFactory::class); - $factory->updateOrCreate( - [ - 'data' => $date, - 'journal' => $journal, - 'name' => $name, - ] - ); - - } - - /** - * @param TransactionJournal $journal - * @param int $order + * @param int $order * * @return bool */ @@ -778,45 +765,11 @@ class JournalRepository implements JournalRepositoryInterface $this->user = $user; } - /** - * @param array $data - * - * @return TransactionJournal - * - * @throws FireflyException - */ - public function store(array $data): TransactionJournal - { - /** @var TransactionJournalFactory $factory */ - $factory = app(TransactionJournalFactory::class); - $factory->setUser($this->user); - - return $factory->create($data); - } - - /** - * @param TransactionJournal $journal - * @param array $data - * - * @return TransactionJournal - * - * @throws FireflyException - * @throws FireflyException - */ - public function update(TransactionJournal $journal, array $data): TransactionJournal - { - /** @var JournalUpdateService $service */ - $service = app(JournalUpdateService::class); - $journal = $service->update($journal, $data); - - return $journal; - } - /** * Update budget for a journal. * * @param TransactionJournal $journal - * @param int $budgetId + * @param int $budgetId * * @return TransactionJournal */ @@ -825,14 +778,23 @@ class JournalRepository implements JournalRepositoryInterface /** @var JournalUpdateService $service */ $service = app(JournalUpdateService::class); - return $service->updateBudget($journal, $budgetId); + $service->setTransactionJournal($journal); + $service->setData( + [ + 'budget_id' => $budgetId, + ] + ); + $service->update(); + $journal->refresh(); + + return $journal; } /** * Update category for a journal. * * @param TransactionJournal $journal - * @param string $category + * @param string $category * * @return TransactionJournal */ @@ -840,15 +802,23 @@ class JournalRepository implements JournalRepositoryInterface { /** @var JournalUpdateService $service */ $service = app(JournalUpdateService::class); + $service->setTransactionJournal($journal); + $service->setData( + [ + 'category_name' => $category, + ] + ); + $service->update(); + $journal->refresh(); - return $service->updateCategory($journal, $category); + return $journal; } /** * Update tag(s) for a journal. * * @param TransactionJournal $journal - * @param array $tags + * @param array $tags * * @return TransactionJournal */ @@ -856,9 +826,74 @@ class JournalRepository implements JournalRepositoryInterface { /** @var JournalUpdateService $service */ $service = app(JournalUpdateService::class); - $service->connectTags($journal, $tags); + $service->setTransactionJournal($journal); + $service->setData( + [ + 'tags' => $tags, + ] + ); + $service->update(); + $journal->refresh(); return $journal; + } + /** + * Get all transaction journals with a specific type, regardless of user. + * + * @param array $types + * @return Collection + */ + public function getAllJournals(array $types): Collection + { + return TransactionJournal + ::leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id') + ->whereIn('transaction_types.type', $types) + ->with(['user', 'transactionType', 'transactionCurrency', 'transactions', 'transactions.account']) + ->get(['transaction_journals.*']); + } + + /** + * Get all transaction journals with a specific type, for the logged in user. + * + * @param array $types + * @return Collection + */ + public function getJournals(array $types): Collection + { + return $this->user->transactionJournals() + ->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id') + ->whereIn('transaction_types.type', $types) + ->with(['user', 'transactionType', 'transactionCurrency', 'transactions', 'transactions.account']) + ->get(['transaction_journals.*']); + } + + /** + * Return Carbon value of a meta field (or NULL). + * + * @param int $journalId + * @param string $field + * + * @return null|Carbon + */ + public function getMetaDateById(int $journalId, string $field): ?Carbon + { + $cache = new CacheProperties; + $cache->addProperty('journal-meta-updated'); + $cache->addProperty($journalId); + $cache->addProperty($field); + + if ($cache->has()) { + return new Carbon($cache->get()); // @codeCoverageIgnore + } + $entry = TransactionJournalMeta::where('transaction_journal_id', $journalId) + ->where('name', $field)->first(); + if (null === $entry) { + return null; + } + $value = new Carbon($entry->data); + $cache->store($entry->data); + + return $value; } } diff --git a/app/Repositories/Journal/JournalRepositoryInterface.php b/app/Repositories/Journal/JournalRepositoryInterface.php index 13eaf8360f..8586eec220 100644 --- a/app/Repositories/Journal/JournalRepositoryInterface.php +++ b/app/Repositories/Journal/JournalRepositoryInterface.php @@ -23,9 +23,9 @@ declare(strict_types=1); namespace FireflyIII\Repositories\Journal; use Carbon\Carbon; -use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\Account; use FireflyIII\Models\Transaction; +use FireflyIII\Models\TransactionGroup; use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionJournalLink; use FireflyIII\Models\TransactionJournalMeta; @@ -36,38 +36,61 @@ use Illuminate\Support\MessageBag; /** * Interface JournalRepositoryInterface. + * TODO needs cleaning up. Remove unused methods. */ interface JournalRepositoryInterface { + /** + * Search in journal descriptions. + * + * @param string $search + * @return Collection + */ + public function searchJournalDescriptions(string $search): Collection; + + /** + * Get all transaction journals with a specific type, regardless of user. + * + * @param array $types + * @return Collection + */ + public function getAllJournals(array $types): Collection; + + /** + * Get all transaction journals with a specific type, for the logged in user. + * + * @param array $types + * @return Collection + */ + public function getJournals(array $types): Collection; + /** * @param TransactionJournal $journal - * @param TransactionType $type - * @param Account $source - * @param Account $destination + * @param TransactionType $type + * @param Account $source + * @param Account $destination * * @return MessageBag */ public function convert(TransactionJournal $journal, TransactionType $type, Account $source, Account $destination): MessageBag; /** - * @param TransactionJournal $journal + * Deletes a transaction group. * - * @return int + * @param TransactionGroup $transactionGroup */ - public function countTransactions(TransactionJournal $journal): int; - - - /** @noinspection MoreThanThreeArgumentsInspection */ + public function destroyGroup(TransactionGroup $transactionGroup): void; /** * Deletes a journal. * * @param TransactionJournal $journal - * - * @return bool */ - public function destroy(TransactionJournal $journal): bool; + public function destroyJournal(TransactionJournal $journal): void; + + + /** @noinspection MoreThanThreeArgumentsInspection */ /** * Find a journal by its hash. @@ -87,13 +110,6 @@ interface JournalRepositoryInterface */ public function findNull(int $journalId): ?TransactionJournal; - /** - * @param Transaction $transaction - * - * @return Transaction|null - */ - public function findOpposingTransaction(Transaction $transaction): ?Transaction; - /** * @param int $transactionid * @@ -124,13 +140,6 @@ interface JournalRepositoryInterface */ public function getAttachments(TransactionJournal $journal): Collection; - /** - * @param Transaction $transaction - * - * @return Collection - */ - public function getAttachmentsByTr(Transaction $transaction): Collection; - /** * Returns the first positive transaction for the journal. Useful when editing journals. * @@ -149,6 +158,15 @@ interface JournalRepositoryInterface */ public function getJournalBudgetId(TransactionJournal $journal): int; + /** + * Return the ID of the category linked to the journal (if any) or to the transactions (if any). + * + * @param TransactionJournal $journal + * + * @return int + */ + public function getJournalCategoryId(TransactionJournal $journal): int; + /** * Return the name of the category linked to the journal (if any) or to the transactions (if any). * @@ -163,7 +181,7 @@ interface JournalRepositoryInterface * otherwise look for meta field and return that one. * * @param TransactionJournal $journal - * @param null|string $field + * @param null|string $field * * @return string */ @@ -196,6 +214,13 @@ interface JournalRepositoryInterface */ public function getJournalTotal(TransactionJournal $journal): string; + /** + * Return all journals without a group, used in an upgrade routine. + * + * @return array + */ + public function getJournalsWithoutGroup(): array; + /** * @param TransactionJournalLink $link * @@ -207,17 +232,27 @@ interface JournalRepositoryInterface * Return Carbon value of a meta field (or NULL). * * @param TransactionJournal $journal - * @param string $field + * @param string $field * * @return null|Carbon */ public function getMetaDate(TransactionJournal $journal, string $field): ?Carbon; + /** + * Return Carbon value of a meta field (or NULL). + * + * @param int $journalId + * @param string $field + * + * @return null|Carbon + */ + public function getMetaDateById(int $journalId, string $field): ?Carbon; + /** * Return string value of a meta date (or NULL). * * @param TransactionJournal $journal - * @param string $field + * @param string $field * * @return null|string */ @@ -227,7 +262,7 @@ interface JournalRepositoryInterface * Return value of a meta field (or NULL). * * @param TransactionJournal $journal - * @param string $field + * @param string $field * * @return null|string */ @@ -250,11 +285,12 @@ interface JournalRepositoryInterface public function getPiggyBankEvents(TransactionJournal $journal): Collection; /** - * @param Transaction $transaction + * Returns all journals with more than 2 transactions. Should only return empty collections + * in Firefly III > v4.8.0. * * @return Collection */ - public function getPiggyBankEventsbyTr(Transaction $transaction): Collection; + public function getSplitJournals(): Collection; /** * Return all tags as strings in an array. @@ -274,18 +310,6 @@ interface JournalRepositoryInterface */ public function getTransactionType(TransactionJournal $journal): string; - /** - * @return Collection - */ - public function getTransactionTypes(): Collection; - - /** - * @param array $transactionIds - * - * @return Collection - */ - public function getTransactionsById(array $transactionIds): Collection; - /** * Will tell you if journal is reconciled or not. * @@ -303,26 +327,13 @@ interface JournalRepositoryInterface public function reconcile(Transaction $transaction): bool; /** - * @param int $transactionId - * - * @return bool + * @param int $journalId */ - public function reconcileById(int $transactionId): bool; - - /** - * Set meta field for journal that contains a date. - * - * @param TransactionJournal $journal - * @param string $name - * @param Carbon $date - * - * @return void - */ - public function setMetaDate(TransactionJournal $journal, string $name, Carbon $date): void; + public function reconcileById(int $journalId): void; /** * @param TransactionJournal $journal - * @param int $order + * @param int $order * * @return bool */ @@ -333,27 +344,11 @@ interface JournalRepositoryInterface */ public function setUser(User $user); - /** - * @param array $data - * - * @throws FireflyException - * @return TransactionJournal - */ - public function store(array $data): TransactionJournal; - - /** - * @param TransactionJournal $journal - * @param array $data - * - * @return TransactionJournal - */ - public function update(TransactionJournal $journal, array $data): TransactionJournal; - /** * Update budget for a journal. * * @param TransactionJournal $journal - * @param int $budgetId + * @param int $budgetId * * @return TransactionJournal */ @@ -363,7 +358,7 @@ interface JournalRepositoryInterface * Update category for a journal. * * @param TransactionJournal $journal - * @param string $category + * @param string $category * * @return TransactionJournal */ @@ -373,7 +368,7 @@ interface JournalRepositoryInterface * Update tag(s) for a journal. * * @param TransactionJournal $journal - * @param array $tags + * @param array $tags * * @return TransactionJournal */ diff --git a/app/Repositories/LinkType/LinkTypeRepository.php b/app/Repositories/LinkType/LinkTypeRepository.php index d4aab759af..cba90c91bb 100644 --- a/app/Repositories/LinkType/LinkTypeRepository.php +++ b/app/Repositories/LinkType/LinkTypeRepository.php @@ -47,7 +47,7 @@ class LinkTypeRepository implements LinkTypeRepositoryInterface public function __construct() { if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } diff --git a/app/Repositories/PiggyBank/PiggyBankRepository.php b/app/Repositories/PiggyBank/PiggyBankRepository.php index dda694106c..fd80bca177 100644 --- a/app/Repositories/PiggyBank/PiggyBankRepository.php +++ b/app/Repositories/PiggyBank/PiggyBankRepository.php @@ -51,13 +51,13 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface public function __construct() { if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } /** * @param PiggyBank $piggyBank - * @param string $amount + * @param string $amount * * @return bool */ @@ -79,7 +79,7 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface /** * @param PiggyBankRepetition $repetition - * @param string $amount + * @param string $amount * * @return string */ @@ -94,7 +94,7 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface /** * @param PiggyBank $piggyBank - * @param string $amount + * @param string $amount * * @return bool */ @@ -110,7 +110,7 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface /** * @param PiggyBank $piggyBank - * @param string $amount + * @param string $amount * * @return bool */ @@ -143,7 +143,7 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface /** * @param PiggyBank $piggyBank - * @param string $amount + * @param string $amount * * @return PiggyBankEvent */ @@ -156,8 +156,8 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface } /** - * @param PiggyBank $piggyBank - * @param string $amount + * @param PiggyBank $piggyBank + * @param string $amount * @param TransactionJournal $journal * * @return PiggyBankEvent @@ -199,6 +199,9 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface public function findByName(string $name): ?PiggyBank { $set = $this->user->piggyBanks()->get(['piggy_banks.*']); + + // TODO no longer need to loop like this + /** @var PiggyBank $piggy */ foreach ($set as $piggy) { if ($piggy->name === $name) { @@ -224,6 +227,37 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface return null; } + /** + * @param int|null $piggyBankId + * @param string|null $piggyBankName + * + * @return PiggyBank|null + */ + public function findPiggyBank(?int $piggyBankId, ?string $piggyBankName): ?PiggyBank + { + Log::debug('Searching for piggy information.'); + + if (null !== $piggyBankId) { + $searchResult = $this->findNull((int)$piggyBankId); + if (null !== $searchResult) { + Log::debug(sprintf('Found piggy based on #%d, will return it.', $piggyBankId)); + + return $searchResult; + } + } + if (null !== $piggyBankName) { + $searchResult = $this->findByName((string)$piggyBankName); + if (null !== $searchResult) { + Log::debug(sprintf('Found piggy based on "%s", will return it.', $piggyBankName)); + + return $searchResult; + } + } + Log::debug('Found nothing'); + + return null; + } + /** * Get current amount saved in piggy bank. * @@ -254,9 +288,9 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface /** * Used for connecting to a piggy bank. * - * @param PiggyBank $piggyBank + * @param PiggyBank $piggyBank * @param PiggyBankRepetition $repetition - * @param TransactionJournal $journal + * @param TransactionJournal $journal * * @return string * @SuppressWarnings(PHPMD.CyclomaticComplexity) @@ -274,7 +308,7 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface Log::debug(sprintf('Will add/remove %f to piggy bank #%d ("%s")', $amount, $piggyBank->id, $piggyBank->name)); // if piggy account matches source account, the amount is positive - if (\in_array($piggyBank->account_id, $sources, true)) { + if (in_array($piggyBank->account_id, $sources, true)) { $amount = bcmul($amount, '-1'); Log::debug(sprintf('Account #%d is the source, so will remove amount from piggy bank.', $piggyBank->account_id)); } @@ -400,36 +434,11 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface return $savePerMonth; } - /** - * @param PiggyBankEvent $event - * - * @return int|null - */ - public function getTransactionWithEvent(PiggyBankEvent $event): ?int - { - $journal = $event->transactionJournal; - if (null === $journal) { - return null; - } - if ((float)$event->amount < 0) { - $transaction = $journal->transactions()->where('amount', '<', 0)->first(); - - return $transaction->id ?? null; - } - if ((float)$event->amount > 0) { - $transaction = $journal->transactions()->where('amount', '>', 0)->first(); - - return $transaction->id ?? null; - } - - return null; - } - /** * Get for piggy account what is left to put in piggies. * * @param PiggyBank $piggyBank - * @param Carbon $date + * @param Carbon $date * * @return string */ @@ -454,7 +463,7 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface /** * @param PiggyBank $piggyBank - * @param string $amount + * @param string $amount * * @return bool */ @@ -474,7 +483,7 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface * set id of piggy bank. * * @param PiggyBank $piggyBank - * @param int $order + * @param int $order * * @return bool */ @@ -519,7 +528,7 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface /** * @param PiggyBank $piggyBank - * @param array $data + * @param array $data * * @return PiggyBank */ @@ -551,7 +560,7 @@ class PiggyBankRepository implements PiggyBankRepositoryInterface /** * @param PiggyBank $piggyBank - * @param string $note + * @param string $note * * @return bool * @throws \Exception diff --git a/app/Repositories/PiggyBank/PiggyBankRepositoryInterface.php b/app/Repositories/PiggyBank/PiggyBankRepositoryInterface.php index c8a789dc68..631d522521 100644 --- a/app/Repositories/PiggyBank/PiggyBankRepositoryInterface.php +++ b/app/Repositories/PiggyBank/PiggyBankRepositoryInterface.php @@ -35,7 +35,6 @@ use Illuminate\Support\Collection; */ interface PiggyBankRepositoryInterface { - /** * @param PiggyBank $piggyBank * @param string $amount @@ -117,6 +116,14 @@ interface PiggyBankRepositoryInterface */ public function findNull(int $piggyBankId): ?PiggyBank; + /** + * @param int|null $piggyBankId + * @param string|null $piggyBankName + * + * @return PiggyBank|null + */ + public function findPiggyBank(?int $piggyBankId, ?string $piggyBankName): ?PiggyBank; + /** * Get current amount saved in piggy bank. * @@ -192,13 +199,6 @@ interface PiggyBankRepositoryInterface */ public function getSuggestedMonthlyAmount(PiggyBank $piggyBank): string; - /** - * @param PiggyBankEvent $event - * - * @return int|null - */ - public function getTransactionWithEvent(PiggyBankEvent $event): ?int; - /** * Get for piggy account what is left to put in piggies. * diff --git a/app/Repositories/Recurring/RecurringRepository.php b/app/Repositories/Recurring/RecurringRepository.php index 82b8b086fd..62c75bab04 100644 --- a/app/Repositories/Recurring/RecurringRepository.php +++ b/app/Repositories/Recurring/RecurringRepository.php @@ -26,9 +26,9 @@ namespace FireflyIII\Repositories\Recurring; use Carbon\Carbon; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Factory\RecurrenceFactory; -use FireflyIII\Helpers\Collector\TransactionCollectorInterface; -use FireflyIII\Helpers\Filter\InternalTransferFilter; +use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Models\Note; +use FireflyIII\Models\PiggyBank; use FireflyIII\Models\Preference; use FireflyIII\Models\Recurrence; use FireflyIII\Models\RecurrenceMeta; @@ -62,7 +62,7 @@ class RecurringRepository implements RecurringRepositoryInterface public function __construct() { if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } @@ -151,7 +151,7 @@ class RecurringRepository implements RecurringRepositoryInterface /** * Returns the journals created for this recurrence, possibly limited by time. * - * @param Recurrence $recurrence + * @param Recurrence $recurrence * @param Carbon|null $start * @param Carbon|null $end * @@ -209,12 +209,30 @@ class RecurringRepository implements RecurringRepositoryInterface return ''; } + /** + * @param Recurrence $recurrence + * @return PiggyBank|null + */ + public function getPiggyBank(Recurrence $recurrence): ?PiggyBank + { + $meta = $recurrence->recurrenceMeta; + /** @var RecurrenceMeta $metaEntry */ + foreach ($meta as $metaEntry) { + if ('piggy_bank_id' === $metaEntry->name) { + $piggyId = (int)$metaEntry->value; + return $this->user->piggyBanks()->where('id', $piggyId)->first(['piggy_banks.*']); + } + } + + return null; + } + /** * Generate events in the date range. * * @param RecurrenceRepetition $repetition - * @param Carbon $start - * @param Carbon $end + * @param Carbon $start + * @param Carbon $end * * * @return array @@ -274,8 +292,8 @@ class RecurringRepository implements RecurringRepositoryInterface /** * @param Recurrence $recurrence - * @param int $page - * @param int $pageSize + * @param int $page + * @param int $pageSize * * @return LengthAwarePaginator */ @@ -290,17 +308,17 @@ class RecurringRepository implements RecurringRepositoryInterface ->get()->pluck('transaction_journal_id')->toArray(); $search = []; foreach ($journalMeta as $journalId) { - $search[] = ['id' => (int)$journalId]; + $search[] = (int)$journalId; } - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setUser($recurrence->user); - $collector->withOpposingAccount()->setAllAssetAccounts()->withCategoryInformation()->withBudgetInformation()->setLimit($pageSize)->setPage($page); - // filter on specific journals. - $collector->removeFilter(InternalTransferFilter::class); - $collector->setJournals(new Collection($search)); + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); - return $collector->getPaginatedTransactions(); + $collector->setUser($recurrence->user); + $collector->withCategoryInformation()->withBudgetInformation()->setLimit($pageSize)->setPage($page) + ->withAccountInformation(); + $collector->setJournalIds($search); + + return $collector->getPaginatedGroups(); } /** @@ -318,26 +336,34 @@ class RecurringRepository implements RecurringRepositoryInterface ->where('data', json_encode((string)$recurrence->id)) ->get()->pluck('transaction_journal_id')->toArray(); $search = []; - foreach ($journalMeta as $journalId) { - $search[] = ['id' => (int)$journalId]; - } - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setUser($recurrence->user); - $collector->withOpposingAccount()->setAllAssetAccounts()->withCategoryInformation()->withBudgetInformation(); - // filter on specific journals. - $collector->removeFilter(InternalTransferFilter::class); - $collector->setJournals(new Collection($search)); - return $collector->getTransactions(); + + + foreach ($journalMeta as $journalId) { + $search[] = (int)$journalId; + } + if (0 === count($search)) { + + return []; + } + + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + + $collector->setUser($recurrence->user); + $collector->withCategoryInformation()->withBudgetInformation()->withAccountInformation(); + // filter on specific journals. + $collector->setJournalIds($search); + + return $collector->getGroups(); } /** * Calculate the next X iterations starting on the date given in $date. * * @param RecurrenceRepetition $repetition - * @param Carbon $date - * @param int $count + * @param Carbon $date + * @param int $count * * @return array * @SuppressWarnings(PHPMD.CyclomaticComplexity) @@ -428,20 +454,26 @@ class RecurringRepository implements RecurringRepositoryInterface * @param array $data * * @return Recurrence + * @throws FireflyException */ public function store(array $data): Recurrence { - $factory = new RecurrenceFactory; + /** @var RecurrenceFactory $factory */ + $factory = app(RecurrenceFactory::class); $factory->setUser($this->user); + $result = $factory->create($data); + if (null === $result) { + throw new FireflyException($factory->getErrors()->first()); + } - return $factory->create($data); + return $result; } /** * Update a recurring transaction. * * @param Recurrence $recurrence - * @param array $data + * @param array $data * * @return Recurrence * @throws FireflyException diff --git a/app/Repositories/Recurring/RecurringRepositoryInterface.php b/app/Repositories/Recurring/RecurringRepositoryInterface.php index fb28828be9..b18caa5e93 100644 --- a/app/Repositories/Recurring/RecurringRepositoryInterface.php +++ b/app/Repositories/Recurring/RecurringRepositoryInterface.php @@ -25,6 +25,7 @@ namespace FireflyIII\Repositories\Recurring; use Carbon\Carbon; use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Models\PiggyBank; use FireflyIII\Models\Recurrence; use FireflyIII\Models\RecurrenceRepetition; use FireflyIII\Models\RecurrenceTransaction; @@ -114,12 +115,16 @@ interface RecurringRepositoryInterface * @param Carbon $start * @param Carbon $end * - * @throws FireflyException - * * @return array */ public function getOccurrencesInRange(RecurrenceRepetition $repetition, Carbon $start, Carbon $end): array; + /** + * @param Recurrence $recurrence + * @return PiggyBank|null + */ + public function getPiggyBank(Recurrence $recurrence): ?PiggyBank; + /** * Get the tags from the recurring transaction. * @@ -176,10 +181,9 @@ interface RecurringRepositoryInterface /** * Store a new recurring transaction. - *\ * * @param array $data - * + * @throws FireflyException * @return Recurrence */ public function store(array $data): Recurrence; diff --git a/app/Repositories/Rule/RuleRepository.php b/app/Repositories/Rule/RuleRepository.php index 46ac34376c..f86f221f30 100644 --- a/app/Repositories/Rule/RuleRepository.php +++ b/app/Repositories/Rule/RuleRepository.php @@ -47,7 +47,7 @@ class RuleRepository implements RuleRepositoryInterface public function __construct() { if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } @@ -277,7 +277,7 @@ class RuleRepository implements RuleRepositoryInterface */ public function resetRulesInGroupOrder(RuleGroup $ruleGroup): bool { - $ruleGroup->rules()->whereNotNull('deleted_at')->update(['order' => 0]); + $ruleGroup->rules()->withTrashed()->whereNotNull('deleted_at')->update(['order' => 0]); $set = $ruleGroup->rules() ->orderBy('order', 'ASC') @@ -325,7 +325,7 @@ class RuleRepository implements RuleRepositoryInterface $rule->strict = $data['strict']; $rule->stop_processing = $data['stop_processing']; $rule->title = $data['title']; - $rule->description = \strlen($data['description']) > 0 ? $data['description'] : null; + $rule->description = strlen($data['description']) > 0 ? $data['description'] : null; $rule->save(); diff --git a/app/Repositories/RuleGroup/RuleGroupRepository.php b/app/Repositories/RuleGroup/RuleGroupRepository.php index 1cb3b47905..d27ee4c073 100644 --- a/app/Repositories/RuleGroup/RuleGroupRepository.php +++ b/app/Repositories/RuleGroup/RuleGroupRepository.php @@ -43,7 +43,7 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface public function __construct() { if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } @@ -56,7 +56,7 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface } /** - * @param RuleGroup $ruleGroup + * @param RuleGroup $ruleGroup * @param RuleGroup|null $moveTo * * @return bool @@ -85,6 +85,49 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface return true; } + /** + * @return bool + */ + public function resetRuleGroupOrder(): bool + { + $this->user->ruleGroups()->whereNotNull('deleted_at')->update(['order' => 0]); + + $set = $this->user->ruleGroups()->where('active', 1)->orderBy('order', 'ASC')->get(); + $count = 1; + /** @var RuleGroup $entry */ + foreach ($set as $entry) { + $entry->order = $count; + $entry->save(); + ++$count; + } + + return true; + } + + /** + * @param RuleGroup $ruleGroup + * + * @return bool + */ + public function resetRulesInGroupOrder(RuleGroup $ruleGroup): bool + { + $ruleGroup->rules()->whereNotNull('deleted_at')->update(['order' => 0]); + + $set = $ruleGroup->rules() + ->orderBy('order', 'ASC') + ->orderBy('updated_at', 'DESC') + ->get(); + $count = 1; + /** @var Rule $entry */ + foreach ($set as $entry) { + $entry->order = $count; + $entry->save(); + ++$count; + } + + return true; + } + /** * @param int $ruleGroupId * @@ -109,13 +152,11 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface } /** - * @param User $user - * * @return Collection */ - public function getActiveGroups(User $user): Collection + public function getActiveGroups(): Collection { - return $user->ruleGroups()->where('rule_groups.active', 1)->orderBy('order', 'ASC')->get(['rule_groups.*']); + return $this->user->ruleGroups()->with(['rules'])->where('rule_groups.active', 1)->orderBy('order', 'ASC')->get(['rule_groups.*']); } /** @@ -160,16 +201,6 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface ->get(['rules.*']); } - /** - * @return int - */ - public function getHighestOrderRuleGroup(): int - { - $entry = $this->user->ruleGroups()->max('order'); - - return (int)$entry; - } - /** * @param User $user * @@ -253,49 +284,6 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface return true; } - /** - * @return bool - */ - public function resetRuleGroupOrder(): bool - { - $this->user->ruleGroups()->whereNotNull('deleted_at')->update(['order' => 0]); - - $set = $this->user->ruleGroups()->where('active', 1)->orderBy('order', 'ASC')->get(); - $count = 1; - /** @var RuleGroup $entry */ - foreach ($set as $entry) { - $entry->order = $count; - $entry->save(); - ++$count; - } - - return true; - } - - /** - * @param RuleGroup $ruleGroup - * - * @return bool - */ - public function resetRulesInGroupOrder(RuleGroup $ruleGroup): bool - { - $ruleGroup->rules()->whereNotNull('deleted_at')->update(['order' => 0]); - - $set = $ruleGroup->rules() - ->orderBy('order', 'ASC') - ->orderBy('updated_at', 'DESC') - ->get(); - $count = 1; - /** @var Rule $entry */ - foreach ($set as $entry) { - $entry->order = $count; - $entry->save(); - ++$count; - } - - return true; - } - /** * @param User $user */ @@ -328,9 +316,19 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface return $newRuleGroup; } + /** + * @return int + */ + public function getHighestOrderRuleGroup(): int + { + $entry = $this->user->ruleGroups()->max('order'); + + return (int)$entry; + } + /** * @param RuleGroup $ruleGroup - * @param array $data + * @param array $data * * @return RuleGroup */ @@ -345,4 +343,14 @@ class RuleGroupRepository implements RuleGroupRepositoryInterface return $ruleGroup; } + + /** + * @param string $title + * + * @return RuleGroup|null + */ + public function findByTitle(string $title): ?RuleGroup + { + return $this->user->ruleGroups()->where('title', $title)->first(); + } } diff --git a/app/Repositories/RuleGroup/RuleGroupRepositoryInterface.php b/app/Repositories/RuleGroup/RuleGroupRepositoryInterface.php index 17363456e0..45847bb7c4 100644 --- a/app/Repositories/RuleGroup/RuleGroupRepositoryInterface.php +++ b/app/Repositories/RuleGroup/RuleGroupRepositoryInterface.php @@ -37,7 +37,7 @@ interface RuleGroupRepositoryInterface public function count(): int; /** - * @param RuleGroup $ruleGroup + * @param RuleGroup $ruleGroup * @param RuleGroup|null $moveTo * * @return bool @@ -51,6 +51,13 @@ interface RuleGroupRepositoryInterface */ public function find(int $ruleGroupId): ?RuleGroup; + /** + * @param string $title + * + * @return RuleGroup|null + */ + public function findByTitle(string $title): ?RuleGroup; + /** * Get all rule groups. * @@ -59,11 +66,9 @@ interface RuleGroupRepositoryInterface public function get(): Collection; /** - * @param User $user - * * @return Collection */ - public function getActiveGroups(User $user): Collection; + public function getActiveGroups(): Collection; /** * @param RuleGroup $group @@ -145,7 +150,7 @@ interface RuleGroupRepositoryInterface /** * @param RuleGroup $ruleGroup - * @param array $data + * @param array $data * * @return RuleGroup */ diff --git a/app/Repositories/Tag/TagRepository.php b/app/Repositories/Tag/TagRepository.php index 9bca4cb58e..208710f825 100644 --- a/app/Repositories/Tag/TagRepository.php +++ b/app/Repositories/Tag/TagRepository.php @@ -25,8 +25,7 @@ namespace FireflyIII\Repositories\Tag; use Carbon\Carbon; use DB; use FireflyIII\Factory\TagFactory; -use FireflyIII\Helpers\Collector\TransactionCollectorInterface; -use FireflyIII\Helpers\Filter\InternalTransferFilter; +use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Models\Tag; use FireflyIII\Models\TransactionType; use FireflyIII\User; @@ -50,7 +49,7 @@ class TagRepository implements TagRepositoryInterface public function __construct() { if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } @@ -76,7 +75,7 @@ class TagRepository implements TagRepositoryInterface } /** - * @param Tag $tag + * @param Tag $tag * @param Carbon $start * @param Carbon $end * @@ -84,30 +83,31 @@ class TagRepository implements TagRepositoryInterface */ public function earnedInPeriod(Tag $tag, Carbon $start, Carbon $end): string { - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setUser($this->user); - $collector->setRange($start, $end)->setTypes([TransactionType::DEPOSIT])->setAllAssetAccounts()->setTag($tag); - $set = $collector->getTransactions(); + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); - return (string)$set->sum('transaction_amount'); + $collector->setUser($this->user); + $collector->setRange($start, $end)->setTypes([TransactionType::DEPOSIT])->setTag($tag); + + return $collector->getSum(); } /** - * @param Tag $tag + * @param Tag $tag * @param Carbon $start * @param Carbon $end * - * @return Collection + * @return array */ - public function expenseInPeriod(Tag $tag, Carbon $start, Carbon $end): Collection + public function expenseInPeriod(Tag $tag, Carbon $start, Carbon $end): array { - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setUser($this->user); - $collector->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])->setAllAssetAccounts()->setTag($tag); + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); - return $collector->getTransactions(); + $collector->setUser($this->user); + $collector->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])->setTag($tag); + + return $collector->getExtractedJournals(); } /** @@ -117,15 +117,7 @@ class TagRepository implements TagRepositoryInterface */ public function findByTag(string $tag): ?Tag { - $tags = $this->user->tags()->get(); - /** @var Tag $databaseTag */ - foreach ($tags as $databaseTag) { - if ($databaseTag->tag === $tag) { - return $databaseTag; - } - } - - return null; + return $this->user->tags()->where('tag', $tag)->first(); } /** @@ -159,31 +151,27 @@ class TagRepository implements TagRepositoryInterface public function get(): Collection { /** @var Collection $tags */ - $tags = $this->user->tags()->get(); - $tags = $tags->sortBy( - function (Tag $tag) { - return strtolower($tag->tag); - } - ); + $tags = $this->user->tags()->orderBy('tag', 'ASC')->get(); return $tags; } /** - * @param Tag $tag + * @param Tag $tag * @param Carbon $start * @param Carbon $end * - * @return Collection + * @return array */ - public function incomeInPeriod(Tag $tag, Carbon $start, Carbon $end): Collection + public function incomeInPeriod(Tag $tag, Carbon $start, Carbon $end): array { - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setUser($this->user); - $collector->setRange($start, $end)->setTypes([TransactionType::DEPOSIT])->setAllAssetAccounts()->setTag($tag); + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); - return $collector->getTransactions(); + $collector->setUser($this->user); + $collector->setRange($start, $end)->setTypes([TransactionType::DEPOSIT])->setTag($tag); + + return $collector->getExtractedJournals(); } /** @@ -219,6 +207,25 @@ class TagRepository implements TagRepositoryInterface return $this->user->tags()->whereNotNull('date')->orderBy('date', 'ASC')->first(); } + /** + * Search the users tags. + * + * @param string $query + * + * @return Collection + */ + public function searchTags(string $query): Collection + { + /** @var Collection $tags */ + $tags = $this->user->tags()->orderBy('tag', 'ASC'); + if ('' !== $query) { + $search = sprintf('%%%s%%', $query); + $tags->where('tag', 'LIKE', $search); + } + + return $tags->get(); + } + /** * @param User $user */ @@ -228,7 +235,7 @@ class TagRepository implements TagRepositoryInterface } /** - * @param Tag $tag + * @param Tag $tag * @param Carbon $start * @param Carbon $end * @@ -236,13 +243,13 @@ class TagRepository implements TagRepositoryInterface */ public function spentInPeriod(Tag $tag, Carbon $start, Carbon $end): string { - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setUser($this->user); - $collector->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])->setAllAssetAccounts()->setTag($tag); - $set = $collector->getTransactions(); + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); - return (string)$set->sum('transaction_amount'); + $collector->setUser($this->user); + $collector->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])->setTag($tag); + + return $collector->getSum(); } /** @@ -260,7 +267,7 @@ class TagRepository implements TagRepositoryInterface } /** - * @param Tag $tag + * @param Tag $tag * @param Carbon|null $start * @param Carbon|null $end * @@ -269,16 +276,15 @@ class TagRepository implements TagRepositoryInterface */ public function sumsOfTag(Tag $tag, ?Carbon $start, ?Carbon $end): array { - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); if (null !== $start && null !== $end) { $collector->setRange($start, $end); } - $collector->setAllAssetAccounts()->setTag($tag)->withOpposingAccount(); - $collector->removeFilter(InternalTransferFilter::class); - $transactions = $collector->getTransactions(); + $collector->setTag($tag)->withAccountInformation(); + $journals = $collector->getExtractedJournals(); $sums = [ TransactionType::WITHDRAWAL => '0', @@ -286,9 +292,10 @@ class TagRepository implements TagRepositoryInterface TransactionType::TRANSFER => '0', ]; - foreach ($transactions as $transaction) { - $amount = app('steam')->positive((string)$transaction->transaction_amount); - $type = $transaction->transaction_type_type; + /** @var array $journal */ + foreach ($journals as $journal) { + $amount = app('steam')->positive((string)$journal['amount']); + $type = $journal['transaction_type_type']; if (TransactionType::WITHDRAWAL === $type) { $amount = bcmul($amount, '-1'); } @@ -319,7 +326,8 @@ class TagRepository implements TagRepositoryInterface Log::debug(sprintf('Minimum is %s, maximum is %s, difference is %s', $min, $max, $diff)); - if (0 !== bccomp($diff, '0')) {// for each full coin in tag, add so many points + if (0 !== bccomp($diff, '0')) { // for each full coin in tag, add so many points + // minus the smallest tag. $pointsPerCoin = bcdiv($maxPoints, $diff); } @@ -328,7 +336,8 @@ class TagRepository implements TagRepositoryInterface foreach ($tags as $tag) { $amount = (string)$tag->amount_sum; $amount = '' === $amount ? '0' : $amount; - $pointsForTag = bcmul($amount, $pointsPerCoin); + $amountMin = bcsub($amount, $min); + $pointsForTag = bcmul($amountMin, $pointsPerCoin); $fontSize = bcadd($minimumFont, $pointsForTag); Log::debug(sprintf('Tag "%s": Amount is %s, so points is %s', $tag->tag, $amount, $fontSize)); @@ -344,39 +353,37 @@ class TagRepository implements TagRepositoryInterface } /** - * @param Tag $tag - * @param Carbon $start - * @param Carbon $end + * @param int|null $year * * @return Collection */ - public function transferredInPeriod(Tag $tag, Carbon $start, Carbon $end): Collection + private function getTagsInYear(?int $year): Collection { - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setUser($this->user); - $collector->setRange($start, $end)->setTypes([TransactionType::TRANSFER])->setAllAssetAccounts()->setTag($tag); + // get all tags in the year (if present): + $tagQuery = $this->user->tags() + ->leftJoin('tag_transaction_journal', 'tag_transaction_journal.tag_id', '=', 'tags.id') + ->leftJoin('transaction_journals', 'tag_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') + ->leftJoin('transactions', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') + ->where( + function (Builder $query) { + $query->where('transactions.amount', '>', 0); + $query->orWhereNull('transactions.amount'); + } + ) + ->groupBy(['tags.id', 'tags.tag']); - return $collector->getTransactions(); - } + // add date range (or not): + if (null === $year) { + Log::debug('Get tags without a date.'); + $tagQuery->whereNull('tags.date'); + } + if (null !== $year) { + Log::debug(sprintf('Get tags with year %s.', $year)); + $tagQuery->where('tags.date', '>=', $year . '-01-01 00:00:00')->where('tags.date', '<=', $year . '-12-31 23:59:59'); + } - /** - * @param Tag $tag - * @param array $data - * - * @return Tag - */ - public function update(Tag $tag, array $data): Tag - { - $tag->tag = $data['tag']; - $tag->date = $data['date']; - $tag->description = $data['description']; - $tag->latitude = $data['latitude']; - $tag->longitude = $data['longitude']; - $tag->zoomLevel = $data['zoom_level']; - $tag->save(); + return $tagQuery->get(['tags.id', 'tags.tag', DB::raw('SUM(transactions.amount) as amount_sum')]); - return $tag; } /** @@ -430,36 +437,38 @@ class TagRepository implements TagRepositoryInterface } /** - * @param int|null $year + * @param Tag $tag + * @param Carbon $start + * @param Carbon $end * - * @return Collection + * @return array */ - private function getTagsInYear(?int $year): Collection + public function transferredInPeriod(Tag $tag, Carbon $start, Carbon $end): array { - // get all tags in the year (if present): - $tagQuery = $this->user->tags() - ->leftJoin('tag_transaction_journal', 'tag_transaction_journal.tag_id', '=', 'tags.id') - ->leftJoin('transaction_journals', 'tag_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id') - ->leftJoin('transactions', 'transaction_journals.id', '=', 'transactions.transaction_journal_id') - ->where( - function (Builder $query) { - $query->where('transactions.amount', '>', 0); - $query->orWhereNull('transactions.amount'); - } - ) - ->groupBy(['tags.id', 'tags.tag']); + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setUser($this->user); + $collector->setRange($start, $end)->setTypes([TransactionType::TRANSFER])->setTag($tag); - // add date range (or not): - if (null === $year) { - Log::debug('Get tags without a date.'); - $tagQuery->whereNull('tags.date'); - } - if (null !== $year) { - Log::debug(sprintf('Get tags with year %s.', $year)); - $tagQuery->where('tags.date', '>=', $year . '-01-01 00:00:00')->where('tags.date', '<=', $year . '-12-31 23:59:59'); - } + return $collector->getExtractedJournals(); + } - return $tagQuery->get(['tags.id', 'tags.tag', DB::raw('SUM(transactions.amount) as amount_sum')]); + /** + * @param Tag $tag + * @param array $data + * + * @return Tag + */ + public function update(Tag $tag, array $data): Tag + { + $tag->tag = $data['tag']; + $tag->date = $data['date']; + $tag->description = $data['description']; + $tag->latitude = $data['latitude']; + $tag->longitude = $data['longitude']; + $tag->zoomLevel = $data['zoom_level']; + $tag->save(); + return $tag; } } diff --git a/app/Repositories/Tag/TagRepositoryInterface.php b/app/Repositories/Tag/TagRepositoryInterface.php index 223b61e22c..a8411f31ee 100644 --- a/app/Repositories/Tag/TagRepositoryInterface.php +++ b/app/Repositories/Tag/TagRepositoryInterface.php @@ -60,9 +60,9 @@ interface TagRepositoryInterface * @param Carbon $start * @param Carbon $end * - * @return Collection + * @return array */ - public function expenseInPeriod(Tag $tag, Carbon $start, Carbon $end): Collection; + public function expenseInPeriod(Tag $tag, Carbon $start, Carbon $end): array; /** * @param string $tag @@ -97,9 +97,9 @@ interface TagRepositoryInterface * @param Carbon $start * @param Carbon $end * - * @return Collection + * @return array */ - public function incomeInPeriod(Tag $tag, Carbon $start, Carbon $end): Collection; + public function incomeInPeriod(Tag $tag, Carbon $start, Carbon $end): array; /** * @param Tag $tag @@ -122,6 +122,15 @@ interface TagRepositoryInterface */ public function oldestTag(): ?Tag; + /** + * Search the users tags. + * + * @param string $query + * + * @return Collection + */ + public function searchTags(string $query): Collection; + /** * @param User $user */ @@ -170,9 +179,9 @@ interface TagRepositoryInterface * @param Carbon $start * @param Carbon $end * - * @return Collection + * @return array */ - public function transferredInPeriod(Tag $tag, Carbon $start, Carbon $end): Collection; + public function transferredInPeriod(Tag $tag, Carbon $start, Carbon $end): array; /** * Update a tag. diff --git a/app/Repositories/TransactionGroup/TransactionGroupRepository.php b/app/Repositories/TransactionGroup/TransactionGroupRepository.php new file mode 100644 index 0000000000..679d203136 --- /dev/null +++ b/app/Repositories/TransactionGroup/TransactionGroupRepository.php @@ -0,0 +1,378 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Repositories\TransactionGroup; + + +use Carbon\Carbon; +use DB; +use Exception; +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Factory\TransactionGroupFactory; +use FireflyIII\Models\AccountMeta; +use FireflyIII\Models\Attachment; +use FireflyIII\Models\Note; +use FireflyIII\Models\PiggyBankEvent; +use FireflyIII\Models\Transaction; +use FireflyIII\Models\TransactionCurrency; +use FireflyIII\Models\TransactionGroup; +use FireflyIII\Models\TransactionJournal; +use FireflyIII\Models\TransactionJournalLink; +use FireflyIII\Models\TransactionType; +use FireflyIII\Services\Internal\Destroy\TransactionGroupDestroyService; +use FireflyIII\Services\Internal\Update\GroupUpdateService; +use FireflyIII\Support\NullArrayObject; +use Illuminate\Database\Eloquent\Builder; + +/** + * Class TransactionGroupRepository + */ +class TransactionGroupRepository implements TransactionGroupRepositoryInterface +{ + private $user; + + /** + * Constructor. + */ + public function __construct() + { + if ('testing' === config('app.env')) { + app('log')->warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); + } + } + + /** + * Return all attachments for all journals in the group. + * + * @param TransactionGroup $group + * + * @return array + */ + public function getAttachments(TransactionGroup $group): array + { + $journals = $group->transactionJournals->pluck('id')->toArray(); + $set = Attachment::whereIn('attachable_id', $journals) + ->where('attachable_type', TransactionJournal::class) + ->whereNull('deleted_at')->get(); + + $result = []; + /** @var Attachment $attachment */ + foreach ($set as $attachment) { + $journalId = (int)$attachment->attachable_id; + $result[$journalId] = $result[$journalId] ?? []; + $current = $attachment->toArray(); + $current['file_exists'] = true; + $current['journal_title'] = $attachment->attachable->description; + $result[$journalId][] = $current; + + } + + return $result; + } + + /** + * Return all journal links for all journals in the group. + * + * @param TransactionGroup $group + * + * @return array + */ + public function getLinks(TransactionGroup $group): array + { + $return = []; + $journals = $group->transactionJournals->pluck('id')->toArray(); + $set = TransactionJournalLink + ::where( + static function (Builder $q) use ($journals) { + $q->whereIn('source_id', $journals); + $q->orWhereIn('destination_id', $journals); + } + ) + ->with(['source', 'destination', 'source.transactions']) + ->leftJoin('link_types', 'link_types.id', '=', 'journal_links.link_type_id') + ->get(['journal_links.*', 'link_types.inward', 'link_types.outward']); + /** @var TransactionJournalLink $entry */ + foreach ($set as $entry) { + $journalId = in_array($entry->source_id, $journals, true) ? $entry->source_id : $entry->destination_id; + $return[$journalId] = $return[$journalId] ?? []; + + if ($journalId === $entry->source_id) { + $amount = $this->getFormattedAmount($entry->destination); + $foreignAmount = $this->getFormattedForeignAmount($entry->destination); + $return[$journalId][] = [ + 'id' => $entry->id, + 'link' => $entry->outward, + 'group' => $entry->destination->transaction_group_id, + 'description' => $entry->destination->description, + 'amount' => $amount, + 'foreign_amount' => $foreignAmount, + ]; + } + if ($journalId === $entry->destination_id) { + $amount = $this->getFormattedAmount($entry->source); + $foreignAmount = $this->getFormattedForeignAmount($entry->source); + $return[$journalId][] = [ + 'id' => $entry->id, + 'link' => $entry->inward, + 'group' => $entry->source->transaction_group_id, + 'description' => $entry->source->description, + 'amount' => $amount, + 'foreign_amount' => $foreignAmount, + ]; + } + } + + return $return; + } + + /** + * Return object with all found meta field things as Carbon objects. + * + * @param int $journalId + * @param array $fields + * + * @return NullArrayObject + * @throws Exception + */ + public function getMetaDateFields(int $journalId, array $fields): NullArrayObject + { + $query = DB + ::table('journal_meta') + ->where('transaction_journal_id', $journalId) + ->whereIn('name', $fields) + ->whereNull('deleted_at') + ->get(['name', 'data']); + $return = []; + + foreach ($query as $row) { + $return[$row->name] = new Carbon(json_decode($row->data)); + } + + return new NullArrayObject($return); + } + + /** + * Return object with all found meta field things. + * + * @param int $journalId + * @param array $fields + * + * @return NullArrayObject + */ + public function getMetaFields(int $journalId, array $fields): NullArrayObject + { + $query = DB + ::table('journal_meta') + ->where('transaction_journal_id', $journalId) + ->whereIn('name', $fields) + ->whereNull('deleted_at') + ->get(['name', 'data']); + $return = []; + + foreach ($query as $row) { + $return[$row->name] = json_decode($row->data); + } + + return new NullArrayObject($return); + } + + /** + * Get the note text for a journal (by ID). + * + * @param int $journalId + * + * @return string|null + */ + public function getNoteText(int $journalId): ?string + { + /** @var Note $note */ + $note = Note + ::where('noteable_id', $journalId) + ->where('noteable_type', TransactionJournal::class) + ->first(); + if (null === $note) { + return null; + } + + return $note->text; + } + + /** + * Return all piggy bank events for all journals in the group. + * + * @param TransactionGroup $group + * + * @return array + */ + public function getPiggyEvents(TransactionGroup $group): array + { + $return = []; + $journals = $group->transactionJournals->pluck('id')->toArray(); + $data = PiggyBankEvent + ::whereIn('transaction_journal_id', $journals) + ->with('piggyBank', 'piggyBank.account') + ->get(['piggy_bank_events.*']); + /** @var PiggyBankEvent $row */ + foreach ($data as $row) { + // get currency preference. + $currencyPreference = AccountMeta + ::where('account_id', $row->piggyBank->account_id) + ->where('name', 'currency_id') + ->first(); + if (null !== $currencyPreference) { + $currency = TransactionCurrency::where('id', $currencyPreference->data)->first(); + } + if (null === $currencyPreference) { + $currencyCode = app('preferences')->getForUser($this->user, 'currencyPreference', 'EUR')->data; + $currency = TransactionCurrency::where('code', $currencyCode)->first(); + } + $journalId = (int)$row->transaction_journal_id; + $return[$journalId] = $return[$journalId] ?? []; + + $return[$journalId][] = [ + 'piggy' => $row->piggyBank->name, + 'piggy_id' => $row->piggy_bank_id, + 'amount' => app('amount')->formatAnything($currency, $row->amount), + ]; + } + + return $return; + } + + /** + * Get the tags for a journal (by ID). + * + * @param int $journalId + * + * @return array + */ + public function getTags(int $journalId): array + { + $result = DB + ::table('tag_transaction_journal') + ->leftJoin('tags', 'tag_transaction_journal.tag_id', '=', 'tags.id') + ->where('tag_transaction_journal.transaction_journal_id', $journalId) + ->get(['tags.tag']); + + return $result->pluck('tag')->toArray(); + } + + /** + * @param mixed $user + */ + public function setUser($user): void + { + $this->user = $user; + } + + /** + * @param array $data + * + * @return TransactionGroup + */ + public function store(array $data): TransactionGroup + { + /** @var TransactionGroupFactory $factory */ + $factory = app(TransactionGroupFactory::class); + $factory->setUser($this->user); + + return $factory->create($data); + } + + /** + * @param TransactionGroup $transactionGroup + * @param array $data + * + * @return TransactionGroup + * + * @throws FireflyException + */ + public function update(TransactionGroup $transactionGroup, array $data): TransactionGroup + { + /** @var GroupUpdateService $service */ + $service = app(GroupUpdateService::class); + $updatedGroup = $service->update($transactionGroup, $data); + + return $updatedGroup; + } + + /** + * @param TransactionJournal $journal + * + * @return string + */ + private function getFormattedAmount(TransactionJournal $journal): string + { + /** @var Transaction $transaction */ + $transaction = $journal->transactions->first(); + $currency = $transaction->transactionCurrency; + $type = $journal->transactionType->type; + $amount = app('steam')->positive($transaction->amount); + $return = ''; + if (TransactionType::WITHDRAWAL === $type) { + $return = app('amount')->formatAnything($currency, app('steam')->negative($amount)); + } + if (TransactionType::WITHDRAWAL !== $type) { + $return = app('amount')->formatAnything($currency, $amount); + + } + + return $return; + } + + /** + * @param TransactionJournal $journal + * + * @return string + */ + private function getFormattedForeignAmount(TransactionJournal $journal): string + { + /** @var Transaction $transaction */ + $transaction = $journal->transactions->first(); + if (null === $transaction->foreign_amount) { + return ''; + } + $currency = $transaction->foreignCurrency; + $type = $journal->transactionType->type; + $amount = app('steam')->positive($transaction->foreign_amount); + $return = ''; + if (TransactionType::WITHDRAWAL === $type) { + $return = app('amount')->formatAnything($currency, app('steam')->negative($amount)); + } + if (TransactionType::WITHDRAWAL !== $type) { + $return = app('amount')->formatAnything($currency, $amount); + } + + return $return; + } + + /** + * @param TransactionGroup $group + */ + public function destroy(TransactionGroup $group): void + { + /** @var TransactionGroupDestroyService $service */ + $service = new TransactionGroupDestroyService; + $service->destroy($group); + } +} \ No newline at end of file diff --git a/app/Repositories/TransactionGroup/TransactionGroupRepositoryInterface.php b/app/Repositories/TransactionGroup/TransactionGroupRepositoryInterface.php new file mode 100644 index 0000000000..b47713dd43 --- /dev/null +++ b/app/Repositories/TransactionGroup/TransactionGroupRepositoryInterface.php @@ -0,0 +1,133 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Repositories\TransactionGroup; + +use FireflyIII\Models\TransactionGroup; +use FireflyIII\Services\Internal\Destroy\TransactionGroupDestroyService; +use FireflyIII\Support\NullArrayObject; +use FireflyIII\User; + +/** + * Interface TransactionGroupRepositoryInterface + */ +interface TransactionGroupRepositoryInterface +{ + + /** + * Return all attachments for all journals in the group. + * + * @param TransactionGroup $group + * + * @return array + */ + public function getAttachments(TransactionGroup $group): array; + + /** + * @param TransactionGroup $group + */ + public function destroy(TransactionGroup $group): void; + + /** + * Return all journal links for all journals in the group. + * + * @param TransactionGroup $group + * + * @return array + */ + public function getLinks(TransactionGroup $group): array; + + /** + * Return object with all found meta field things as Carbon objects. + * + * @param int $journalId + * @param array $fields + * + * @return NullArrayObject + */ + public function getMetaDateFields(int $journalId, array $fields): NullArrayObject; + + /** + * Return object with all found meta field things. + * + * @param int $journalId + * @param array $fields + * + * @return NullArrayObject + */ + public function getMetaFields(int $journalId, array $fields): NullArrayObject; + + /** + * Get the note text for a journal (by ID). + * + * @param int $journalId + * + * @return string|null + */ + public function getNoteText(int $journalId): ?string; + + /** + * Return all piggy bank events for all journals in the group. + * + * @param TransactionGroup $group + * + * @return array + */ + public function getPiggyEvents(TransactionGroup $group): array; + + /** + * Get the tags for a journal (by ID). + * + * @param int $journalId + * + * @return array + */ + public function getTags(int $journalId): array; + + /** + * Set the user. + * + * @param User $user + */ + public function setUser(User $user): void; + + /** + * Create a new transaction group. + * + * @param array $data + * + * @return TransactionGroup + */ + public function store(array $data): TransactionGroup; + + /** + * Update an existing transaction group. + * + * @param TransactionGroup $transactionGroup + * @param array $data + * + * @return TransactionGroup + */ + public function update(TransactionGroup $transactionGroup, array $data): TransactionGroup; + +} \ No newline at end of file diff --git a/app/Repositories/TransactionType/TransactionTypeRepository.php b/app/Repositories/TransactionType/TransactionTypeRepository.php new file mode 100644 index 0000000000..0e139c6787 --- /dev/null +++ b/app/Repositories/TransactionType/TransactionTypeRepository.php @@ -0,0 +1,84 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Repositories\TransactionType; + +use FireflyIII\Models\TransactionType; +use Illuminate\Support\Collection; +use Log; + +/** + * Class TransactionTypeRepository + */ +class TransactionTypeRepository implements TransactionTypeRepositoryInterface +{ + + /** + * @param string $type + * + * @return TransactionType|null + */ + public function findByType(string $type): ?TransactionType + { + $search = ucfirst($type); + + return TransactionType::whereType($search)->first(); + } + + /** + * @param TransactionType|null $type + * @param string|null $typeString + * + * @return TransactionType + */ + public function findTransactionType(?TransactionType $type, ?string $typeString): TransactionType + { + Log::debug('Now looking for a transaction type.'); + if (null !== $type) { + Log::debug(sprintf('Found $type in parameters, its %s. Will return it.', $type->type)); + + return $type; + } + $typeString = $typeString ?? TransactionType::WITHDRAWAL; + $search = $this->findByType($typeString); + if (null === $search) { + $search = $this->findByType(TransactionType::WITHDRAWAL); + } + Log::debug(sprintf('Tried to search for "%s", came up with "%s". Will return it.', $typeString, $search->type)); + + return $search; + } + + /** + * @param string $query + * @return Collection + */ + public function searchTypes(string $query): Collection + { + if ('' === $query) { + return TransactionType::get(); + } + + return TransactionType::where('type', 'LIKE', sprintf('%%%s%%', $query))->get(); + } +} \ No newline at end of file diff --git a/app/Export/Collector/CollectorInterface.php b/app/Repositories/TransactionType/TransactionTypeRepositoryInterface.php similarity index 54% rename from app/Export/Collector/CollectorInterface.php rename to app/Repositories/TransactionType/TransactionTypeRepositoryInterface.php index c58e538ee1..0300889cf5 100644 --- a/app/Export/Collector/CollectorInterface.php +++ b/app/Repositories/TransactionType/TransactionTypeRepositoryInterface.php @@ -1,8 +1,7 @@ getForUser($user, 'twoFactorAuthEnabled', false)->data; - $has2faSecret = null !== app('preferences')->getForUser($user, 'twoFactorAuthSecret'); - $return['has_2fa'] = false; - if ($is2faEnabled && $has2faSecret) { - $return['has_2fa'] = true; - } - - $return['is_admin'] = $this->hasRole($user, 'owner'); - $return['blocked'] = 1 === (int)$user->blocked; - $return['blocked_code'] = $user->blocked_code; - $return['accounts'] = $user->accounts()->count(); - $return['journals'] = $user->transactionJournals()->count(); - $return['transactions'] = $user->transactions()->count(); - $return['attachments'] = $user->attachments()->count(); + $return['has_2fa'] = $user->mfa_secret !== null; + $return['is_admin'] = $this->hasRole($user, 'owner'); + $return['blocked'] = 1 === (int)$user->blocked; + $return['blocked_code'] = $user->blocked_code; + $return['accounts'] = $user->accounts()->count(); + $return['journals'] = $user->transactionJournals()->count(); + $return['transactions'] = $user->transactions()->count(); + $return['attachments'] = $user->attachments()->count(); $return['attachments_size'] = $user->attachments()->sum('size'); $return['bills'] = $user->bills()->count(); $return['categories'] = $user->categories()->count(); @@ -268,8 +262,6 @@ class UserRepository implements UserRepositoryInterface ->where('amount', '>', 0) ->whereNull('budgets.deleted_at') ->where('budgets.user_id', $user->id)->get(['budget_limits.budget_id'])->count(); - $return['export_jobs'] = $user->exportJobs()->count(); - $return['export_jobs_success'] = $user->exportJobs()->where('status', 'export_downloaded')->count(); $return['import_jobs'] = $user->importJobs()->count(); $return['import_jobs_success'] = $user->importJobs()->where('status', 'finished')->count(); $return['rule_groups'] = $user->ruleGroups()->count(); @@ -287,6 +279,8 @@ class UserRepository implements UserRepositoryInterface */ public function hasRole(User $user, string $role): bool { + // TODO no longer need to loop like this + /** @var Role $userRole */ foreach ($user->roles as $userRole) { if ($userRole->name === $role) { @@ -373,4 +367,16 @@ class UserRepository implements UserRepositoryInterface return true; } + + /** + * Set MFA code. + * + * @param User $user + * @param string|null $code + */ + public function setMFACode(User $user, ?string $code): void + { + $user->mfa_secret = $code; + $user->save(); + } } diff --git a/app/Repositories/User/UserRepositoryInterface.php b/app/Repositories/User/UserRepositoryInterface.php index c01be9f65d..9e3bfdde9f 100644 --- a/app/Repositories/User/UserRepositoryInterface.php +++ b/app/Repositories/User/UserRepositoryInterface.php @@ -39,6 +39,14 @@ interface UserRepositoryInterface */ public function all(): Collection; + /** + * Set MFA code. + * + * @param User $user + * @param string|null $code + */ + public function setMFACode(User $user, ?string $code): void; + /** * Gives a user a role. * diff --git a/app/Rules/BelongsUser.php b/app/Rules/BelongsUser.php index d33e0ef412..c9497d45dc 100644 --- a/app/Rules/BelongsUser.php +++ b/app/Rules/BelongsUser.php @@ -31,6 +31,7 @@ use FireflyIII\Models\Budget; use FireflyIII\Models\Category; use FireflyIII\Models\PiggyBank; use Illuminate\Contracts\Validation\Rule; +use Log; /** * Class BelongsUser @@ -41,6 +42,7 @@ class BelongsUser implements Rule * Create a new rule instance. * * @return void + * @codeCoverageIgnore */ public function __construct() { @@ -51,6 +53,7 @@ class BelongsUser implements Rule * Get the validation error message. * * @return string + * @codeCoverageIgnore */ public function message(): string { @@ -76,6 +79,7 @@ class BelongsUser implements Rule return true; // @codeCoverageIgnore } $attribute = (string)$attribute; + Log::debug(sprintf('Going to validate %s', $attribute)); switch ($attribute) { case 'piggy_bank_id': return $this->validatePiggyBankId((int)$value); @@ -110,6 +114,7 @@ class BelongsUser implements Rule */ protected function countField(string $class, string $field, string $value): int { + $value = trim($value); $objects = []; // get all objects belonging to user: if (PiggyBank::class === $class) { @@ -122,8 +127,11 @@ class BelongsUser implements Rule } $count = 0; foreach ($objects as $object) { - if (trim((string)$object->$field) === trim($value)) { + $objectValue = trim((string)$object->$field); + Log::debug(sprintf('Comparing object "%s" with value "%s"', $objectValue, $value)); + if ($objectValue === $value) { $count++; + Log::debug(sprintf('Hit! Count is now %d', $count)); } } @@ -138,10 +146,10 @@ class BelongsUser implements Rule private function parseAttribute(string $attribute): string { $parts = explode('.', $attribute); - if (1 === \count($parts)) { + if (1 === count($parts)) { return $attribute; } - if (3 === \count($parts)) { + if (3 === count($parts)) { return $parts[2]; } @@ -180,6 +188,7 @@ class BelongsUser implements Rule private function validateBillName(string $value): bool { $count = $this->countField(Bill::class, 'name', $value); + Log::debug(sprintf('Result of countField for bill name "%s" is %d', $value, $count)); return 1 === $count; } diff --git a/app/Rules/IsAssetAccountId.php b/app/Rules/IsAssetAccountId.php index 9a59f823ee..84c6b72bec 100644 --- a/app/Rules/IsAssetAccountId.php +++ b/app/Rules/IsAssetAccountId.php @@ -36,6 +36,7 @@ class IsAssetAccountId implements Rule * Get the validation error message. This is not translated because only the API uses it. * * @return string + * @codeCoverageIgnore */ public function message(): string { diff --git a/app/Rules/IsBoolean.php b/app/Rules/IsBoolean.php index b23314e657..c40e8ce1f8 100644 --- a/app/Rules/IsBoolean.php +++ b/app/Rules/IsBoolean.php @@ -33,7 +33,7 @@ class IsBoolean implements Rule { /** * Get the validation error message. - * + * @codeCoverageIgnore * @return string */ public function message(): string @@ -51,25 +51,16 @@ class IsBoolean implements Rule */ public function passes($attribute, $value): bool { - if (\is_bool($value)) { + if (is_bool($value)) { return true; } - if (\is_int($value) && 0 === $value) { + if (is_int($value) && 0 === $value) { return true; } - if (\is_int($value) && 1 === $value) { + if (is_int($value) && 1 === $value) { return true; } - if (\is_string($value) && '1' === $value) { - return true; - } - if (\is_string($value) && '0' === $value) { - return true; - } - if (\is_string($value) && 'true' === $value) { - return true; - } - if (\is_string($value) && 'false' === $value) { + if (is_string($value) && in_array($value, ['0', '1', 'true', 'false', 'on', 'off', 'yes', 'no', 'y', 'n'])) { return true; } diff --git a/app/Rules/IsDateOrTime.php b/app/Rules/IsDateOrTime.php index a8c76eb604..f942e8206b 100644 --- a/app/Rules/IsDateOrTime.php +++ b/app/Rules/IsDateOrTime.php @@ -26,6 +26,7 @@ namespace FireflyIII\Rules; use Carbon\Carbon; use Carbon\Exceptions\InvalidDateException; +use Exception; use Illuminate\Contracts\Validation\Rule; use Log; @@ -39,6 +40,7 @@ class IsDateOrTime implements Rule * Get the validation error message. * * @return string|array + * @codeCoverageIgnore */ public function message() { @@ -56,11 +58,14 @@ class IsDateOrTime implements Rule public function passes($attribute, $value): bool { $value = (string)$value; - if (10 === \strlen($value)) { + if ('' === $value) { + return false; + } + if (10 === strlen($value)) { // probably a date format. try { Carbon::createFromFormat('Y-m-d', $value); - } catch (InvalidDateException $e) { + } catch (InvalidDateException|Exception $e) { Log::error(sprintf('"%s" is not a valid date: %s', $value, $e->getMessage())); return false; @@ -71,7 +76,7 @@ class IsDateOrTime implements Rule // is an atom string, I hope? try { Carbon::parse($value); - } catch (InvalidDateException $e) { + } catch (InvalidDateException|Exception $e) { Log::error(sprintf('"%s" is not a valid date or time: %s', $value, $e->getMessage())); return false; diff --git a/app/Rules/IsValidAttachmentModel.php b/app/Rules/IsValidAttachmentModel.php index 6034a8c34c..5cfec6935e 100644 --- a/app/Rules/IsValidAttachmentModel.php +++ b/app/Rules/IsValidAttachmentModel.php @@ -46,16 +46,19 @@ class IsValidAttachmentModel implements Rule /** * IsValidAttachmentModel constructor. * + * @codeCoverageIgnore + * * @param string $model */ public function __construct(string $model) { + $model = $this->normalizeModel($model); $this->model = $model; } /** * Get the validation error message. - * + * @codeCoverageIgnore * @return string */ public function message(): string @@ -78,9 +81,9 @@ class IsValidAttachmentModel implements Rule if (!auth()->check()) { return false; } - $model = false === strpos('FireflyIII', $this->model) ? 'FireflyIII\\Models\\' . $this->model : $this->model; - if (Bill::class === $model) { + + if (Bill::class === $this->model) { /** @var BillRepositoryInterface $repository */ $repository = app(BillRepositoryInterface::class); /** @var User $user */ @@ -91,7 +94,7 @@ class IsValidAttachmentModel implements Rule return null !== $bill; } - if (ImportJob::class === $model) { + if (ImportJob::class === $this->model) { /** @var ImportJobRepositoryInterface $repository */ $repository = app(ImportJobRepositoryInterface::class); /** @var User $user */ @@ -102,7 +105,7 @@ class IsValidAttachmentModel implements Rule return null !== $importJob; } - if (Transaction::class === $model) { + if (Transaction::class === $this->model) { /** @var JournalRepositoryInterface $repository */ $repository = app(JournalRepositoryInterface::class); /** @var User $user */ @@ -113,7 +116,7 @@ class IsValidAttachmentModel implements Rule return null !== $transaction; } - if (TransactionJournal::class === $model) { + if (TransactionJournal::class === $this->model) { $repository = app(JournalRepositoryInterface::class); $user = auth()->user(); $repository->setUser($user); @@ -121,8 +124,23 @@ class IsValidAttachmentModel implements Rule return null !== $result; } - Log::error(sprintf('No model was recognized from string "%s"', $model)); + Log::error(sprintf('No model was recognized from string "%s"', $this->model)); return false; } + + /** + * @param string $model + * + * @return string + */ + private function normalizeModel(string $model): string + { + $search = ['FireflyIII\Models\\']; + $replace = ''; + $model = str_replace($search, $replace, $model); + + $model = sprintf('FireflyIII\Models\%s', $model); + return $model; + } } diff --git a/app/Rules/UniqueIban.php b/app/Rules/UniqueIban.php index 28922f3cf2..c3d18d4da2 100644 --- a/app/Rules/UniqueIban.php +++ b/app/Rules/UniqueIban.php @@ -26,7 +26,6 @@ namespace FireflyIII\Rules; use FireflyIII\Models\Account; use FireflyIII\Models\AccountType; use Illuminate\Contracts\Validation\Rule; -use Illuminate\Support\Collection; use Log; /** @@ -43,6 +42,8 @@ class UniqueIban implements Rule /** * Create a new rule instance. * + * @codeCoverageIgnore + * * @param Account|null $account * @param string|null $expectedType */ @@ -55,6 +56,8 @@ class UniqueIban implements Rule /** * Get the validation error message. * + * @codeCoverageIgnore + * * @return string */ public function message(): string @@ -78,13 +81,13 @@ class UniqueIban implements Rule return true; // @codeCoverageIgnore } if (null === $this->expectedType) { - return true; + return true; // @codeCoverageIgnore } $maxCounts = $this->getMaxOccurrences(); foreach ($maxCounts as $type => $max) { $count = $this->countHits($type, $value); - + Log::debug(sprintf('Count for "%s" and IBAN "%s" is %d', $type, $value, $count)); if ($count > $max) { Log::debug( sprintf( @@ -108,24 +111,18 @@ class UniqueIban implements Rule */ private function countHits(string $type, string $iban): int { - $count = 0; - /** @noinspection NullPointerExceptionInspection */ - $query = auth()->user() - ->accounts() - ->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id') - ->where('account_types.type', $type); + $query + = auth()->user() + ->accounts() + ->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id') + ->where('accounts.iban', $iban) + ->where('account_types.type', $type); + if (null !== $this->account) { $query->where('accounts.id', '!=', $this->account->id); } - /** @var Collection $result */ - $result = $query->get(['accounts.*']); - foreach ($result as $account) { - if ($account->iban === $iban) { - $count++; - } - } - return $count; + return $query->count(); } /** diff --git a/app/Rules/ValidTransactions.php b/app/Rules/ValidJournals.php similarity index 64% rename from app/Rules/ValidTransactions.php rename to app/Rules/ValidJournals.php index 29fd7baaef..a9b449b9f1 100644 --- a/app/Rules/ValidTransactions.php +++ b/app/Rules/ValidJournals.php @@ -1,8 +1,8 @@ user()->id; - foreach ($value as $transactionId) { - $count = Transaction::where('transactions.id', $transactionId) - ->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id') - ->where('accounts.user_id', $userId)->count(); + foreach ($value as $journalId) { + $count = TransactionJournal::where('id', $journalId)->where('user_id', $userId)->count(); if (0 === $count) { - Log::debug(sprintf('Count for transaction #%d and user #%d is zero! Return FALSE', $transactionId, $userId)); + Log::debug(sprintf('Count for transaction #%d and user #%d is zero! Return FALSE', $journalId, $userId)); return false; } diff --git a/app/Rules/ValidRecurrenceRepetitionType.php b/app/Rules/ValidRecurrenceRepetitionType.php index 403fc9223a..c984e6b0ee 100644 --- a/app/Rules/ValidRecurrenceRepetitionType.php +++ b/app/Rules/ValidRecurrenceRepetitionType.php @@ -27,6 +27,7 @@ use Illuminate\Contracts\Validation\Rule; /** * Class ValidRecurrenceRepetitionType + * @codeCoverageIgnore */ class ValidRecurrenceRepetitionType implements Rule { @@ -59,7 +60,7 @@ class ValidRecurrenceRepetitionType implements Rule } //monthly,17 //ndom,3,7 - if (\in_array(substr($value, 0, 6), ['yearly', 'weekly'])) { + if (in_array(substr($value, 0, 6), ['yearly', 'weekly'])) { return true; } if (0 === strpos($value, 'monthly')) { diff --git a/app/Rules/ValidRecurrenceRepetitionValue.php b/app/Rules/ValidRecurrenceRepetitionValue.php index da24867046..620e317e7f 100644 --- a/app/Rules/ValidRecurrenceRepetitionValue.php +++ b/app/Rules/ValidRecurrenceRepetitionValue.php @@ -31,6 +31,7 @@ use Log; /** * Class ValidRecurrenceRepetitionValue + * @codeCoverageIgnore */ class ValidRecurrenceRepetitionValue implements Rule { @@ -108,7 +109,7 @@ class ValidRecurrenceRepetitionValue implements Rule private function validateNdom(string $value): bool { $parameters = explode(',', substr($value, 5)); - if (2 !== \count($parameters)) { + if (2 !== count($parameters)) { return false; } $nthDay = (int)($parameters[0] ?? 0.0); diff --git a/app/Rules/ZeroOrMore.php b/app/Rules/ZeroOrMore.php index 642a89cf36..444f5fb195 100644 --- a/app/Rules/ZeroOrMore.php +++ b/app/Rules/ZeroOrMore.php @@ -29,6 +29,7 @@ use Illuminate\Contracts\Validation\Rule; /** * * Class ZeroOrMore + * @codeCoverageIgnore */ class ZeroOrMore implements Rule { diff --git a/app/Services/Bunq/ApiContext.php b/app/Services/Bunq/ApiContext.php index 4e68bef43f..b6bff72041 100644 --- a/app/Services/Bunq/ApiContext.php +++ b/app/Services/Bunq/ApiContext.php @@ -35,6 +35,7 @@ use Tests\Object\FakeApiContext; * Special class to hide away bunq's static initialisation methods. * * Class ApiContext + * @codeCoverageIgnore */ class ApiContext { diff --git a/app/Services/Bunq/MonetaryAccount.php b/app/Services/Bunq/MonetaryAccount.php index be13f57759..9163e70039 100644 --- a/app/Services/Bunq/MonetaryAccount.php +++ b/app/Services/Bunq/MonetaryAccount.php @@ -31,6 +31,7 @@ use FireflyIII\Exceptions\FireflyException; /** * Class MonetaryAccount + * @codeCoverageIgnore */ class MonetaryAccount { diff --git a/app/Services/Bunq/Payment.php b/app/Services/Bunq/Payment.php index 3ce3447b66..b01d57fbd1 100644 --- a/app/Services/Bunq/Payment.php +++ b/app/Services/Bunq/Payment.php @@ -31,6 +31,7 @@ use FireflyIII\Exceptions\FireflyException; /** * Class Payment + * @codeCoverageIgnore */ class Payment { diff --git a/app/Services/Currency/FixerIOv2.php b/app/Services/Currency/FixerIOv2.php index 47bfa4c71a..6ad748efd9 100644 --- a/app/Services/Currency/FixerIOv2.php +++ b/app/Services/Currency/FixerIOv2.php @@ -45,7 +45,7 @@ class FixerIOv2 implements ExchangeRateInterface public function __construct() { if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } @@ -55,6 +55,7 @@ class FixerIOv2 implements ExchangeRateInterface * @param Carbon $date * * @return CurrencyExchangeRate + * @throws Exception */ public function getRate(TransactionCurrency $fromCurrency, TransactionCurrency $toCurrency, Carbon $date): CurrencyExchangeRate { diff --git a/app/Services/Currency/RatesApiIOv1.php b/app/Services/Currency/RatesApiIOv1.php index 2c606ddfec..369b6b1651 100644 --- a/app/Services/Currency/RatesApiIOv1.php +++ b/app/Services/Currency/RatesApiIOv1.php @@ -33,6 +33,7 @@ use Log; /** * Class RatesApiIOv1. + * @codeCoverageIgnore */ class RatesApiIOv1 implements ExchangeRateInterface { @@ -45,7 +46,7 @@ class RatesApiIOv1 implements ExchangeRateInterface public function __construct() { if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } @@ -71,7 +72,7 @@ class RatesApiIOv1 implements ExchangeRateInterface // build URI $uri = sprintf( - 'https://ratesapi.io/api/%s?base=%s&symbols=%s', + 'https://api.ratesapi.io/api/%s?base=%s&symbols=%s', $date->format('Y-m-d'), $fromCurrency->code, $toCurrency->code ); Log::debug(sprintf('Going to request exchange rate using URI %s', $uri)); diff --git a/app/Services/Github/Request/UpdateRequest.php b/app/Services/Github/Request/UpdateRequest.php index d49a982874..6a5fae2116 100644 --- a/app/Services/Github/Request/UpdateRequest.php +++ b/app/Services/Github/Request/UpdateRequest.php @@ -34,6 +34,7 @@ use SimpleXMLElement; /** * Class UpdateRequest + * @codeCoverageIgnore */ class UpdateRequest implements GithubRequest { @@ -67,7 +68,7 @@ class UpdateRequest implements GithubRequest //fetch the products for each category if (isset($releaseXml->entry)) { - Log::debug(sprintf('Count of entries is: %d', \count($releaseXml->entry))); + Log::debug(sprintf('Count of entries is: %d', count($releaseXml->entry))); foreach ($releaseXml->entry as $entry) { $array = [ 'id' => (string)$entry->id, diff --git a/app/Services/IP/IpifyOrg.php b/app/Services/IP/IpifyOrg.php index c1adfc490d..80851c9e5e 100644 --- a/app/Services/IP/IpifyOrg.php +++ b/app/Services/IP/IpifyOrg.php @@ -31,6 +31,7 @@ use RuntimeException; /** * Class IpifyOrg + * @codeCoverageIgnore */ class IpifyOrg implements IPRetrievalInterface { @@ -40,7 +41,7 @@ class IpifyOrg implements IPRetrievalInterface public function __construct() { if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } diff --git a/app/Services/Internal/Destroy/AccountDestroyService.php b/app/Services/Internal/Destroy/AccountDestroyService.php index d0aba6f8f7..00a2609257 100644 --- a/app/Services/Internal/Destroy/AccountDestroyService.php +++ b/app/Services/Internal/Destroy/AccountDestroyService.php @@ -40,11 +40,13 @@ class AccountDestroyService { /** * Constructor. + * + * @codeCoverageIgnore */ public function __construct() { if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } @@ -53,18 +55,38 @@ class AccountDestroyService * @param Account|null $moveTo * * @return void - * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ public function destroy(Account $account, ?Account $moveTo): void { - if (null !== $moveTo) { - DB::table('transactions')->where('account_id', $account->id)->update(['account_id' => $moveTo->id]); - - // also update recurring transactions: - DB::table('recurrences_transactions')->where('source_id', $account->id)->update(['source_id' => $moveTo->id]); - DB::table('recurrences_transactions')->where('destination_id', $account->id)->update(['destination_id' => $moveTo->id]); + $this->moveTransactions($account, $moveTo); + $this->updateRecurrences($account, $moveTo); } + $this->destroyJournals($account); + + // delete recurring transactions with this account: + if (null === $moveTo) { + $this->destroyRecurrences($account); + } + + // delete piggy banks: + PiggyBank::where('account_id', $account->id)->delete(); + + // delete account. + try { + $account->delete(); + } catch (Exception $e) { // @codeCoverageIgnore + Log::error(sprintf('Could not delete account: %s', $e->getMessage())); // @codeCoverageIgnore + } + } + + /** + * @param Account $account + */ + private function destroyJournals(Account $account): void + { + + /** @var JournalDestroyService $service */ $service = app(JournalDestroyService::class); Log::debug('Now trigger account delete response #' . $account->id); @@ -75,37 +97,49 @@ class AccountDestroyService $journal = $transaction->transactionJournal()->first(); if (null !== $journal) { Log::debug('Call for deletion of journal #' . $journal->id); - /** @var JournalDestroyService $service */ - $service->destroy($journal); } } + } - // delete recurring transactions with this account: - if (null === $moveTo) { - $recurrences = RecurrenceTransaction:: - where( - function (Builder $q) use ($account) { - $q->where('source_id', $account->id); - $q->orWhere('destination_id', $account->id); - } - )->get(['recurrence_id'])->pluck('recurrence_id')->toArray(); - - - $destroyService = new RecurrenceDestroyService(); - foreach ($recurrences as $recurrenceId) { - $destroyService->destroyById((int)$recurrenceId); + /** + * @param Account $account + */ + private function destroyRecurrences(Account $account): void + { + $recurrences = RecurrenceTransaction:: + where( + static function (Builder $q) use ($account) { + $q->where('source_id', $account->id); + $q->orWhere('destination_id', $account->id); } - } + )->get(['recurrence_id'])->pluck('recurrence_id')->toArray(); - // delete piggy banks: - PiggyBank::where('account_id', $account->id)->delete(); - - try { - $account->delete(); - } catch (Exception $e) { // @codeCoverageIgnore - Log::error(sprintf('Could not delete account: %s', $e->getMessage())); // @codeCoverageIgnore + /** @var RecurrenceDestroyService $destroyService */ + $destroyService = app(RecurrenceDestroyService::class); + foreach ($recurrences as $recurrenceId) { + $destroyService->destroyById((int)$recurrenceId); } } + + /** + * @param Account $account + * @param Account $moveTo + */ + private function moveTransactions(Account $account, Account $moveTo): void + { + DB::table('transactions')->where('account_id', $account->id)->update(['account_id' => $moveTo->id]); + } + + /** + * @param Account $account + * @param Account $moveTo + */ + private function updateRecurrences(Account $account, Account $moveTo): void + { + DB::table('recurrences_transactions')->where('source_id', $account->id)->update(['source_id' => $moveTo->id]); + DB::table('recurrences_transactions')->where('destination_id', $account->id)->update(['destination_id' => $moveTo->id]); + } + } diff --git a/app/Services/Internal/Destroy/BillDestroyService.php b/app/Services/Internal/Destroy/BillDestroyService.php index 13d170421f..4b256c8f89 100644 --- a/app/Services/Internal/Destroy/BillDestroyService.php +++ b/app/Services/Internal/Destroy/BillDestroyService.php @@ -39,7 +39,7 @@ class BillDestroyService public function __construct() { if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } diff --git a/app/Services/Internal/Destroy/BudgetDestroyService.php b/app/Services/Internal/Destroy/BudgetDestroyService.php index 4f1f1b06cf..0f7d9eb828 100644 --- a/app/Services/Internal/Destroy/BudgetDestroyService.php +++ b/app/Services/Internal/Destroy/BudgetDestroyService.php @@ -30,6 +30,7 @@ use Log; /** * Class BudgetDestroyService + * @codeCoverageIgnore */ class BudgetDestroyService { @@ -39,7 +40,7 @@ class BudgetDestroyService public function __construct() { if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } diff --git a/app/Services/Internal/Destroy/CategoryDestroyService.php b/app/Services/Internal/Destroy/CategoryDestroyService.php index d8d34ab00b..936a1dff78 100644 --- a/app/Services/Internal/Destroy/CategoryDestroyService.php +++ b/app/Services/Internal/Destroy/CategoryDestroyService.php @@ -30,6 +30,7 @@ use Log; /** * Class CategoryDestroyService + * @codeCoverageIgnore */ class CategoryDestroyService { @@ -39,7 +40,7 @@ class CategoryDestroyService public function __construct() { if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } diff --git a/app/Services/Internal/Destroy/CurrencyDestroyService.php b/app/Services/Internal/Destroy/CurrencyDestroyService.php index 3940fcb548..8a6eb317b9 100644 --- a/app/Services/Internal/Destroy/CurrencyDestroyService.php +++ b/app/Services/Internal/Destroy/CurrencyDestroyService.php @@ -29,6 +29,7 @@ use Log; /** * Class CurrencyDestroyService + * @codeCoverageIgnore */ class CurrencyDestroyService { @@ -38,7 +39,7 @@ class CurrencyDestroyService public function __construct() { if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } diff --git a/app/Services/Internal/Destroy/JournalDestroyService.php b/app/Services/Internal/Destroy/JournalDestroyService.php index ee67a9665e..7d70c6b540 100644 --- a/app/Services/Internal/Destroy/JournalDestroyService.php +++ b/app/Services/Internal/Destroy/JournalDestroyService.php @@ -23,9 +23,12 @@ declare(strict_types=1); namespace FireflyIII\Services\Internal\Destroy; +use DB; use Exception; +use FireflyIII\Models\Attachment; use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionJournal; +use FireflyIII\Models\TransactionJournalLink; use FireflyIII\Models\TransactionJournalMeta; use Log; @@ -41,7 +44,7 @@ class JournalDestroyService public function __construct() { if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } @@ -58,12 +61,42 @@ class JournalDestroyService } // also delete journal_meta entries. - /** @var TransactionJournalMeta $meta */ foreach ($journal->transactionJournalMeta()->get() as $meta) { Log::debug(sprintf('Will now delete meta-entry #%d', $meta->id)); $meta->delete(); } + + // also delete attachments. + /** @var Attachment $attachment */ + foreach ($journal->attachments()->get() as $attachment) { + $attachment->delete(); + } + + // delete all from 'budget_transaction_journal' + DB::table('budget_transaction_journal') + ->where('transaction_journal_id', $journal->id)->delete(); + + // delete all from 'category_transaction_journal' + DB::table('category_transaction_journal') + ->where('transaction_journal_id', $journal->id)->delete(); + + // delete all from 'tag_transaction_journal' + DB::table('tag_transaction_journal') + ->where('transaction_journal_id', $journal->id)->delete(); + + // delete all links: + TransactionJournalLink::where('source_id', $journal->id)->delete(); + TransactionJournalLink::where('destination_id', $journal->id)->delete(); + + // delete all notes + $journal->notes()->delete(); + + // update events + $journal->piggyBankEvents()->update(['transaction_journal_id' => null]); + + + $journal->delete(); } catch (Exception $e) { Log::error(sprintf('Could not delete bill: %s', $e->getMessage())); // @codeCoverageIgnore diff --git a/app/Services/Internal/Destroy/RecurrenceDestroyService.php b/app/Services/Internal/Destroy/RecurrenceDestroyService.php index 68da18cc6f..ebd7fd0a6c 100644 --- a/app/Services/Internal/Destroy/RecurrenceDestroyService.php +++ b/app/Services/Internal/Destroy/RecurrenceDestroyService.php @@ -40,7 +40,7 @@ class RecurrenceDestroyService public function __construct() { if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } diff --git a/app/Services/Internal/Destroy/TransactionGroupDestroyService.php b/app/Services/Internal/Destroy/TransactionGroupDestroyService.php new file mode 100644 index 0000000000..e45ef99259 --- /dev/null +++ b/app/Services/Internal/Destroy/TransactionGroupDestroyService.php @@ -0,0 +1,53 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Services\Internal\Destroy; + +use Exception; +use FireflyIII\Models\TransactionGroup; + +/** + * Class TransactionGroupDestroyService + * @codeCoverageIgnore + */ +class TransactionGroupDestroyService +{ + + /** + * @param TransactionGroup $transactionGroup + */ + public function destroy(TransactionGroup $transactionGroup): void + { + /** @var JournalDestroyService $service */ + $service = app(JournalDestroyService::class); + foreach ($transactionGroup->transactionJournals as $journal) { + $service->destroy($journal); + } + try { + $transactionGroup->delete(); + } catch (Exception $e) { + app('log')->error(sprintf('Could not delete transaction group: %s', $e->getMessage())); // @codeCoverageIgnore + } + } + +} \ No newline at end of file diff --git a/app/Services/Internal/File/EncryptService.php b/app/Services/Internal/File/EncryptService.php deleted file mode 100644 index b72398571e..0000000000 --- a/app/Services/Internal/File/EncryptService.php +++ /dev/null @@ -1,71 +0,0 @@ -. - */ - -declare(strict_types=1); - -namespace FireflyIII\Services\Internal\File; - -use Crypt; -use FireflyIII\Exceptions\FireflyException; -use Illuminate\Contracts\Encryption\EncryptException; -use Illuminate\Support\Facades\Storage; -use Log; - -/** - * Class EncryptService - */ -class EncryptService -{ - /** - * Constructor. - */ - public function __construct() - { - if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); - } - } - - /** - * @param string $file - * @param string $key - * - * @throws FireflyException - */ - public function encrypt(string $file, string $key): void - { - if (!file_exists($file)) { - throw new FireflyException(sprintf('File "%s" does not seem to exist.', $file)); - } - $content = file_get_contents($file); - try { - $content = Crypt::encrypt($content); - } catch (EncryptException $e) { - $message = sprintf('Could not encrypt file: %s', $e->getMessage()); - Log::error($message); - throw new FireflyException($message); - } - $newName = sprintf('%s.upload', $key); - $disk = Storage::disk('upload'); - $disk->put($newName, $content); - } - -} diff --git a/app/Services/Internal/Support/AccountServiceTrait.php b/app/Services/Internal/Support/AccountServiceTrait.php index 9f0db9bc94..2318b84f7e 100644 --- a/app/Services/Internal/Support/AccountServiceTrait.php +++ b/app/Services/Internal/Support/AccountServiceTrait.php @@ -24,18 +24,19 @@ declare(strict_types=1); namespace FireflyIII\Services\Internal\Support; use Exception; -use FireflyIII\Factory\AccountFactory; +use FireflyIII\Exceptions\FireflyException; use FireflyIII\Factory\AccountMetaFactory; -use FireflyIII\Factory\TransactionFactory; -use FireflyIII\Factory\TransactionJournalFactory; +use FireflyIII\Factory\TransactionCurrencyFactory; +use FireflyIII\Factory\TransactionGroupFactory; use FireflyIII\Models\Account; use FireflyIII\Models\AccountType; use FireflyIII\Models\Note; use FireflyIII\Models\Transaction; +use FireflyIII\Models\TransactionCurrency; +use FireflyIII\Models\TransactionGroup; use FireflyIII\Models\TransactionJournal; -use FireflyIII\Models\TransactionType; -use FireflyIII\Services\Internal\Destroy\JournalDestroyService; -use FireflyIII\User; +use FireflyIII\Repositories\Account\AccountRepositoryInterface; +use FireflyIII\Services\Internal\Destroy\TransactionGroupDestroyService; use Log; use Validator; @@ -45,35 +46,15 @@ use Validator; */ trait AccountServiceTrait { + /** @var AccountRepositoryInterface */ + protected $accountRepository; + /** @var array */ - public $validAssetFields = ['accountRole', 'accountNumber', 'currency_id', 'BIC', 'include_net_worth']; + protected $validAssetFields = ['account_role', 'account_number', 'currency_id', 'BIC', 'include_net_worth']; /** @var array */ - public $validCCFields = ['accountRole', 'ccMonthlyPaymentDate', 'ccType', 'accountNumber', 'currency_id', 'BIC', 'include_net_worth']; + protected $validCCFields = ['account_role', 'cc_monthly_payment_date', 'cc_type', 'account_number', 'currency_id', 'BIC', 'include_net_worth']; /** @var array */ - public $validFields = ['accountNumber', 'currency_id', 'BIC', 'interest', 'interest_period', 'include_net_worth']; - - /** - * @param Account $account - * - * @return bool - */ - public function deleteIB(Account $account): bool - { - Log::debug(sprintf('deleteIB() for account #%d', $account->id)); - $openingBalance = $this->getIBJournal($account); - - // opening balance data? update it! - if (null !== $openingBalance) { - Log::debug('Opening balance journal found, delete journal.'); - /** @var JournalDestroyService $service */ - $service = app(JournalDestroyService::class); - $service->destroy($openingBalance); - - return true; - } - - return true; - } + protected $validFields = ['account_number', 'currency_id', 'BIC', 'interest', 'interest_period', 'include_net_worth']; /** * @param null|string $iban @@ -98,217 +79,13 @@ trait AccountServiceTrait return $iban; } - /** - * Find existing opening balance. - * - * @param Account $account - * - * @return TransactionJournal|null - */ - public function getIBJournal(Account $account): ?TransactionJournal - { - $journal = TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') - ->where('transactions.account_id', $account->id) - ->transactionTypes([TransactionType::OPENING_BALANCE]) - ->first(['transaction_journals.*']); - if (null === $journal) { - Log::debug('Could not find a opening balance journal, return NULL.'); - - return null; - } - Log::debug(sprintf('Found opening balance: journal #%d.', $journal->id)); - - return $journal; - } - - /** - * @param Account $account - * @param array $data - * - * @return TransactionJournal|null - * @throws \FireflyIII\Exceptions\FireflyException - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - */ - public function storeIBJournal(Account $account, array $data): ?TransactionJournal - { - $amount = (string)$data['openingBalance']; - Log::debug(sprintf('Submitted amount is %s', $amount)); - - if (0 === bccomp($amount, '0')) { - return null; - } - - // store journal, without transactions: - $name = $data['name']; - $currencyId = $data['currency_id']; - $journalData = [ - 'type' => TransactionType::OPENING_BALANCE, - 'user' => $account->user->id, - 'transaction_currency_id' => $currencyId, - 'description' => (string)trans('firefly.initial_balance_description', ['account' => $account->name]), - 'completed' => true, - 'date' => $data['openingBalanceDate'], - 'bill_id' => null, - 'bill_name' => null, - 'piggy_bank_id' => null, - 'piggy_bank_name' => null, - 'tags' => null, - 'notes' => null, - 'transactions' => [], - - ]; - /** @var TransactionJournalFactory $factory */ - $factory = app(TransactionJournalFactory::class); - $factory->setUser($account->user); - $journal = $factory->create($journalData); - $opposing = $this->storeOpposingAccount($account->user, $name); - Log::notice(sprintf('Created new opening balance journal: #%d', $journal->id)); - - $firstAccount = $account; - $secondAccount = $opposing; - $firstAmount = $amount; - $secondAmount = bcmul($amount, '-1'); - Log::notice(sprintf('First amount is %s, second amount is %s', $firstAmount, $secondAmount)); - - if (bccomp($amount, '0') === -1) { - Log::debug(sprintf('%s is a negative number.', $amount)); - $firstAccount = $opposing; - $secondAccount = $account; - $firstAmount = bcmul($amount, '-1'); - $secondAmount = $amount; - Log::notice(sprintf('First amount is %s, second amount is %s', $firstAmount, $secondAmount)); - } - /** @var TransactionFactory $factory */ - $factory = app(TransactionFactory::class); - $factory->setUser($account->user); - $factory->create( - [ - 'account' => $firstAccount, - 'transaction_journal' => $journal, - 'amount' => $firstAmount, - 'currency_id' => $currencyId, - 'description' => null, - 'identifier' => 0, - 'foreign_amount' => null, - 'reconciled' => false, - ] - ); - $factory->create( - [ - 'account' => $secondAccount, - 'transaction_journal' => $journal, - 'amount' => $secondAmount, - 'currency_id' => $currencyId, - 'description' => null, - 'identifier' => 0, - 'foreign_amount' => null, - 'reconciled' => false, - ] - ); - - return $journal; - } - - /** - * @param User $user - * @param string $name - * - * @return Account - * @throws \FireflyIII\Exceptions\FireflyException - */ - public function storeOpposingAccount(User $user, string $name): Account - { - $opposingAccountName = (string)trans('firefly.initial_balance_account', ['name' => $name]); - Log::debug('Going to create an opening balance opposing account.'); - /** @var AccountFactory $factory */ - $factory = app(AccountFactory::class); - $factory->setUser($user); - - return $factory->findOrCreate($opposingAccountName, AccountType::INITIAL_BALANCE); - } - - /** - * @param Account $account - * @param array $data - * - * @return bool - * @throws \FireflyIII\Exceptions\FireflyException - */ - public function updateIB(Account $account, array $data): bool - { - Log::debug(sprintf('updateInitialBalance() for account #%d', $account->id)); - $openingBalance = $this->getIBJournal($account); - - // no opening balance journal? create it: - if (null === $openingBalance) { - Log::debug('No opening balance journal yet, create journal.'); - $this->storeIBJournal($account, $data); - - return true; - } - - // opening balance data? update it! - if (null !== $openingBalance->id) { - Log::debug('Opening balance journal found, update journal.'); - $this->updateIBJournal($account, $openingBalance, $data); - - return true; - } - - return true; // @codeCoverageIgnore - } - - /** - * @param Account $account - * @param TransactionJournal $journal - * @param array $data - * - * @return bool - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - */ - public function updateIBJournal(Account $account, TransactionJournal $journal, array $data): bool - { - $date = $data['openingBalanceDate']; - $amount = (string)$data['openingBalance']; - $negativeAmount = bcmul($amount, '-1'); - $currencyId = (int)$data['currency_id']; - Log::debug(sprintf('Submitted amount for opening balance to update is "%s"', $amount)); - if (0 === bccomp($amount, '0')) { - Log::notice(sprintf('Amount "%s" is zero, delete opening balance.', $amount)); - /** @var JournalDestroyService $service */ - $service = app(JournalDestroyService::class); - $service->destroy($journal); - - return true; - } - $journal->date = $date; - $journal->transaction_currency_id = $currencyId; - $journal->save(); - /** @var Transaction $transaction */ - foreach ($journal->transactions()->get() as $transaction) { - if ((int)$account->id === (int)$transaction->account_id) { - Log::debug(sprintf('Will (eq) change transaction #%d amount from "%s" to "%s"', $transaction->id, $transaction->amount, $amount)); - $transaction->amount = $amount; - $transaction->transaction_currency_id = $currencyId; - $transaction->save(); - } - if (!((int)$account->id === (int)$transaction->account_id)) { - Log::debug(sprintf('Will (neq) change transaction #%d amount from "%s" to "%s"', $transaction->id, $transaction->amount, $negativeAmount)); - $transaction->amount = $negativeAmount; - $transaction->transaction_currency_id = $currencyId; - $transaction->save(); - } - } - Log::debug('Updated opening balance journal.'); - - return true; - } - /** * Update meta data for account. Depends on type which fields are valid. * + * TODO this method treats expense accounts and liabilities the same way (tries to save interest) + * * @param Account $account - * @param array $data + * @param array $data * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ public function updateMetaData(Account $account, array $data): void @@ -318,7 +95,7 @@ trait AccountServiceTrait if ($account->accountType->type === AccountType::ASSET) { $fields = $this->validAssetFields; } - if ($account->accountType->type === AccountType::ASSET && 'ccAsset' === $data['accountRole']) { + if ($account->accountType->type === AccountType::ASSET && 'ccAsset' === $data['account_role']) { $fields = $this->validCCFields; } /** @var AccountMetaFactory $factory */ @@ -330,8 +107,9 @@ trait AccountServiceTrait /** * @param Account $account - * @param string $note + * @param string $note * + * @codeCoverageIgnore * @return bool */ public function updateNote(Account $account, string $note): bool @@ -366,10 +144,10 @@ trait AccountServiceTrait * * @return bool */ - public function validIBData(array $data): bool + public function validOBData(array $data): bool { - $data['openingBalance'] = (string)($data['openingBalance'] ?? ''); - if ('' !== $data['openingBalance'] && isset($data['openingBalance'], $data['openingBalanceDate'])) { + $data['opening_balance'] = (string)($data['opening_balance'] ?? ''); + if ('' !== $data['opening_balance'] && isset($data['opening_balance'], $data['opening_balance_date'])) { Log::debug('Array has valid opening balance data.'); return true; @@ -378,4 +156,186 @@ trait AccountServiceTrait return false; } + + /** + * @param int $currencyId + * @param string $currencyCode + * @return TransactionCurrency + */ + protected function getCurrency(int $currencyId, string $currencyCode): TransactionCurrency + { + // find currency, or use default currency instead. + /** @var TransactionCurrencyFactory $factory */ + $factory = app(TransactionCurrencyFactory::class); + /** @var TransactionCurrency $currency */ + $currency = $factory->find($currencyId, $currencyCode); + + if (null === $currency) { + // use default currency: + $currency = app('amount')->getDefaultCurrencyByUser($this->user); + } + $currency->enabled = true; + $currency->save(); + + return $currency; + } + + /** + * Delete TransactionGroup with opening balance in it. + * + * @param Account $account + */ + protected function deleteOBGroup(Account $account): void + { + Log::debug(sprintf('deleteOB() for account #%d', $account->id)); + $openingBalanceGroup = $this->getOBGroup($account); + + // opening balance data? update it! + if (null !== $openingBalanceGroup) { + Log::debug('Opening balance journal found, delete journal.'); + /** @var TransactionGroupDestroyService $service */ + $service = app(TransactionGroupDestroyService::class); + $service->destroy($openingBalanceGroup); + } + } + + /** + * @param Account $account + * @param array $data + * @return TransactionGroup|null + */ + protected function createOBGroup(Account $account, array $data): ?TransactionGroup + { + Log::debug('Now going to create an OB group.'); + $language = app('preferences')->getForUser($account->user, 'language', 'en_US')->data; + $sourceId = null; + $sourceName = null; + $destId = null; + $destName = null; + $amount = $data['opening_balance']; + if (1 === bccomp($amount, '0')) { + Log::debug(sprintf('Amount is %s, which is positive. Source is a new IB account, destination is #%d', $amount, $account->id)); + // amount is positive. + $sourceName = trans('firefly.initial_balance_description', ['account' => $account->name], $language); + $destId = $account->id; + } + if (-1 === bccomp($amount, '0')) { + Log::debug(sprintf('Amount is %s, which is negative. Destination is a new IB account, source is #%d', $amount, $account->id)); + // amount is not positive + $destName = trans('firefly.initial_balance_account', ['account' => $account->name], $language); + $sourceId = $account->id; + } + $amount = app('steam')->positive($amount); + $submission = [ + 'group_title' => null, + 'user' => $account->user_id, + 'transactions' => [ + [ + 'type' => 'Opening balance', + 'date' => $data['opening_balance_date'], + 'source_id' => $sourceId, + 'source_name' => $sourceName, + 'destination_id' => $destId, + 'destination_name' => $destName, + 'user' => $account->user_id, + 'order' => 0, + 'amount' => $amount, + 'foreign_amount' => null, + 'description' => trans('firefly.initial_balance_description', ['account' => $account->name]), + 'budget_id' => null, + 'budget_name' => null, + 'category_id' => null, + 'category_name' => null, + 'piggy_bank_id' => null, + 'piggy_bank_name' => null, + 'reconciled' => false, + 'notes' => null, + 'tags' => [], + ], + ], + ]; + Log::debug('Going for submission', $submission); + $group = null; + /** @var TransactionGroupFactory $factory */ + $factory = app(TransactionGroupFactory::class); + $factory->setUser($account->user); + + try { + $group = $factory->create($submission); + // @codeCoverageIgnoreStart + } catch (FireflyException $e) { + Log::error($e->getMessage()); + Log::error($e->getTraceAsString()); + } + + // @codeCoverageIgnoreEnd + + return $group; + } + + /** + * Update or create the opening balance group. Assumes valid data in $data. + * + * Returns null if this fails. + * + * @param Account $account + * @param array $data + * + * @return TransactionGroup|null + * @codeCoverageIgnore + */ + protected function updateOBGroup(Account $account, array $data): ?TransactionGroup + { + $obGroup = $this->getOBGroup($account); + if (null === $obGroup) { + return $this->createOBGroup($account, $data); + } + /** @var TransactionJournal $journal */ + $journal = $obGroup->transactionJournals()->first(); + $journal->date = $data['opening_balance_date'] ?? $journal->date; + $journal->transaction_currency_id = $data['currency_id']; + + /** @var Transaction $obTransaction */ + $obTransaction = $journal->transactions()->where('account_id', '!=', $account->id)->first(); + /** @var Transaction $accountTransaction */ + $accountTransaction = $journal->transactions()->where('account_id', $account->id)->first(); + + // if amount is negative: + if (1 === bccomp('0', $data['opening_balance'])) { + // account transaction loses money: + $accountTransaction->amount = app('steam')->negative($data['opening_balance']); + $accountTransaction->transaction_currency_id = $data['currency_id']; + + // OB account transaction gains money + $obTransaction->amount = app('steam')->positive($data['opening_balance']); + $obTransaction->transaction_currency_id = $data['currency_id']; + } + if (-1 === bccomp('0', $data['opening_balance'])) { + // account gains money: + $accountTransaction->amount = app('steam')->positive($data['opening_balance']); + $accountTransaction->transaction_currency_id = $data['currency_id']; + + // OB account loses money: + $obTransaction->amount = app('steam')->negative($data['opening_balance']); + $obTransaction->transaction_currency_id = $data['currency_id']; + } + // save both + $accountTransaction->save(); + $obTransaction->save(); + $journal->save(); + $obGroup->refresh(); + + return $obGroup; + } + + /** + * Returns the opening balance group, or NULL if it does not exist. + * + * @param Account $account + * @return TransactionGroup|null + */ + protected function getOBGroup(Account $account): ?TransactionGroup + { + return $this->accountRepository->getOpeningBalanceGroup($account); + } } diff --git a/app/Services/Internal/Support/BillServiceTrait.php b/app/Services/Internal/Support/BillServiceTrait.php index a8e6b2f10f..eb1f9dc992 100644 --- a/app/Services/Internal/Support/BillServiceTrait.php +++ b/app/Services/Internal/Support/BillServiceTrait.php @@ -32,7 +32,7 @@ use Log; /** * Trait BillServiceTrait - * + * @codeCoverageIgnore */ trait BillServiceTrait { @@ -66,7 +66,6 @@ trait BillServiceTrait * @param string $note * * @return bool - * @throws \Exception */ public function updateNote(Bill $bill, string $note): bool { diff --git a/app/Services/Internal/Support/JournalServiceTrait.php b/app/Services/Internal/Support/JournalServiceTrait.php index 01cfbf4cb9..a4838d9a15 100644 --- a/app/Services/Internal/Support/JournalServiceTrait.php +++ b/app/Services/Internal/Support/JournalServiceTrait.php @@ -24,11 +24,17 @@ declare(strict_types=1); namespace FireflyIII\Services\Internal\Support; use Exception; -use FireflyIII\Factory\BillFactory; +use FireflyIII\Exceptions\FireflyException; use FireflyIII\Factory\TagFactory; -use FireflyIII\Factory\TransactionJournalMetaFactory; +use FireflyIII\Models\Account; +use FireflyIII\Models\AccountType; use FireflyIII\Models\Note; use FireflyIII\Models\TransactionJournal; +use FireflyIII\Models\TransactionType; +use FireflyIII\Repositories\Account\AccountRepositoryInterface; +use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; +use FireflyIII\Repositories\Category\CategoryRepositoryInterface; +use FireflyIII\Support\NullArrayObject; use Log; /** @@ -37,26 +43,242 @@ use Log; */ trait JournalServiceTrait { + /** @var AccountRepositoryInterface */ + private $accountRepository; + /** @var BudgetRepositoryInterface */ + private $budgetRepository; + /** @var CategoryRepositoryInterface */ + private $categoryRepository; + /** @var TagFactory */ + private $tagFactory; + + + /** + * @param string|null $amount + * + * @return string + * @codeCoverageIgnore + */ + protected function getForeignAmount(?string $amount): ?string + { + if (null === $amount) { + Log::debug('No foreign amount info in array. Return NULL'); + + return null; + } + if ('' === $amount) { + Log::debug('Foreign amount is empty string, return NULL.'); + + return null; + } + if (0 === bccomp('0', $amount)) { + Log::debug('Foreign amount is 0.0, return NULL.'); + + return null; + } + Log::debug(sprintf('Foreign amount is %s', $amount)); + + return $amount; + } + + /** + * @param string $transactionType + * @param string $direction + * @param int|null $accountId + * @param string|null $accountName + * + * @return Account + * @codeCoverageIgnore + */ + protected function getAccount(string $transactionType, string $direction, ?int $accountId, ?string $accountName): Account + { + // some debug logging: + Log::debug(sprintf('Now in getAccount(%s, %d, %s)', $direction, $accountId, $accountName)); + + // final result: + $result = null; + + // expected type of source account, in order of preference + /** @var array $array */ + $array = config('firefly.expected_source_types'); + $expectedTypes = $array[$direction]; + unset($array); + + // and now try to find it, based on the type of transaction. + $message = 'Based on the fact that the transaction is a %s, the %s account should be in: %s'; + Log::debug(sprintf($message, $transactionType, $direction, implode(', ', $expectedTypes[$transactionType]))); + + // first attempt, find by ID. + if (null !== $accountId) { + $search = $this->accountRepository->findNull($accountId); + if (null !== $search && in_array($search->accountType->type, $expectedTypes[$transactionType], true)) { + Log::debug( + sprintf('Found "account_id" object for %s: #%d, "%s" of type %s', $direction, $search->id, $search->name, $search->accountType->type) + ); + $result = $search; + } + } + + // second attempt, find by name. + if (null === $result && null !== $accountName) { + Log::debug('Found nothing by account ID.'); + // find by preferred type. + $source = $this->accountRepository->findByName($accountName, [$expectedTypes[$transactionType][0]]); + // or any expected type. + $source = $source ?? $this->accountRepository->findByName($accountName, $expectedTypes[$transactionType]); + + if (null !== $source) { + Log::debug(sprintf('Found "account_name" object for %s: #%d, %s', $direction, $source->id, $source->name)); + + $result = $source; + } + } + + // return cash account. + if (null === $result && null === $accountName + && in_array(AccountType::CASH, $expectedTypes[$transactionType], true)) { + $result = $this->accountRepository->getCashAccount(); + } + + // return new account. + if (null === $result) { + $accountName = $accountName ?? '(no name)'; + // final attempt, create it. + $preferredType = $expectedTypes[$transactionType][0]; + if (AccountType::ASSET === $preferredType) { + throw new FireflyException(sprintf('TransactionFactory: Cannot create asset account with ID #%d or name "%s".', $accountId, $accountName)); + } + + $result = $this->accountRepository->store( + [ + 'account_type_id' => null, + 'account_type' => $preferredType, + 'name' => $accountName, + 'active' => true, + 'iban' => null, + ] + ); + } + + return $result; + } + + /** + * @param string $amount + * + * @return string + * @throws FireflyException + * @codeCoverageIgnore + */ + protected function getAmount(string $amount): string + { + if ('' === $amount) { + throw new FireflyException(sprintf('The amount cannot be an empty string: "%s"', $amount)); + } + if (0 === bccomp('0', $amount)) { + throw new FireflyException(sprintf('The amount seems to be zero: "%s"', $amount)); + } + + return $amount; + } + + /** + * @param TransactionJournal $journal + * @param NullArrayObject $data + * + * @codeCoverageIgnore + */ + protected function storeBudget(TransactionJournal $journal, NullArrayObject $data): void + { + if (TransactionType::WITHDRAWAL !== $journal->transactionType->type) { + $journal->budgets()->sync([]); + + return; + } + $budget = $this->budgetRepository->findBudget($data['budget_id'], $data['budget_name']); + if (null !== $budget) { + Log::debug(sprintf('Link budget #%d to journal #%d', $budget->id, $journal->id)); + $journal->budgets()->sync([$budget->id]); + + return; + } + // if the budget is NULL, sync empty. + $journal->budgets()->sync([]); + } + + /** + * @param TransactionJournal $journal + * @param NullArrayObject $data + * + * @codeCoverageIgnore + */ + protected function storeCategory(TransactionJournal $journal, NullArrayObject $data): void + { + $category = $this->categoryRepository->findCategory($data['category_id'], $data['category_name']); + if (null !== $category) { + Log::debug(sprintf('Link category #%d to journal #%d', $category->id, $journal->id)); + $journal->categories()->sync([$category->id]); + + return; + } + // if the category is NULL, sync empty. + $journal->categories()->sync([]); + } + + /** + * @param TransactionJournal $journal + * @param string $notes + * + * @codeCoverageIgnore + */ + protected function storeNotes(TransactionJournal $journal, ?string $notes): void + { + $notes = (string)$notes; + $note = $journal->notes()->first(); + if ('' !== $notes) { + if (null === $note) { + $note = new Note; + $note->noteable()->associate($journal); + } + $note->text = $notes; + $note->save(); + Log::debug(sprintf('Stored notes for journal #%d', $journal->id)); + + return; + } + if ('' === $notes && null !== $note) { + // try to delete existing notes. + try { + $note->delete(); + // @codeCoverageIgnoreStart + } catch (Exception $e) { + Log::debug(sprintf('Could not delete journal notes: %s', $e->getMessage())); + } + // @codeCoverageIgnoreEnd + } + } /** * Link tags to journal. * * @param TransactionJournal $journal - * @param array $data + * @param array $tags * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * + * @codeCoverageIgnore */ - public function connectTags(TransactionJournal $journal, array $data): void + protected function storeTags(TransactionJournal $journal, ?array $tags): void { - /** @var TagFactory $factory */ - $factory = app(TagFactory::class); - $factory->setUser($journal->user); + + $this->tagFactory->setUser($journal->user); $set = []; - if (!\is_array($data['tags'])) { - return; // @codeCoverageIgnore + if (!is_array($tags)) { + return; } - foreach ($data['tags'] as $string) { - if ('' != $string) { - $tag = $factory->findOrCreate($string); + foreach ($tags as $string) { + $string = (string)$string; + if ('' !== $string) { + $tag = $this->tagFactory->findOrCreate($string); if (null !== $tag) { $set[] = $tag->id; } @@ -65,77 +287,5 @@ trait JournalServiceTrait $journal->tags()->sync($set); } - /** - * Connect bill if present. - * - * @param TransactionJournal $journal - * @param array $data - */ - protected function connectBill(TransactionJournal $journal, array $data): void - { - /** @var BillFactory $factory */ - $factory = app(BillFactory::class); - $factory->setUser($journal->user); - $bill = $factory->find((int)$data['bill_id'], $data['bill_name']); - if (null !== $bill) { - $journal->bill_id = $bill->id; - $journal->save(); - - return; - } - $journal->bill_id = null; - $journal->save(); - - } - - /** - * @param TransactionJournal $journal - * @param array $data - * @param string $field - */ - protected function storeMeta(TransactionJournal $journal, array $data, string $field): void - { - $set = [ - 'journal' => $journal, - 'name' => $field, - 'data' => (string)($data[$field] ?? ''), - ]; - - Log::debug(sprintf('Going to store meta-field "%s", with value "%s".', $set['name'], $set['data'])); - - /** @var TransactionJournalMetaFactory $factory */ - $factory = app(TransactionJournalMetaFactory::class); - $factory->updateOrCreate($set); - } - - /** - * @param TransactionJournal $journal - * @param string $notes - */ - protected function storeNote(TransactionJournal $journal, ?string $notes): void - { - $notes = (string)$notes; - if ('' !== $notes) { - $note = $journal->notes()->first(); - if (null === $note) { - $note = new Note; - $note->noteable()->associate($journal); - } - $note->text = $notes; - $note->save(); - - return; - } - $note = $journal->notes()->first(); - if (null !== $note) { - try { - $note->delete(); - } catch (Exception $e) { - Log::debug(sprintf('Journal service trait could not delete note: %s', $e->getMessage())); - } - } - - - } } diff --git a/app/Services/Internal/Support/RecurringTransactionTrait.php b/app/Services/Internal/Support/RecurringTransactionTrait.php index 894636e5bd..a4225c4e51 100644 --- a/app/Services/Internal/Support/RecurringTransactionTrait.php +++ b/app/Services/Internal/Support/RecurringTransactionTrait.php @@ -24,6 +24,8 @@ declare(strict_types=1); namespace FireflyIII\Services\Internal\Support; use Exception; +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Factory\AccountFactory; use FireflyIII\Factory\BudgetFactory; use FireflyIII\Factory\CategoryFactory; use FireflyIII\Factory\PiggyBankFactory; @@ -35,7 +37,8 @@ use FireflyIII\Models\RecurrenceMeta; use FireflyIII\Models\RecurrenceRepetition; use FireflyIII\Models\RecurrenceTransaction; use FireflyIII\Models\RecurrenceTransactionMeta; -use FireflyIII\Models\TransactionType; +use FireflyIII\Repositories\Account\AccountRepositoryInterface; +use FireflyIII\Validation\AccountValidator; use Log; @@ -47,9 +50,9 @@ trait RecurringTransactionTrait { /** * @param Recurrence $recurrence - * @param array $repetitions + * @param array $repetitions */ - public function createRepetitions(Recurrence $recurrence, array $repetitions): void + protected function createRepetitions(Recurrence $recurrence, array $repetitions): void { /** @var array $array */ foreach ($repetitions as $array) { @@ -57,7 +60,7 @@ trait RecurringTransactionTrait [ 'recurrence_id' => $recurrence->id, 'repetition_type' => $array['type'], - 'repetition_moment' => $array['moment'], + 'repetition_moment' => $array['moment'] ?? '', 'repetition_skip' => $array['skip'], 'weekend' => $array['weekend'] ?? 1, ] @@ -66,33 +69,72 @@ trait RecurringTransactionTrait } } + /** + * @param array $expectedTypes + * @param Account|null $account + * @param int|null $accountId + * @param string|null $accountName + * + * @return Account + */ + protected function findAccount(array $expectedTypes, ?int $accountId, ?string $accountName): Account + { + $result = null; + $accountId = (int)$accountId; + $accountName = (string)$accountName; + /** @var AccountRepositoryInterface $repository */ + $repository = app(AccountRepositoryInterface::class); + $repository->setUser($this->user); + + // if user has submitted an account ID, search for it. + $result = $repository->findNull((int)$accountId); + if (null !== $result) { + return $result; + } + + // if user has submitted a name, search for it: + $result = $repository->findByName($accountName, $expectedTypes); + if (null !== $result) { + return $result; + } + + // maybe we can create it? Try to avoid LOAN and other asset types. + $cannotCreate = [AccountType::ASSET, AccountType::DEBT, AccountType::LOAN, AccountType::MORTGAGE, AccountType::CREDITCARD]; + /** @var AccountFactory $factory */ + $factory = app(AccountFactory::class); + $factory->setUser($this->user); + foreach ($expectedTypes as $expectedType) { + if (in_array($expectedType, $cannotCreate, true)) { + continue; + } + if (!in_array($expectedType, $cannotCreate, true)) { + try { + $result = $factory->findOrCreate($accountName, $expectedType); + // @codeCoverageIgnoreStart + } catch (FireflyException $e) { + Log::error($e->getMessage()); + } + // @codeCoverageIgnoreEnd + } + } + + return $result ?? $repository->getCashAccount(); + } + /** * Store transactions of a recurring transactions. It's complex but readable. * * @param Recurrence $recurrence - * @param array $transactions - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + * @param array $transactions + * @throws FireflyException */ - public function createTransactions(Recurrence $recurrence, array $transactions): void + protected function createTransactions(Recurrence $recurrence, array $transactions): void { foreach ($transactions as $array) { - $source = null; - $destination = null; - switch ($recurrence->transactionType->type) { - case TransactionType::WITHDRAWAL: - $source = $this->findAccount(AccountType::ASSET, $array['source_id'], $array['source_name']); - $destination = $this->findAccount(AccountType::EXPENSE, $array['destination_id'], $array['destination_name']); - break; - case TransactionType::DEPOSIT: - $source = $this->findAccount(AccountType::REVENUE, $array['source_id'], $array['source_name']); - $destination = $this->findAccount(AccountType::ASSET, $array['destination_id'], $array['destination_name']); - break; - case TransactionType::TRANSFER: - $source = $this->findAccount(AccountType::ASSET, $array['source_id'], $array['source_name']); - $destination = $this->findAccount(AccountType::ASSET, $array['destination_id'], $array['destination_name']); - break; - } + $sourceTypes = config(sprintf('firefly.expected_source_types.source.%s', $recurrence->transactionType->type)); + $destTypes = config(sprintf('firefly.expected_source_types.destination.%s', $recurrence->transactionType->type)); + $source = $this->findAccount($sourceTypes, $array['source_id'], $array['source_name']); + $destination = $this->findAccount($destTypes, $array['destination_id'], $array['destination_name']); /** @var TransactionCurrencyFactory $factory */ $factory = app(TransactionCurrencyFactory::class); @@ -101,6 +143,20 @@ trait RecurringTransactionTrait if (null === $currency) { $currency = app('amount')->getDefaultCurrencyByUser($recurrence->user); } + + // once the accounts have been determined, we still verify their validity: + /** @var AccountValidator $validator */ + $validator = app(AccountValidator::class); + $validator->setUser($recurrence->user); + $validator->setTransactionType($recurrence->transactionType->type); + if (!$validator->validateSource($source->id, null)) { + throw new FireflyException(sprintf('Source invalid: %s', $validator->sourceError)); // @codeCoverageIgnore + } + + if (!$validator->validateDestination($destination->id, null)) { + throw new FireflyException(sprintf('Destination invalid: %s', $validator->destError)); // @codeCoverageIgnore + } + $transaction = new RecurrenceTransaction( [ 'recurrence_id' => $recurrence->id, @@ -149,16 +205,20 @@ trait RecurringTransactionTrait /** * @param Recurrence $recurrence + * + * @codeCoverageIgnore */ - public function deleteRepetitions(Recurrence $recurrence): void + protected function deleteRepetitions(Recurrence $recurrence): void { $recurrence->recurrenceRepetitions()->delete(); } /** * @param Recurrence $recurrence + * + * @codeCoverageIgnore */ - public function deleteTransactions(Recurrence $recurrence): void + protected function deleteTransactions(Recurrence $recurrence): void { /** @var RecurrenceTransaction $transaction */ foreach ($recurrence->recurrenceTransactions as $transaction) { @@ -171,22 +231,13 @@ trait RecurringTransactionTrait } } - /** - * @param null|string $expectedType - * @param int|null $accountId - * @param null|string $accountName - * - * @return Account|null - */ - abstract public function findAccount(?string $expectedType, ?int $accountId, ?string $accountName): ?Account; - /** * Update meta data for recurring transaction. * * @param Recurrence $recurrence - * @param array $data + * @param array $data */ - public function updateMetaData(Recurrence $recurrence, array $data): void + protected function updateMetaData(Recurrence $recurrence, array $data): void { // only two special meta fields right now. Let's just hard code them. $piggyId = (int)($data['meta']['piggy_bank_id'] ?? 0.0); @@ -201,8 +252,8 @@ trait RecurringTransactionTrait /** * @param Recurrence $recurrence - * @param int $piggyId - * @param string $piggyName + * @param int $piggyId + * @param string $piggyName */ protected function updatePiggyBank(Recurrence $recurrence, int $piggyId, string $piggyName): void { @@ -228,11 +279,11 @@ trait RecurringTransactionTrait /** * @param Recurrence $recurrence - * @param array $tags + * @param array $tags */ protected function updateTags(Recurrence $recurrence, array $tags): void { - if (\count($tags) > 0) { + if (count($tags) > 0) { /** @var RecurrenceMeta $entry */ $entry = $recurrence->recurrenceMeta()->where('name', 'tags')->first(); if (null === $entry) { @@ -241,7 +292,7 @@ trait RecurringTransactionTrait $entry->value = implode(',', $tags); $entry->save(); } - if (0 === \count($tags)) { + if (0 === count($tags)) { // delete if present $recurrence->recurrenceMeta()->where('name', 'tags')->delete(); } diff --git a/app/Services/Internal/Support/TransactionServiceTrait.php b/app/Services/Internal/Support/TransactionServiceTrait.php deleted file mode 100644 index d777bf6308..0000000000 --- a/app/Services/Internal/Support/TransactionServiceTrait.php +++ /dev/null @@ -1,232 +0,0 @@ -. - */ - -declare(strict_types=1); - -namespace FireflyIII\Services\Internal\Support; - - -use FireflyIII\Factory\AccountFactory; -use FireflyIII\Factory\BudgetFactory; -use FireflyIII\Factory\CategoryFactory; -use FireflyIII\Factory\TransactionCurrencyFactory; -use FireflyIII\Models\Account; -use FireflyIII\Models\AccountType; -use FireflyIII\Models\Budget; -use FireflyIII\Models\Category; -use FireflyIII\Models\Transaction; -use FireflyIII\Models\TransactionCurrency; -use FireflyIII\Models\TransactionJournal; -use FireflyIII\Models\TransactionType; -use FireflyIII\Repositories\Account\AccountRepositoryInterface; -use Log; - -/** - * Trait TransactionServiceTrait - * - */ -trait TransactionServiceTrait -{ - - /** - * @param TransactionJournal $journal - * @param string $direction - * - * @return string|null - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - */ - public function accountType(TransactionJournal $journal, string $direction): ?string - { - $types = []; - $type = $journal->transactionType->type; - if (TransactionType::WITHDRAWAL === $type) { - $types['source'] = AccountType::ASSET; - $types['destination'] = AccountType::EXPENSE; - } - if (TransactionType::DEPOSIT === $type) { - $types['source'] = AccountType::REVENUE; - $types['destination'] = AccountType::ASSET; - } - if (TransactionType::TRANSFER === $type) { - $types['source'] = AccountType::ASSET; - $types['destination'] = AccountType::ASSET; - } - if (TransactionType::RECONCILIATION === $type) { - $types['source'] = null; - $types['destination'] = null; - } - - return $types[$direction] ?? null; - } - - /** - * @param string|null $expectedType - * @param int|null $accountId - * @param string|null $accountName - * - * @return Account|null - * @throws \FireflyIII\Exceptions\FireflyException - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - */ - public function findAccount(?string $expectedType, ?int $accountId, ?string $accountName): ?Account - { - $accountId = (int)$accountId; - $accountName = (string)$accountName; - $repository = app(AccountRepositoryInterface::class); - $repository->setUser($this->user); - - Log::debug(sprintf('Going to find account #%d ("%s")', $accountId, $accountName)); - - if (null === $expectedType) { - return $repository->findNull($accountId); - } - - if ($accountId > 0) { - // must be able to find it based on ID. Validator should catch invalid ID's. - return $repository->findNull($accountId); - } - if (AccountType::ASSET === $expectedType) { - return $repository->findByName($accountName, [AccountType::ASSET]); - } - // for revenue and expense: - if ('' !== $accountName) { - /** @var AccountFactory $factory */ - $factory = app(AccountFactory::class); - $factory->setUser($this->user); - - return $factory->findOrCreate($accountName, $expectedType); - } - - return $repository->getCashAccount(); - } - - /** - * @param int|null $budgetId - * @param null|string $budgetName - * - * @return Budget|null - */ - protected function findBudget(?int $budgetId, ?string $budgetName): ?Budget - { - /** @var BudgetFactory $factory */ - $factory = app(BudgetFactory::class); - $factory->setUser($this->user); - - return $factory->find($budgetId, $budgetName); - } - - /** - * @param int|null $categoryId - * @param null|string $categoryName - * - * @return Category|null - */ - protected function findCategory(?int $categoryId, ?string $categoryName): ?Category - { - Log::debug(sprintf('Going to find or create category #%d, with name "%s"', $categoryId, $categoryName)); - /** @var CategoryFactory $factory */ - $factory = app(CategoryFactory::class); - $factory->setUser($this->user); - - return $factory->findOrCreate($categoryId, $categoryName); - } - - /** - * @param int|null $currencyId - * @param null|string $currencyCode - * - * @return TransactionCurrency|null - */ - protected function findCurrency(?int $currencyId, ?string $currencyCode): ?TransactionCurrency - { - $factory = app(TransactionCurrencyFactory::class); - - return $factory->find($currencyId, $currencyCode); - } - - /** - * @param Transaction $transaction - * @param Budget|null $budget - */ - protected function setBudget(Transaction $transaction, ?Budget $budget): void - { - if (null === $budget) { - $transaction->budgets()->sync([]); - - return; - } - $transaction->budgets()->sync([$budget->id]); - - } - - - /** - * @param Transaction $transaction - * @param Category|null $category - */ - protected function setCategory(Transaction $transaction, ?Category $category): void - { - if (null === $category) { - $transaction->categories()->sync([]); - - return; - } - $transaction->categories()->sync([$category->id]); - - } - - - /** - * @param Transaction $transaction - * @param string|null $amount - */ - protected function setForeignAmount(Transaction $transaction, ?string $amount): void - { - $amount = '' === (string)$amount ? null : $amount; - $transaction->foreign_amount = $amount; - $transaction->save(); - } - - /** - * @param Transaction $transaction - * @param TransactionCurrency|null $currency - */ - protected function setForeignCurrency(Transaction $transaction, ?TransactionCurrency $currency): void - { - if (null === $currency) { - $transaction->foreign_currency_id = null; - $transaction->save(); - - return; - } - // enable currency if not enabled: - if (false === $currency->enabled) { - $currency->enabled = true; - $currency->save(); - } - - $transaction->foreign_currency_id = $currency->id; - $transaction->save(); - - } - - -} diff --git a/app/Services/Internal/Update/AccountUpdateService.php b/app/Services/Internal/Update/AccountUpdateService.php index a7c0a26548..812143b7fb 100644 --- a/app/Services/Internal/Update/AccountUpdateService.php +++ b/app/Services/Internal/Update/AccountUpdateService.php @@ -25,8 +25,11 @@ namespace FireflyIII\Services\Internal\Update; use FireflyIII\Factory\TransactionCurrencyFactory; use FireflyIII\Models\Account; +use FireflyIII\Models\AccountType; use FireflyIII\Models\TransactionCurrency; +use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Services\Internal\Support\AccountServiceTrait; +use FireflyIII\User; use Log; /** @@ -36,14 +39,24 @@ class AccountUpdateService { use AccountServiceTrait; + /** @var array */ + private $canHaveVirtual; + /** @var AccountRepositoryInterface */ + protected $accountRepository; + + /** @var User */ + private $user; /** * Constructor. */ public function __construct() { if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } + // TODO move to configuration. + $this->canHaveVirtual = [AccountType::ASSET, AccountType::DEBT, AccountType::LOAN, AccountType::MORTGAGE, AccountType::CREDITCARD]; + $this->accountRepository = app(AccountRepositoryInterface::class); } /** @@ -53,48 +66,45 @@ class AccountUpdateService * @param array $data * * @return Account - * @throws \FireflyIII\Exceptions\FireflyException - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ public function update(Account $account, array $data): Account { + $this->accountRepository->setUser($account->user); + $this->user = $account->user; + // update the account itself: $account->name = $data['name']; $account->active = $data['active']; - $account->virtual_balance = '' === trim($data['virtualBalance']) ? '0' : $data['virtualBalance']; + $account->virtual_balance = '' === trim($data['virtual_balance']) ? '0' : $data['virtual_balance']; $account->iban = $data['iban']; $account->save(); if (isset($data['currency_id']) && 0 === $data['currency_id']) { unset($data['currency_id']); } - // find currency, or use default currency instead. - /** @var TransactionCurrencyFactory $factory */ - $factory = app(TransactionCurrencyFactory::class); - /** @var TransactionCurrency $currency */ - $currency = $factory->find($data['currency_id'] ?? null, $data['currency_code'] ?? null); - if (null === $currency) { - // use default currency: - $currency = app('amount')->getDefaultCurrencyByUser($account->user); - } - $currency->enabled = true; - $currency->save(); + // find currency, or use default currency instead. + $currency = $this->getCurrency((int)($data['currency_id'] ?? null), (string)($data['currency_code'] ?? null)); + unset($data['currency_code']); $data['currency_id'] = $currency->id; + // update all meta data: $this->updateMetaData($account, $data); // has valid initial balance (IB) data? - if ($this->validIBData($data)) { - // then do update! - $this->updateIB($account, $data); - } + $type = $account->accountType; + // if it can have a virtual balance, it can also have an opening balance. - // if not, delete it when exist. - if (!$this->validIBData($data)) { - $this->deleteIB($account); + if (in_array($type->type, $this->canHaveVirtual, true)) { + + if ($this->validOBData($data)) { + $this->updateOBGroup($account, $data); + } + + if (!$this->validOBData($data)) { + $this->deleteOBGroup($account); + } } // update note: diff --git a/app/Services/Internal/Update/BillUpdateService.php b/app/Services/Internal/Update/BillUpdateService.php index c1d4f8a621..f3bb7ff8ed 100644 --- a/app/Services/Internal/Update/BillUpdateService.php +++ b/app/Services/Internal/Update/BillUpdateService.php @@ -43,12 +43,12 @@ class BillUpdateService public function __construct() { if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } /** - * @param Bill $bill + * @param Bill $bill * @param array $data * * @return Bill @@ -69,9 +69,9 @@ class BillUpdateService $currency->enabled = true; $currency->save(); - $oldName = $bill->name; $bill->name = $data['name']; + $bill->match = $data['match'] ?? $bill->match; $bill->amount_min = $data['amount_min']; $bill->amount_max = $data['amount_max']; $bill->date = $data['date']; diff --git a/app/Services/Internal/Update/CategoryUpdateService.php b/app/Services/Internal/Update/CategoryUpdateService.php index fdd10cbd60..b3c176e65b 100644 --- a/app/Services/Internal/Update/CategoryUpdateService.php +++ b/app/Services/Internal/Update/CategoryUpdateService.php @@ -28,6 +28,7 @@ use Log; /** * Class CategoryUpdateService + * @codeCoverageIgnore */ class CategoryUpdateService { @@ -37,7 +38,7 @@ class CategoryUpdateService public function __construct() { if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } diff --git a/app/Services/Internal/Update/CurrencyUpdateService.php b/app/Services/Internal/Update/CurrencyUpdateService.php index 38149b77fe..ef5fa2393e 100644 --- a/app/Services/Internal/Update/CurrencyUpdateService.php +++ b/app/Services/Internal/Update/CurrencyUpdateService.php @@ -28,6 +28,7 @@ use Log; /** * Class CurrencyUpdateService + * @codeCoverageIgnore */ class CurrencyUpdateService { @@ -37,7 +38,7 @@ class CurrencyUpdateService public function __construct() { if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } diff --git a/app/Services/Internal/Update/GroupUpdateService.php b/app/Services/Internal/Update/GroupUpdateService.php new file mode 100644 index 0000000000..0bca2eb1de --- /dev/null +++ b/app/Services/Internal/Update/GroupUpdateService.php @@ -0,0 +1,153 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Services\Internal\Update; + +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Factory\TransactionJournalFactory; +use FireflyIII\Models\TransactionGroup; +use FireflyIII\Models\TransactionJournal; +use Log; + +/** + * Class GroupUpdateService + * TODO test. + */ +class GroupUpdateService +{ + /** + * Update a transaction group. + * + * @param TransactionGroup $transactionGroup + * @param array $data + * + * @return TransactionGroup + * @throws FireflyException + */ + public function update(TransactionGroup $transactionGroup, array $data): TransactionGroup + { + Log::debug('Now in group update service', $data); + $transactions = $data['transactions'] ?? []; + // update group name. + if (array_key_exists('group_title', $data)) { + Log::debug(sprintf('Update transaction group #%d title.', $transactionGroup->id)); + $transactionGroup->title = $data['group_title']; + $transactionGroup->save(); + } + if (1 === count($transactions) && 1 === $transactionGroup->transactionJournals()->count()) { + /** @var TransactionJournal $first */ + $first = $transactionGroup->transactionJournals()->first(); + Log::debug(sprintf('Will now update journal #%d (only journal in group #%d)', $first->id, $transactionGroup->id)); + $this->updateTransactionJournal($transactionGroup, $first, reset($transactions)); + $transactionGroup->refresh(); + app('preferences')->mark(); + + return $transactionGroup; + } + + Log::debug('Going to update split group.'); + + /** + * @var int $index + * @var array $transaction + */ + foreach ($transactions as $index => $transaction) { + Log::debug(sprintf('Now at #%d of %d', ($index + 1), count($transactions)), $transaction); + $journalId = (int)($transaction['transaction_journal_id'] ?? 0); + /** @var TransactionJournal $journal */ + $journal = $transactionGroup->transactionJournals()->find($journalId); + if (null === $journal) { + Log::debug('This entry has no existing journal: make a new split.'); + // force the transaction type on the transaction data. + // by plucking it from another journal in the group: + if (!isset($transaction['type'])) { + Log::debug('No transaction type is indicated.'); + /** @var TransactionJournal $randomJournal */ + $randomJournal = $transactionGroup->transactionJournals()->inRandomOrder()->with(['transactionType'])->first(); + if (null !== $randomJournal) { + $transaction['type'] = $randomJournal->transactionType->type; + Log::debug(sprintf('Transaction type set to %s.', $transaction['type'])); + } + } + Log::debug('Call createTransactionJournal'); + $this->createTransactionJournal($transactionGroup, $transaction); + Log::debug('Done calling createTransactionJournal'); + } + if (null !== $journal) { + Log::debug('Call updateTransactionJournal'); + $this->updateTransactionJournal($transactionGroup, $journal, $transaction); + Log::debug('Done calling updateTransactionJournal'); + } + } + + app('preferences')->mark(); + + return $transactionGroup; + } + + /** + * @param TransactionGroup $transactionGroup + * @param array $data + * @throws FireflyException + */ + private function createTransactionJournal(TransactionGroup $transactionGroup, array $data): void + { + + $submission = [ + 'transactions' => [ + $data, + ], + ]; + /** @var TransactionJournalFactory $factory */ + $factory = app(TransactionJournalFactory::class); + $factory->setUser($transactionGroup->user); + try { + $collection = $factory->create($submission); + } catch (FireflyException $e) { + Log::error($e->getMessage()); + Log::error($e->getTraceAsString()); + throw new FireflyException(sprintf('Could not create new transaction journal: %s', $e->getMessage())); + } + $collection->each(function (TransactionJournal $journal) use ($transactionGroup) { + $transactionGroup->transactionJournals()->save($journal); + }); + } + + /** + * Update single journal. + * + * @param TransactionGroup $transactionGroup + * @param TransactionJournal $journal + * @param array $data + */ + private function updateTransactionJournal(TransactionGroup $transactionGroup, TransactionJournal $journal, array $data): void + { + /** @var JournalUpdateService $updateService */ + $updateService = app(JournalUpdateService::class); + $updateService->setTransactionGroup($transactionGroup); + $updateService->setTransactionJournal($journal); + $updateService->setData($data); + $updateService->update(); + } + +} \ No newline at end of file diff --git a/app/Services/Internal/Update/JournalUpdateService.php b/app/Services/Internal/Update/JournalUpdateService.php index bdd17f41f7..be0114e0f6 100644 --- a/app/Services/Internal/Update/JournalUpdateService.php +++ b/app/Services/Internal/Update/JournalUpdateService.php @@ -23,180 +23,672 @@ declare(strict_types=1); namespace FireflyIII\Services\Internal\Update; -use FireflyIII\Factory\TransactionFactory; +use Carbon\Carbon; +use Exception; +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Factory\TagFactory; +use FireflyIII\Factory\TransactionJournalMetaFactory; +use FireflyIII\Factory\TransactionTypeFactory; +use FireflyIII\Models\Account; use FireflyIII\Models\Transaction; +use FireflyIII\Models\TransactionGroup; use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionType; +use FireflyIII\Repositories\Account\AccountRepositoryInterface; +use FireflyIII\Repositories\Bill\BillRepositoryInterface; +use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; +use FireflyIII\Repositories\Category\CategoryRepositoryInterface; +use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; use FireflyIII\Services\Internal\Support\JournalServiceTrait; -use Illuminate\Support\Collection; +use FireflyIII\Support\NullArrayObject; +use FireflyIII\Validation\AccountValidator; use Log; /** * Class to centralise code that updates a journal given the input by system. * * Class JournalUpdateService + * TODO test me */ class JournalUpdateService { use JournalServiceTrait; + /** @var BillRepositoryInterface */ + private $billRepository; + /** @var CurrencyRepositoryInterface */ + private $currencyRepository; + /** @var array The data to update the journal with. */ + private $data; + /** @var Account The destination account. */ + private $destinationAccount; + /** @var Transaction */ + private $destinationTransaction; + /** @var array All meta values that are dates. */ + private $metaDate; + /** @var array All meta values that are strings. */ + private $metaString; + /** @var Account Source account of the journal */ + private $sourceAccount; + /** @var Transaction Source transaction of the journal. */ + private $sourceTransaction; + /** @var TransactionGroup The parent group. */ + private $transactionGroup; + /** @var TransactionJournal The journal to update. */ + private $transactionJournal; + /** @var Account If new account info is submitted, this array will hold the valid destination. */ + private $validDestination; + /** @var Account If new account info is submitted, this array will hold the valid source. */ + private $validSource; + /** - * Constructor. + * JournalUpdateService constructor. */ public function __construct() { - if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + $this->billRepository = app(BillRepositoryInterface::class); + $this->categoryRepository = app(CategoryRepositoryInterface::class); + $this->budgetRepository = app(BudgetRepositoryInterface::class); + $this->tagFactory = app(TagFactory::class); + $this->accountRepository = app(AccountRepositoryInterface::class); + $this->currencyRepository = app(CurrencyRepositoryInterface::class); + $this->metaString = ['sepa_cc', 'sepa_ct_op', 'sepa_ct_id', 'sepa_db', 'sepa_country', 'sepa_ep', 'sepa_ci', 'sepa_batch_id', 'recurrence_id', + 'internal_reference', 'bunq_payment_id', 'external_id',]; + $this->metaDate = ['interest_date', 'book_date', 'process_date', 'due_date', 'payment_date', 'invoice_date',]; + } + + /** + * @param array $data + */ + public function setData(array $data): void + { + $this->data = $data; + } + + /** + * @param TransactionGroup $transactionGroup + */ + public function setTransactionGroup(TransactionGroup $transactionGroup): void + { + $this->transactionGroup = $transactionGroup; + $this->billRepository->setUser($transactionGroup->user); + $this->categoryRepository->setUser($transactionGroup->user); + $this->budgetRepository->setUser($transactionGroup->user); + $this->tagFactory->setUser($transactionGroup->user); + $this->accountRepository->setUser($transactionGroup->user); + } + + /** + * @param TransactionJournal $transactionJournal + */ + public function setTransactionJournal(TransactionJournal $transactionJournal): void + { + $this->transactionJournal = $transactionJournal; + } + + /** + * + */ + public function update(): void + { + Log::debug(sprintf('Now in JournalUpdateService for journal #%d.', $this->transactionJournal->id)); + // can we update account data using the new type? + if ($this->hasValidAccounts()) { + Log::info('-- account info is valid, now update.'); + // update accounts: + $this->updateAccounts(); + + // then also update transaction journal type ID: + $this->updateType(); + $this->transactionJournal->refresh(); + } + // find and update bill, if possible. + $this->updateBill(); + + // update journal fields. + $this->updateField('description'); + $this->updateField('date'); + $this->updateField('order'); + + $this->transactionJournal->save(); + $this->transactionJournal->refresh(); + + // update category + if ($this->hasFields(['category_id', 'category_name'])) { + Log::debug('Will update category.'); + + $this->storeCategory($this->transactionJournal, new NullArrayObject($this->data)); + } + // update budget + if ($this->hasFields(['budget_id', 'budget_name'])) { + Log::debug('Will update budget.'); + $this->storeBudget($this->transactionJournal, new NullArrayObject($this->data)); + } + // update tags + + if ($this->hasFields(['tags'])) { + Log::debug('Will update tags.'); + $tags = $this->data['tags'] ?? null; + $this->storeTags($this->transactionJournal, $tags); + } + + // update notes. + if ($this->hasFields(['notes'])) { + $notes = '' === (string)$this->data['notes'] ? null : $this->data['notes']; + $this->storeNotes($this->transactionJournal, $notes); + } + // update meta fields. + // first string + if ($this->hasFields($this->metaString)) { + Log::debug('Meta string fields are present.'); + $this->updateMetaFields(); + } + + // then date fields. + if ($this->hasFields($this->metaDate)) { + Log::debug('Meta date fields are present.'); + $this->updateMetaDateFields(); + } + + + // update transactions. + if ($this->hasFields(['currency_id', 'currency_code'])) { + $this->updateCurrency(); + } + if ($this->hasFields(['amount'])) { + $this->updateAmount(); + } + + // amount, foreign currency. + if ($this->hasFields(['foreign_currency_id', 'foreign_currency_code', 'foreign_amount'])) { + $this->updateForeignAmount(); + } + + // TODO update hash + + app('preferences')->mark(); + + $this->transactionJournal->refresh(); + } + + /** + * Get destination transaction. + * + * @return Transaction + */ + private function getDestinationTransaction(): Transaction + { + if (null === $this->destinationTransaction) { + $this->destinationTransaction = $this->transactionJournal->transactions()->where('amount', '>', 0)->first(); + } + + return $this->destinationTransaction; + } + + /** + * This method returns the current or expected type of the journal (in case of a change) based on the data in the array. + * + * If the array contains key 'type' and the value is correct, this is returned. Otherwise, the original type is returned. + * + * @return string + */ + private function getExpectedType(): string + { + Log::debug('Now in getExpectedType()'); + if ($this->hasFields(['type'])) { + return ucfirst('opening-balance' === $this->data['type'] ? 'opening balance' : $this->data['type']); + } + + return $this->transactionJournal->transactionType->type; + } + + /** + * @return Account + */ + private function getOriginalDestinationAccount(): Account + { + if (null === $this->destinationAccount) { + $destination = $this->getSourceTransaction(); + $this->destinationAccount = $destination->account; + } + + return $this->destinationAccount; + } + + /** + * @return Account + */ + private function getOriginalSourceAccount(): Account + { + if (null === $this->sourceAccount) { + $source = $this->getSourceTransaction(); + $this->sourceAccount = $source->account; + } + + return $this->sourceAccount; + } + + /** + * @return Transaction + */ + private function getSourceTransaction(): Transaction + { + if (null === $this->sourceTransaction) { + $this->sourceTransaction = $this->transactionJournal->transactions()->with(['account'])->where('amount', '<', 0)->first(); + } + Log::debug(sprintf('getSourceTransaction: %s', $this->sourceTransaction->amount)); + + return $this->sourceTransaction; + } + + /** + * Does a validation and returns the destination account. This method will break if the dest isn't really valid. + * + * @return Account + */ + private function getValidDestinationAccount(): Account + { + Log::debug('Now in getValidDestinationAccount().'); + + if (!$this->hasFields(['destination_id', 'destination_name'])) { + return $this->getOriginalDestinationAccount(); + } + + $destId = $this->data['destination_id'] ?? null; + $destName = $this->data['destination_name'] ?? null; + + // make new account validator. + $expectedType = $this->getExpectedType(); + Log::debug(sprintf('Expected type (new or unchanged) is %s', $expectedType)); + try { + $result = $this->getAccount($expectedType, 'destination', $destId, $destName); + } catch (FireflyException $e) { + Log::error(sprintf('getValidDestinationAccount() threw unexpected error: %s', $e->getMessage())); + $result = $this->getOriginalDestinationAccount(); + } + + return $result; + } + + /** + * Does a validation and returns the source account. This method will break if the source isn't really valid. + * + * @return Account + */ + private function getValidSourceAccount(): Account + { + Log::debug('Now in getValidSourceAccount().'); + $sourceId = $this->data['source_id'] ?? null; + $sourceName = $this->data['source_name'] ?? null; + + if (!$this->hasFields(['source_id', 'source_name'])) { + return $this->getOriginalSourceAccount(); + } + + $expectedType = $this->getExpectedType(); + try { + $result = $this->getAccount($expectedType, 'source', $sourceId, $sourceName); + } catch (FireflyException $e) { + Log::error(sprintf('Cant get the valid source account: %s', $e->getMessage())); + + $result = $this->getOriginalSourceAccount(); + } + + Log::debug(sprintf('getValidSourceAccount() will return #%d ("%s")', $result->id, $result->name)); + + return $result; + } + + /** + * @param array $fields + * + * @return bool + */ + private function hasFields(array $fields): bool + { + foreach ($fields as $field) { + if (array_key_exists($field, $this->data)) { + return true; + } + } + + return false; + } + + /** + * @return bool + */ + private function hasValidAccounts(): bool + { + return $this->hasValidSourceAccount() && $this->hasValidDestinationAccount(); + } + + /** + * @return bool + */ + private function hasValidDestinationAccount(): bool + { + Log::debug('Now in hasValidDestinationAccount().'); + $destId = $this->data['destination_id'] ?? null; + $destName = $this->data['destination_name'] ?? null; + + if (!$this->hasFields(['destination_id', 'destination_name'])) { + $destination = $this->getOriginalDestinationAccount(); + $destId = $destination->id; + $destName = $destination->name; + } + + // make new account validator. + $expectedType = $this->getExpectedType(); + Log::debug(sprintf('Expected type (new or unchanged) is %s', $expectedType)); + + // make a new validator. + /** @var AccountValidator $validator */ + $validator = app(AccountValidator::class); + $validator->setTransactionType($expectedType); + $validator->setUser($this->transactionJournal->user); + $validator->source = $this->getValidSourceAccount(); + + + $result = $validator->validateDestination($destId, $destName); + Log::debug(sprintf('hasValidDestinationAccount(%d, "%s") will return %s', $destId, $destName, var_export($result, true))); + + // validate submitted info: + return $result; + } + + /** + * @return bool + */ + private function hasValidSourceAccount(): bool + { + Log::debug('Now in hasValidSourceAccount().'); + $sourceId = $this->data['source_id'] ?? null; + $sourceName = $this->data['source_name'] ?? null; + + if (!$this->hasFields(['source_id', 'source_name'])) { + $sourceAccount = $this->getOriginalSourceAccount(); + $sourceId = $sourceAccount->id; + $sourceName = $sourceAccount->name; + } + + // make new account validator. + $expectedType = $this->getExpectedType(); + Log::debug(sprintf('Expected type (new or unchanged) is %s', $expectedType)); + + // make a new validator. + /** @var AccountValidator $validator */ + $validator = app(AccountValidator::class); + $validator->setTransactionType($expectedType); + $validator->setUser($this->transactionJournal->user); + + $result = $validator->validateSource($sourceId, $sourceName); + Log::debug(sprintf('hasValidSourceAccount(%d, "%s") will return %s', $sourceId, $sourceName, var_export($result, true))); + + // validate submitted info: + return $result; + } + + /** + * Will update the source and destination accounts of this journal. Assumes they are valid. + */ + private function updateAccounts(): void + { + $source = $this->getValidSourceAccount(); + $destination = $this->getValidDestinationAccount(); + + // cowardly refuse to update if both accounts are the same. + if ($source->id === $destination->id) { + Log::error(sprintf('Source + dest accounts are equal (%d, "%s")', $source->id, $source->name)); + + return; + } + + $sourceTransaction = $this->getSourceTransaction(); + $sourceTransaction->account()->associate($source); + $sourceTransaction->save(); + + $destTransaction = $this->getDestinationTransaction(); + $destTransaction->account()->associate($destination); + $destTransaction->save(); + + // refresh transactions. + $this->sourceTransaction->refresh(); + $this->destinationTransaction->refresh(); + + + Log::debug(sprintf('Will set source to #%d ("%s")', $source->id, $source->name)); + Log::debug(sprintf('Will set dest to #%d ("%s")', $destination->id, $destination->name)); + } + + /** + * + */ + private function updateAmount(): void + { + $value = $this->data['amount'] ?? ''; + try { + $amount = $this->getAmount($value); + } catch (FireflyException $e) { + Log::debug(sprintf('getAmount("%s") returns error: %s', $value, $e->getMessage())); + + return; + } + $sourceTransaction = $this->getSourceTransaction(); + $sourceTransaction->amount = app('steam')->negative($amount); + $sourceTransaction->save(); + + + $destTransaction = $this->getDestinationTransaction(); + $destTransaction->amount = app('steam')->positive($amount); + $destTransaction->save(); + + + // refresh transactions. + $this->sourceTransaction->refresh(); + $this->destinationTransaction->refresh(); + Log::debug(sprintf('Updated amount to "%s"', $amount)); + } + + /** + * Update journal bill information. + */ + private function updateBill(): void + { + $type = $this->transactionJournal->transactionType->type; + if (( + array_key_exists('bill_id', $this->data) + || array_key_exists('bill_name', $this->data) + ) + && TransactionType::WITHDRAWAL === $type + ) { + $billId = (int)($this->data['bill_id'] ?? 0); + $billName = (string)($this->data['bill_name'] ?? ''); + $bill = $this->billRepository->findBill($billId, $billName); + $this->transactionJournal->bill_id = null === $bill ? null : $bill->id; + Log::debug('Updated bill ID'); + } + } + + /** + * + */ + private function updateCurrency(): void + { + $currencyId = $this->data['currency_id'] ?? null; + $currencyCode = $this->data['currency_code'] ?? null; + $currency = $this->currencyRepository->findCurrency($currencyId, $currencyCode); + if (null !== $currency) { + // update currency everywhere. + $this->transactionJournal->transaction_currency_id = $currency->id; + $this->transactionJournal->save(); + + $source = $this->getSourceTransaction(); + $source->transaction_currency_id = $currency->id; + $source->save(); + + $dest = $this->getDestinationTransaction(); + $dest->transaction_currency_id = $currency->id; + $dest->save(); + + // refresh transactions. + $this->sourceTransaction->refresh(); + $this->destinationTransaction->refresh(); + Log::debug(sprintf('Updated currency to #%d (%s)', $currency->id, $currency->code)); + } + } + + /** + * Update journal generic field. Cannot be set to NULL. + * + * @param $fieldName + */ + private function updateField($fieldName): void + { + if (array_key_exists($fieldName, $this->data) && '' !== (string)$this->data[$fieldName]) { + $this->transactionJournal->$fieldName = $this->data[$fieldName]; + Log::debug(sprintf('Updated %s', $fieldName)); } } /** - * @param TransactionJournal $journal - * @param array $data * - * @return TransactionJournal - * @throws \FireflyIII\Exceptions\FireflyException - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ - public function update(TransactionJournal $journal, array $data): TransactionJournal + private function updateForeignAmount(): void { - // update journal: - $journal->description = $data['description']; - $journal->date = $data['date']; - $journal->save(); + $amount = $this->data['foreign_amount'] ?? null; + $foreignAmount = $this->getForeignAmount($amount); + $source = $this->getSourceTransaction(); + $dest = $this->getDestinationTransaction(); + $foreignCurrency = $source->foreignCurrency; - // update transactions: - /** @var TransactionUpdateService $service */ - $service = app(TransactionUpdateService::class); - $service->setUser($journal->user); + // find currency in data array + $newForeignId = $this->data['foreign_currency_id'] ?? null; + $newForeignCode = $this->data['foreign_currency_code'] ?? null; + $foreignCurrency = $this->currencyRepository->findCurrencyNull($newForeignId, $newForeignCode) ?? $foreignCurrency; - // create transactions: - /** @var TransactionFactory $factory */ - $factory = app(TransactionFactory::class); - $factory->setUser($journal->user); + // not the same as normal currency + if (null !== $foreignCurrency && $foreignCurrency->id === $this->transactionJournal->transaction_currency_id) { + Log::error(sprintf('Foreign currency is equal to normal currency (%s)', $foreignCurrency->code)); - Log::debug(sprintf('Found %d rows in array (should result in %d transactions', \count($data['transactions']), \count($data['transactions']) * 2)); - - /** - * @var int $identifier - * @var array $trData - */ - foreach ($data['transactions'] as $identifier => $trData) { - // exists transaction(s) with this identifier? update! - /** @var Collection $existing */ - $existing = $journal->transactions()->where('identifier', $identifier)->get(); - Log::debug(sprintf('Found %d transactions with identifier %d', $existing->count(), $identifier)); - if ($existing->count() > 0) { - $existing->each( - function (Transaction $transaction) use ($service, $trData) { - Log::debug(sprintf('Update transaction #%d (identifier %d)', $transaction->id, $trData['identifier'])); - $service->update($transaction, $trData); - } - ); - continue; - } - Log::debug('Found none, so create a pair.'); - // otherwise, create! - $factory->createPair($journal, $trData); + return; } - // could be that journal has more transactions than submitted (remove split) - $transactions = $journal->transactions()->where('amount', '>', 0)->get(); - Log::debug(sprintf('Journal #%d has %d transactions', $journal->id, $transactions->count())); - /** @var Transaction $transaction */ - foreach ($transactions as $transaction) { - Log::debug(sprintf('Now at transaction %d with identifier %d', $transaction->id, $transaction->identifier)); - if (!isset($data['transactions'][$transaction->identifier])) { - Log::debug('No such entry in array, delete this set of transactions.'); - $journal->transactions()->where('identifier', $transaction->identifier)->delete(); - } + + // add foreign currency info to source and destination if possible. + if (null !== $foreignCurrency && null !== $foreignAmount) { + $source->foreign_currency_id = $foreignCurrency->id; + $source->foreign_amount = app('steam')->negative($foreignAmount); + $source->save(); + + + $dest->foreign_currency_id = $foreignCurrency->id; + $dest->foreign_amount = app('steam')->positive($foreignAmount); + $dest->save(); + + Log::debug(sprintf('Update foreign info to %s (#%d) %s', $foreignCurrency->code, $foreignCurrency->id, $foreignAmount)); + + // refresh transactions. + $this->sourceTransaction->refresh(); + $this->destinationTransaction->refresh(); + + return; } - Log::debug(sprintf('New count is %d, transactions array held %d items', $journal->transactions()->count(), \count($data['transactions']))); + if ('0' === $amount) { + $source->foreign_currency_id = null; + $source->foreign_amount = null; + $source->save(); - // connect bill: - $this->connectBill($journal, $data); + $dest->foreign_currency_id = null; + $dest->foreign_amount = null; + $dest->save(); + Log::debug(sprintf('Foreign amount is "%s" so remove foreign amount info.', $amount)); + } + Log::info('Not enough info to update foreign currency info.'); - // connect tags: - $this->connectTags($journal, $data); - - // remove category from journal: - $journal->categories()->sync([]); - - // remove budgets from journal: - $journal->budgets()->sync([]); - - // update or create custom fields: - // store date meta fields (if present): - $this->storeMeta($journal, $data, 'interest_date'); - $this->storeMeta($journal, $data, 'book_date'); - $this->storeMeta($journal, $data, 'process_date'); - $this->storeMeta($journal, $data, 'due_date'); - $this->storeMeta($journal, $data, 'payment_date'); - $this->storeMeta($journal, $data, 'invoice_date'); - $this->storeMeta($journal, $data, 'internal_reference'); - - // store note: - $this->storeNote($journal, $data['notes']); - - - return $journal; + // refresh transactions. + $this->sourceTransaction->refresh(); + $this->destinationTransaction->refresh(); } /** - * Update budget for a journal. * - * @param TransactionJournal $journal - * @param int $budgetId - * - * @return TransactionJournal */ - public function updateBudget(TransactionJournal $journal, int $budgetId): TransactionJournal + private function updateMetaDateFields(): void { - /** @var TransactionUpdateService $service */ - $service = app(TransactionUpdateService::class); - $service->setUser($journal->user); - if (TransactionType::WITHDRAWAL === $journal->transactionType->type) { - /** @var Transaction $transaction */ - foreach ($journal->transactions as $transaction) { - $service->updateBudget($transaction, $budgetId); + /** @var TransactionJournalMetaFactory $factory */ + $factory = app(TransactionJournalMetaFactory::class); + + foreach ($this->metaDate as $field) { + if ($this->hasFields([$field])) { + try { + $value = '' === (string)$this->data[$field] ? null : new Carbon($this->data[$field]); + } catch (Exception $e) { + Log::debug(sprintf('%s is not a valid date value: %s', $this->data[$field], $e->getMessage())); + + return; + } + Log::debug(sprintf('Field "%s" is present ("%s"), try to update it.', $field, $value)); + $set = [ + 'journal' => $this->transactionJournal, + 'name' => $field, + 'data' => $value, + ]; + $factory->updateOrCreate($set); } - - return $journal; } - // clear budget. - /** @var Transaction $transaction */ - foreach ($journal->transactions as $transaction) { - $transaction->budgets()->sync([]); - } - // remove budgets from journal: - $journal->budgets()->sync([]); - - return $journal; } /** - * Update category for a journal. * - * @param TransactionJournal $journal - * @param string $category - * - * @return TransactionJournal */ - public function updateCategory(TransactionJournal $journal, string $category): TransactionJournal + private function updateMetaFields(): void { - /** @var TransactionUpdateService $service */ - $service = app(TransactionUpdateService::class); - $service->setUser($journal->user); + /** @var TransactionJournalMetaFactory $factory */ + $factory = app(TransactionJournalMetaFactory::class); - /** @var Transaction $transaction */ - foreach ($journal->transactions as $transaction) { - $service->updateCategory($transaction, $category); + foreach ($this->metaString as $field) { + if ($this->hasFields([$field])) { + $value = '' === $this->data[$field] ? null : $this->data[$field]; + Log::debug(sprintf('Field "%s" is present ("%s"), try to update it.', $field, $value)); + $set = [ + 'journal' => $this->transactionJournal, + 'name' => $field, + 'data' => $value, + ]; + $factory->updateOrCreate($set); + } } - // make journal empty: - $journal->categories()->sync([]); - - return $journal; } + /** + * Updates journal transaction type. + */ + private function updateType(): void + { + Log::debug('Now in updateType()'); + if ($this->hasFields(['type'])) { + $type = 'opening-balance' === $this->data['type'] ? 'opening balance' : $this->data['type']; + Log::debug( + sprintf( + 'Trying to change journal #%d from a %s to a %s.', + $this->transactionJournal->id, $this->transactionJournal->transactionType->type, $type + ) + ); + + /** @var TransactionTypeFactory $typeFactory */ + $typeFactory = app(TransactionTypeFactory::class); + $result = $typeFactory->find($this->data['type']); + if (null !== $result) { + Log::debug('Changed transaction type!'); + $this->transactionJournal->transaction_type_id = $result->id; + $this->transactionJournal->save(); + + return; + } + + return; + } + Log::debug('No type field present.'); + } } diff --git a/app/Services/Internal/Update/RecurrenceUpdateService.php b/app/Services/Internal/Update/RecurrenceUpdateService.php index 0fe5f18be7..ad8ca4a9a2 100644 --- a/app/Services/Internal/Update/RecurrenceUpdateService.php +++ b/app/Services/Internal/Update/RecurrenceUpdateService.php @@ -26,16 +26,16 @@ namespace FireflyIII\Services\Internal\Update; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\Recurrence; use FireflyIII\Services\Internal\Support\RecurringTransactionTrait; -use FireflyIII\Services\Internal\Support\TransactionServiceTrait; use FireflyIII\Services\Internal\Support\TransactionTypeTrait; use FireflyIII\User; /** * Class RecurrenceUpdateService + * @codeCoverageIgnore */ class RecurrenceUpdateService { - use TransactionTypeTrait, TransactionServiceTrait, RecurringTransactionTrait; + use TransactionTypeTrait, RecurringTransactionTrait; /** @var User */ private $user; @@ -51,7 +51,6 @@ class RecurrenceUpdateService */ public function update(Recurrence $recurrence, array $data): Recurrence { - // is expected by TransactionServiceTrait $this->user = $recurrence->user; $transactionType = $this->findTransactionType(ucfirst($data['recurrence']['type'])); // update basic fields first: @@ -66,10 +65,10 @@ class RecurrenceUpdateService if (isset($data['recurrence']['repetition_end'])) { - if (\in_array($data['recurrence']['repetition_end'], ['forever', 'until_date'])) { + if (in_array($data['recurrence']['repetition_end'], ['forever', 'until_date'])) { $recurrence->repetitions = 0; } - if (\in_array($data['recurrence']['repetition_end'], ['forever', 'times'])) { + if (in_array($data['recurrence']['repetition_end'], ['forever', 'times'])) { $recurrence->repeat_until = null; } } diff --git a/app/Services/Internal/Update/TransactionUpdateService.php b/app/Services/Internal/Update/TransactionUpdateService.php deleted file mode 100644 index 78ef4cbabd..0000000000 --- a/app/Services/Internal/Update/TransactionUpdateService.php +++ /dev/null @@ -1,178 +0,0 @@ -. - */ - -declare(strict_types=1); - -namespace FireflyIII\Services\Internal\Update; - -use FireflyIII\Models\Transaction; -use FireflyIII\Services\Internal\Support\TransactionServiceTrait; -use FireflyIII\User; -use Log; - -/** - * Class TransactionUpdateService - */ -class TransactionUpdateService -{ - use TransactionServiceTrait; - - /** @var User */ - private $user; - - /** - * Constructor. - */ - public function __construct() - { - if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); - } - } - - /** - * @param int $transactionId - * - * @return Transaction|null - */ - public function reconcile(int $transactionId): ?Transaction - { - $transaction = Transaction::find($transactionId); - if (null !== $transaction) { - $transaction->reconciled = true; - $transaction->save(); - - return $transaction; - } - - return null; - - } - - /** - * @param User $user - */ - public function setUser(User $user): void - { - $this->user = $user; - } - - /** - * @param Transaction $transaction - * @param array $data - * - * @return Transaction - * @throws \FireflyIII\Exceptions\FireflyException - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * @SuppressWarnings(PHPMD.NPathComplexity) - * - */ - public function update(Transaction $transaction, array $data): Transaction - { - $currency = $this->findCurrency($data['currency_id'], $data['currency_code']); - $journal = $transaction->transactionJournal; - $amount = (string)$data['amount']; - $account = null; - // update description: - $transaction->description = $data['description']; - $foreignAmount = null; - if ((float)$transaction->amount < 0) { - // this is the source transaction. - $type = $this->accountType($journal, 'source'); - $account = $this->findAccount($type, $data['source_id'], $data['source_name']); - $amount = app('steam')->negative($amount); - $foreignAmount = app('steam')->negative((string)$data['foreign_amount']); - } - - if ((float)$transaction->amount > 0) { - // this is the destination transaction. - $type = $this->accountType($journal, 'destination'); - $account = $this->findAccount($type, $data['destination_id'], $data['destination_name']); - $amount = app('steam')->positive($amount); - $foreignAmount = app('steam')->positive((string)$data['foreign_amount']); - } - - // update the actual transaction: - $transaction->description = $data['description']; - $transaction->amount = $amount; - $transaction->foreign_amount = null; - $transaction->transaction_currency_id = null === $currency ? $transaction->transaction_currency_id : $currency->id; - $transaction->account_id = $account->id; - $transaction->reconciled = $data['reconciled']; - $transaction->save(); - - // set foreign currency - $foreign = $this->findCurrency($data['foreign_currency_id'], $data['foreign_currency_code']); - // set foreign amount: - if (null !== $foreign && null !== $data['foreign_amount']) { - $this->setForeignCurrency($transaction, $foreign); - $this->setForeignAmount($transaction, $foreignAmount); - } - if (null === $foreign || null === $data['foreign_amount']) { - $this->setForeignCurrency($transaction, null); - $this->setForeignAmount($transaction, null); - } - - // set budget: - $budget = $this->findBudget($data['budget_id'], $data['budget_name']); - $this->setBudget($transaction, $budget); - - // set category - $category = $this->findCategory($data['category_id'], $data['category_name']); - $this->setCategory($transaction, $category); - - return $transaction; - } - - /** - * Update budget for a journal. - * - * @param Transaction $transaction - * @param int $budgetId - * - * @return Transaction - */ - public function updateBudget(Transaction $transaction, int $budgetId): Transaction - { - $budget = $this->findBudget($budgetId, null); - $this->setBudget($transaction, $budget); - - return $transaction; - - } - - /** - * Update category for a journal. - * - * @param Transaction $transaction - * @param string $category - * - * @return Transaction - */ - public function updateCategory(Transaction $transaction, string $category): Transaction - { - $found = $this->findCategory(0, $category); - $this->setCategory($transaction, $found); - - return $transaction; - } -} diff --git a/app/Services/Password/PwndVerifierV2.php b/app/Services/Password/PwndVerifierV2.php index e46498f75f..571e8277fb 100644 --- a/app/Services/Password/PwndVerifierV2.php +++ b/app/Services/Password/PwndVerifierV2.php @@ -30,6 +30,7 @@ use RuntimeException; /** * Class PwndVerifierV2. + * @codeCoverageIgnore */ class PwndVerifierV2 implements Verifier { @@ -39,7 +40,7 @@ class PwndVerifierV2 implements Verifier public function __construct() { if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } diff --git a/app/Services/Password/PwndVerifierV3.php b/app/Services/Password/PwndVerifierV3.php new file mode 100644 index 0000000000..833c86872e --- /dev/null +++ b/app/Services/Password/PwndVerifierV3.php @@ -0,0 +1,92 @@ +. + */ + +namespace FireflyIII\Services\Password; + + +use Exception; +use GuzzleHttp\Client; +use GuzzleHttp\Exception\GuzzleException; +use Log; +use RuntimeException; + +/** + * Class PwndVerifierV3 + * @codeCoverageIgnore + */ +class PwndVerifierV3 implements Verifier +{ + + /** + * Verify the given password against (some) service. + * + * @param string $password + * + * @return bool + */ + public function validPassword(string $password): bool + { + Log::debug('Now in API v3.'); + $hash = strtoupper(sha1($password)); + $prefix = substr($hash, 0, 5); + $rest = substr($hash, 5); + $uri = sprintf('https://api.pwnedpasswords.com/%s/%s', 'range', $prefix); + + Log::debug(sprintf('URI is %s', $uri)); + + $headers = [ + 'User-Agent' => sprintf('Firefly III v%s', config('firefly.version')), + ]; + Log::debug('Headers', $headers); + $opts = [ + 'headers' => $headers, + 'timeout' => 5, + ]; + + Log::debug(sprintf('hash prefix is %s', $prefix)); + Log::debug(sprintf('rest is %s', $rest)); + + try { + $client = new Client; + $res = $client->request('GET', $uri, $opts); + } catch (GuzzleException|Exception $e) { + Log::error(sprintf('Could not verify password security: %s', $e->getMessage())); + return true; + } + Log::debug(sprintf('Status code returned is %d', $res->getStatusCode())); + if (404 === $res->getStatusCode()) { + return true; + } + $body = $res->getBody()->getContents(); + try { + $strpos = stripos($body, $rest); + } catch (RuntimeException $e) { + Log::error(sprintf('Could not get body from Pwnd result: %s', $e->getMessage())); + $strpos = false; + } + if (false === $strpos) { + Log::debug(sprintf('%s was not found in result body. Return true.', $rest)); + return true; + } + Log::debug(sprintf('Found %s, so return FALSE.', $rest)); + return false; + } +} \ No newline at end of file diff --git a/app/Services/Spectre/Object/Account.php b/app/Services/Spectre/Object/Account.php index e8069f99f1..5270360736 100644 --- a/app/Services/Spectre/Object/Account.php +++ b/app/Services/Spectre/Object/Account.php @@ -67,7 +67,7 @@ class Account extends SpectreObject $this->nature = $data['nature']; $this->createdAt = new Carbon($data['created_at']); $this->updatedAt = new Carbon($data['updated_at']); - $extraArray = \is_array($data['extra']) ? $data['extra'] : []; + $extraArray = is_array($data['extra']) ? $data['extra'] : []; foreach ($extraArray as $key => $value) { $this->extra[$key] = $value; } diff --git a/app/Services/Spectre/Request/CreateTokenRequest.php b/app/Services/Spectre/Request/CreateTokenRequest.php index c700d7b78c..83e1aaab03 100644 --- a/app/Services/Spectre/Request/CreateTokenRequest.php +++ b/app/Services/Spectre/Request/CreateTokenRequest.php @@ -29,6 +29,7 @@ use FireflyIII\Services\Spectre\Object\Token; /** * Class CreateTokenRequest + * @codeCoverageIgnore */ class CreateTokenRequest extends SpectreRequest { diff --git a/app/Services/Spectre/Request/ListAccountsRequest.php b/app/Services/Spectre/Request/ListAccountsRequest.php index a641819202..469ba5e313 100644 --- a/app/Services/Spectre/Request/ListAccountsRequest.php +++ b/app/Services/Spectre/Request/ListAccountsRequest.php @@ -30,6 +30,7 @@ use Log; /** * Class ListAccountsRequest + * @codeCoverageIgnore */ class ListAccountsRequest extends SpectreRequest { @@ -53,7 +54,7 @@ class ListAccountsRequest extends SpectreRequest $response = $this->sendSignedSpectreGet($uri, []); // count entries: - Log::debug(sprintf('Found %d entries in data-array', \count($response['data']))); + Log::debug(sprintf('Found %d entries in data-array', count($response['data']))); // extract next ID $hasNextPage = false; diff --git a/app/Services/Spectre/Request/ListCustomersRequest.php b/app/Services/Spectre/Request/ListCustomersRequest.php index 5b92b00ec2..f17caed6a1 100644 --- a/app/Services/Spectre/Request/ListCustomersRequest.php +++ b/app/Services/Spectre/Request/ListCustomersRequest.php @@ -29,6 +29,7 @@ use Log; /** * Class ListCustomersRequest + * @codeCoverageIgnore */ class ListCustomersRequest extends SpectreRequest { @@ -51,7 +52,7 @@ class ListCustomersRequest extends SpectreRequest $response = $this->sendSignedSpectreGet($uri, []); // count entries: - Log::debug(sprintf('Found %d entries in data-array', \count($response['data']))); + Log::debug(sprintf('Found %d entries in data-array', count($response['data']))); // extract next ID $hasNextPage = false; diff --git a/app/Services/Spectre/Request/ListLoginsRequest.php b/app/Services/Spectre/Request/ListLoginsRequest.php index f5cda26a3b..8062ef1da6 100644 --- a/app/Services/Spectre/Request/ListLoginsRequest.php +++ b/app/Services/Spectre/Request/ListLoginsRequest.php @@ -31,6 +31,7 @@ use Log; /** * Class ListLoginsRequest + * @codeCoverageIgnore */ class ListLoginsRequest extends SpectreRequest { @@ -55,7 +56,7 @@ class ListLoginsRequest extends SpectreRequest $response = $this->sendSignedSpectreGet($uri, []); // count entries: - Log::debug(sprintf('Found %d entries in data-array', \count($response['data']))); + Log::debug(sprintf('Found %d entries in data-array', count($response['data']))); // extract next ID $hasNextPage = false; @@ -72,7 +73,7 @@ class ListLoginsRequest extends SpectreRequest } // sort logins by date created: $sorted = $collection->sortByDesc( - function (Login $login) { + static function (Login $login) { return $login->getUpdatedAt()->timestamp; } ); diff --git a/app/Services/Spectre/Request/ListTransactionsRequest.php b/app/Services/Spectre/Request/ListTransactionsRequest.php index 642562a067..0fd161c751 100644 --- a/app/Services/Spectre/Request/ListTransactionsRequest.php +++ b/app/Services/Spectre/Request/ListTransactionsRequest.php @@ -30,6 +30,7 @@ use Log; /** * Class ListTransactionsRequest + * @codeCoverageIgnore */ class ListTransactionsRequest extends SpectreRequest { @@ -53,7 +54,7 @@ class ListTransactionsRequest extends SpectreRequest $response = $this->sendSignedSpectreGet($uri, []); // count entries: - Log::debug(sprintf('Found %d entries in data-array', \count($response['data']))); + Log::debug(sprintf('Found %d entries in data-array', count($response['data']))); // extract next ID $hasNextPage = false; diff --git a/app/Services/Spectre/Request/NewCustomerRequest.php b/app/Services/Spectre/Request/NewCustomerRequest.php index 36e4946387..f6c31f9932 100644 --- a/app/Services/Spectre/Request/NewCustomerRequest.php +++ b/app/Services/Spectre/Request/NewCustomerRequest.php @@ -27,6 +27,7 @@ use Log; /** * Class NewCustomerRequest + * @codeCoverageIgnore */ class NewCustomerRequest extends SpectreRequest { diff --git a/app/Services/Spectre/Request/SpectreRequest.php b/app/Services/Spectre/Request/SpectreRequest.php index d392fad037..f07a5e175e 100644 --- a/app/Services/Spectre/Request/SpectreRequest.php +++ b/app/Services/Spectre/Request/SpectreRequest.php @@ -32,6 +32,7 @@ use RuntimeException; /** * Class SpectreRequest + * @codeCoverageIgnore */ abstract class SpectreRequest { diff --git a/app/Services/Ynab/Request/GetAccountsRequest.php b/app/Services/Ynab/Request/GetAccountsRequest.php index 81c4a1f5be..c4e4e83bae 100644 --- a/app/Services/Ynab/Request/GetAccountsRequest.php +++ b/app/Services/Ynab/Request/GetAccountsRequest.php @@ -27,6 +27,7 @@ use Log; /** * Class GetAccountsRequest + * @codeCoverageIgnore */ class GetAccountsRequest extends YnabRequest { diff --git a/app/Services/Ynab/Request/GetBudgetsRequest.php b/app/Services/Ynab/Request/GetBudgetsRequest.php index b087467b3f..03691de2d5 100644 --- a/app/Services/Ynab/Request/GetBudgetsRequest.php +++ b/app/Services/Ynab/Request/GetBudgetsRequest.php @@ -27,6 +27,7 @@ use Log; /** * Class GetBudgetsRequest + * @codeCoverageIgnore */ class GetBudgetsRequest extends YnabRequest { diff --git a/app/Services/Ynab/Request/GetTransactionsRequest.php b/app/Services/Ynab/Request/GetTransactionsRequest.php index bc6e23cd9e..cc02d9c40b 100644 --- a/app/Services/Ynab/Request/GetTransactionsRequest.php +++ b/app/Services/Ynab/Request/GetTransactionsRequest.php @@ -27,6 +27,7 @@ use Log; /** * Class GetTransactionsRequest + * @codeCoverageIgnore */ class GetTransactionsRequest extends YnabRequest { diff --git a/app/Services/Ynab/Request/YnabRequest.php b/app/Services/Ynab/Request/YnabRequest.php index 26eb6a3800..c538100db2 100644 --- a/app/Services/Ynab/Request/YnabRequest.php +++ b/app/Services/Ynab/Request/YnabRequest.php @@ -30,6 +30,7 @@ use RuntimeException; /** * Class YnabRequest + * @codeCoverageIgnore */ abstract class YnabRequest { @@ -60,7 +61,7 @@ abstract class YnabRequest 'Authorization' => 'Bearer ' . $this->token, ], ]; - if (\count($params) > 0) { + if (count($params) > 0) { $uri = $uri . '?' . http_build_query($params); } Log::debug(sprintf('Going to call YNAB on URI: %s', $uri), $options); diff --git a/app/Support/Amount.php b/app/Support/Amount.php index 797dce4779..fa8d176d2c 100644 --- a/app/Support/Amount.php +++ b/app/Support/Amount.php @@ -33,6 +33,7 @@ use Preferences as Prefs; /** * Class Amount. + * @codeCoverageIgnore */ class Amount { @@ -154,11 +155,57 @@ class Amount return $result; } + /** + * This method will properly format the given number, in color or "black and white", + * as a currency, given two things: the currency required and the current locale. + * + * @param string $symbol + * @param int $decimalPlaces + * @param string $amount + * @param bool $coloured + * + * @return string + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @noinspection MoreThanThreeArgumentsInspection + */ + public function formatFlat(string $symbol, int $decimalPlaces, string $amount, bool $coloured = null): string + { + $coloured = $coloured ?? true; + $locale = explode(',', (string)trans('config.locale')); + $locale = array_map('trim', $locale); + setlocale(LC_MONETARY, $locale); + $float = round($amount, 12); + $info = localeconv(); + $formatted = number_format($float, $decimalPlaces, $info['mon_decimal_point'], $info['mon_thousands_sep']); + + // some complicated switches to format the amount correctly: + $precedes = $amount < 0 ? $info['n_cs_precedes'] : $info['p_cs_precedes']; + $separated = $amount < 0 ? $info['n_sep_by_space'] : $info['p_sep_by_space']; + $space = true === $separated ? ' ' : ''; + $result = false === $precedes ? $formatted . $space . $symbol : $symbol . $space . $formatted; + + if (true === $coloured) { + if ($amount > 0) { + return sprintf('%s', $result); + } + if ($amount < 0) { + return sprintf('%s', $result); + } + + return sprintf('%s', $result); + } + + return $result; + } + /** * @return Collection */ public function getAllCurrencies(): Collection { + if ('testing' === config('app.env')) { + Log::warning(sprintf('%s should NOT be called in the TEST environment!', __METHOD__)); + } return TransactionCurrency::orderBy('code', 'ASC')->get(); } @@ -167,6 +214,9 @@ class Amount */ public function getCurrencies(): Collection { + if ('testing' === config('app.env')) { + Log::warning(sprintf('%s should NOT be called in the TEST environment!', __METHOD__)); + } return TransactionCurrency::where('enabled', true)->orderBy('code', 'ASC')->get(); } @@ -175,6 +225,9 @@ class Amount */ public function getCurrencyCode(): string { + if ('testing' === config('app.env')) { + Log::warning(sprintf('%s should NOT be called in the TEST environment!', __METHOD__)); + } $cache = new CacheProperties; $cache->addProperty('getCurrencyCode'); if ($cache->has()) { @@ -198,6 +251,9 @@ class Amount */ public function getCurrencySymbol(): string { + if ('testing' === config('app.env')) { + Log::warning(sprintf('%s should NOT be called in the TEST environment!', __METHOD__)); + } $cache = new CacheProperties; $cache->addProperty('getCurrencySymbol'); if ($cache->has()) { @@ -218,6 +274,9 @@ class Amount */ public function getDefaultCurrency(): TransactionCurrency { + if ('testing' === config('app.env')) { + Log::warning(sprintf('%s should NOT be called in the TEST environment!', __METHOD__)); + } /** @var User $user */ $user = auth()->user(); @@ -233,6 +292,9 @@ class Amount */ public function getDefaultCurrencyByUser(User $user): TransactionCurrency { + if ('testing' === config('app.env')) { + Log::warning(sprintf('%s should NOT be called in the TEST environment!', __METHOD__)); + } $cache = new CacheProperties; $cache->addProperty('getDefaultCurrency'); $cache->addProperty($user->id); @@ -246,7 +308,7 @@ class Amount $currencyCode = $this->tryDecrypt((string)$currencyPreference->data); // could still be json encoded: - if (\strlen($currencyCode) > 3) { + if (strlen($currencyCode) > 3) { $currencyCode = json_decode($currencyCode) ?? 'EUR'; } diff --git a/app/Support/Binder/AccountList.php b/app/Support/Binder/AccountList.php index 3c0cca2618..b7c05fb049 100644 --- a/app/Support/Binder/AccountList.php +++ b/app/Support/Binder/AccountList.php @@ -22,7 +22,6 @@ declare(strict_types=1); namespace FireflyIII\Support\Binder; -use FireflyIII\Models\Account; use FireflyIII\Models\AccountType; use Illuminate\Routing\Route; use Illuminate\Support\Collection; @@ -37,7 +36,7 @@ class AccountList implements BinderInterface /** * @param string $value - * @param Route $route + * @param Route $route * * @return Collection * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException @@ -45,32 +44,32 @@ class AccountList implements BinderInterface */ public static function routeBinder(string $value, Route $route): Collection { + //Log::debug(sprintf('Now in AccountList::routeBinder("%s")', $value)); if (auth()->check()) { + //Log::debug('User is logged in.'); $collection = new Collection; if ('allAssetAccounts' === $value) { - /** @var \Illuminate\Support\Collection $collection */ + /** @var Collection $collection */ $collection = auth()->user()->accounts() ->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id') ->where('account_types.type', AccountType::ASSET) + ->orderBy('accounts.name', 'ASC') ->get(['accounts.*']); + //Log::debug(sprintf('Collection length is %d', $collection->count())); } if ('allAssetAccounts' !== $value) { $incoming = array_map('\intval', explode(',', $value)); $list = array_merge(array_unique($incoming), [0]); - /** @var \Illuminate\Support\Collection $collection */ + /** @var Collection $collection */ $collection = auth()->user()->accounts() ->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id') ->whereIn('accounts.id', $list) + ->orderBy('accounts.name', 'ASC') ->get(['accounts.*']); + //Log::debug(sprintf('Collection length is %d', $collection->count())); } if ($collection->count() > 0) { - $collection = $collection->sortBy( - function (Account $account) { - return $account->name; - } - ); - return $collection; } } diff --git a/app/Support/Binder/BudgetList.php b/app/Support/Binder/BudgetList.php index 93921d7c53..09f6188e9a 100644 --- a/app/Support/Binder/BudgetList.php +++ b/app/Support/Binder/BudgetList.php @@ -25,6 +25,7 @@ namespace FireflyIII\Support\Binder; use FireflyIII\Models\Budget; use Illuminate\Routing\Route; use Illuminate\Support\Collection; +use Log; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** @@ -34,7 +35,7 @@ class BudgetList implements BinderInterface { /** * @param string $value - * @param Route $route + * @param Route $route * * @return Collection * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException @@ -42,27 +43,38 @@ class BudgetList implements BinderInterface */ public static function routeBinder(string $value, Route $route): Collection { + //Log::debug(sprintf('Now in BudgetList::routeBinder("%s")', $value)); if (auth()->check()) { $list = array_unique(array_map('\intval', explode(',', $value))); - if (0 === \count($list)) { - throw new NotFoundHttpException; // @codeCoverageIgnore - } - /** @var \Illuminate\Support\Collection $collection */ + // @codeCoverageIgnoreStart + if (0 === count($list)) { + Log::warning('Budget list count is zero, return 404.'); + throw new NotFoundHttpException; + } + // @codeCoverageIgnoreEnd + + /** @var Collection $collection */ $collection = auth()->user()->budgets() ->where('active', 1) ->whereIn('id', $list) ->get(); + //Log::debug(sprintf('Found %d active budgets', $collection->count()), $list); // add empty budget if applicable. - if (\in_array(0, $list, true)) { + if (in_array(0, $list, true)) { + //Log::debug('Add empty budget because $list contains 0.'); $collection->push(new Budget); } if ($collection->count() > 0) { + //Log::debug(sprintf('List length is > 0 (%d), so return it.', $collection->count())); + return $collection; } + //Log::debug('List length is zero, fall back to 404.'); } + Log::warning('BudgetList fallback to 404.'); throw new NotFoundHttpException; } } diff --git a/app/Support/Binder/CategoryList.php b/app/Support/Binder/CategoryList.php index 37e392b9ab..46df804e55 100644 --- a/app/Support/Binder/CategoryList.php +++ b/app/Support/Binder/CategoryList.php @@ -44,7 +44,7 @@ class CategoryList implements BinderInterface { if (auth()->check()) { $list = array_unique(array_map('\intval', explode(',', $value))); - if (0 === \count($list)) { + if (0 === count($list)) { throw new NotFoundHttpException; // @codeCoverageIgnore } @@ -54,7 +54,7 @@ class CategoryList implements BinderInterface ->get(); // add empty category if applicable. - if (\in_array(0, $list, true)) { + if (in_array(0, $list, true)) { $collection->push(new Category); } diff --git a/app/Support/Binder/ConfigurationName.php b/app/Support/Binder/ConfigurationName.php index 9b4f6751a5..9fdb3c0e0d 100644 --- a/app/Support/Binder/ConfigurationName.php +++ b/app/Support/Binder/ConfigurationName.php @@ -43,7 +43,7 @@ class ConfigurationName implements BinderInterface public static function routeBinder(string $value, Route $route): string { $accepted = ['is_demo_site', 'permission_update_check', 'single_user_mode']; - if (\in_array($value, $accepted, true)) { + if (in_array($value, $accepted, true)) { return $value; } throw new NotFoundHttpException; diff --git a/app/Support/Binder/Date.php b/app/Support/Binder/Date.php index 55aed73c56..0d42bd2704 100644 --- a/app/Support/Binder/Date.php +++ b/app/Support/Binder/Date.php @@ -24,7 +24,7 @@ namespace FireflyIII\Support\Binder; use Carbon\Carbon; use Exception; -use FireflyIII\Helpers\FiscalHelperInterface; +use FireflyIII\Helpers\Fiscal\FiscalHelperInterface; use Illuminate\Routing\Route; use Log; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; diff --git a/app/Support/Binder/ImportProvider.php b/app/Support/Binder/ImportProvider.php index 926696b8ab..5a01ff38dc 100644 --- a/app/Support/Binder/ImportProvider.php +++ b/app/Support/Binder/ImportProvider.php @@ -44,8 +44,14 @@ class ImportProvider implements BinderInterface { $repository = app(UserRepositoryInterface::class); // get and filter all import routines: + + if (!auth()->check()) { + return []; + } + /** @var User $user */ $user = auth()->user(); + /** @var array $config */ $providerNames = array_keys(config('import.enabled')); $providers = []; @@ -55,12 +61,15 @@ class ImportProvider implements BinderInterface // only consider enabled providers $enabled = (bool)config(sprintf('import.enabled.%s', $providerName)); $allowedForUser = (bool)config(sprintf('import.allowed_for_user.%s', $providerName)); + $allowedForDemo = (bool)config(sprintf('import.allowed_for_demo.%s', $providerName)); if (false === $enabled) { continue; } - - if (false === $isDemoUser && false === $allowedForUser && false === $isDebug) { - continue; // @codeCoverageIgnore + if (false === $allowedForUser && !$isDemoUser) { + continue; + } + if (false === $allowedForDemo && $isDemoUser) { + continue; } $providers[$providerName] = [ @@ -78,7 +87,7 @@ class ImportProvider implements BinderInterface } $providers[$providerName]['prereq_complete'] = $result; } - Log::debug(sprintf('Enabled providers: %s', json_encode(array_keys($providers)))); + //Log::debug(sprintf('Enabled providers: %s', json_encode(array_keys($providers)))); return $providers; } @@ -93,7 +102,7 @@ class ImportProvider implements BinderInterface public static function routeBinder(string $value, Route $route): string { $providers = array_keys(self::getProviders()); - if (\in_array($value, $providers, true)) { + if (in_array($value, $providers, true)) { return $value; } throw new NotFoundHttpException; diff --git a/app/Support/Binder/JournalList.php b/app/Support/Binder/JournalList.php index 7562d9346c..8abc85ed24 100644 --- a/app/Support/Binder/JournalList.php +++ b/app/Support/Binder/JournalList.php @@ -22,8 +22,9 @@ declare(strict_types=1); namespace FireflyIII\Support\Binder; +use FireflyIII\Helpers\Collector\GroupCollectorInterface; +use FireflyIII\Models\TransactionType; use Illuminate\Routing\Route; -use Illuminate\Support\Collection; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; /** @@ -38,24 +39,38 @@ class JournalList implements BinderInterface * @return mixed * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException */ - public static function routeBinder(string $value, Route $route): Collection + public static function routeBinder(string $value, Route $route): array { if (auth()->check()) { - $list = array_unique(array_map('\intval', explode(',', $value))); - if (0 === \count($list)) { - throw new NotFoundHttpException; // @codeCoverageIgnore + $list = self::parseList($value); + + // get the journals by using the collector. + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setTypes([TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::TRANSFER]); + $collector->withCategoryInformation()->withBudgetInformation()->withTagInformation()->withAccountInformation(); + $collector->setJournalIds($list); + $result = $collector->getExtractedJournals(); + if (0 === count($result)) { + throw new NotFoundHttpException; } - /** @var \Illuminate\Support\Collection $collection */ - $collection = auth()->user()->transactionJournals() - ->whereIn('transaction_journals.id', $list) - ->where('transaction_journals.completed', 1) - ->get(['transaction_journals.*']); - - if ($collection->count() > 0) { - return $collection; - } + return $result; } throw new NotFoundHttpException; } + + /** + * @param string $value + * @return array + */ + protected static function parseList(string $value): array + { + $list = array_unique(array_map('\intval', explode(',', $value))); + if (0 === count($list)) { + throw new NotFoundHttpException; // @codeCoverageIgnore + } + + return $list; + } } diff --git a/app/Support/Binder/SimpleJournalList.php b/app/Support/Binder/SimpleJournalList.php deleted file mode 100644 index 35708338ed..0000000000 --- a/app/Support/Binder/SimpleJournalList.php +++ /dev/null @@ -1,107 +0,0 @@ -. - */ - -declare(strict_types=1); - -namespace FireflyIII\Support\Binder; - -use FireflyIII\Models\TransactionJournal; -use FireflyIII\Models\TransactionType; -use FireflyIII\Repositories\Journal\JournalRepositoryInterface; -use Illuminate\Routing\Route; -use Illuminate\Support\Collection; -use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; - -/** - * Class SimpleJournalList - */ -class SimpleJournalList implements BinderInterface -{ - /** - * @param string $value - * @param Route $route - * - * @return mixed - * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - * @SuppressWarnings(PHPMD.NPathComplexity) - */ - public static function routeBinder(string $value, Route $route): Collection - { - if (auth()->check()) { - $list = array_unique(array_map('\intval', explode(',', $value))); - if (0 === \count($list)) { - throw new NotFoundHttpException; // @codeCoverageIgnore - } - - // prep some vars - $messages = []; - $final = new Collection; - /** @var JournalRepositoryInterface $repository */ - $repository = app(JournalRepositoryInterface::class); - - // get all journals: - /** @var \Illuminate\Support\Collection $collection */ - $collection = auth()->user()->transactionJournals() - ->whereIn('transaction_journals.id', $list) - ->where('transaction_journals.completed', 1) - ->get(['transaction_journals.*']); - - // filter the list! Yay! - /** @var TransactionJournal $journal */ - foreach ($collection as $journal) { - $sources = $repository->getJournalSourceAccounts($journal); - $destinations = $repository->getJournalDestinationAccounts($journal); - if ($sources->count() > 1) { - $messages[] = (string)trans('firefly.cannot_edit_multiple_source', ['description' => $journal->description, 'id' => $journal->id]); - continue; - } - - if ($destinations->count() > 1) { - $messages[] = (string)trans('firefly.cannot_edit_multiple_dest', ['description' => $journal->description, 'id' => $journal->id]); - continue; - } - if (TransactionType::OPENING_BALANCE === $repository->getTransactionType($journal)) { - $messages[] = (string)trans('firefly.cannot_edit_opening_balance'); - continue; - } - - // cannot edit reconciled transactions / journals: - if ($repository->isJournalReconciled($journal)) { - $messages[] = (string)trans('firefly.cannot_edit_reconciled', ['description' => $journal->description, 'id' => $journal->id]); - continue; - } - - $final->push($journal); - } - - if ($final->count() > 0) { - if (\count($messages) > 0) { - session()->flash('info', $messages); - } - - return $final; - } - } - throw new NotFoundHttpException; - } -} diff --git a/app/Support/Binder/TagList.php b/app/Support/Binder/TagList.php index b6162cf766..62d64dd568 100644 --- a/app/Support/Binder/TagList.php +++ b/app/Support/Binder/TagList.php @@ -46,21 +46,24 @@ class TagList implements BinderInterface if (auth()->check()) { $list = array_unique(array_map('\strtolower', explode(',', $value))); Log::debug('List of tags is', $list); - if (0 === \count($list)) { + // @codeCoverageIgnoreStart + if (0 === count($list)) { Log::error('Tag list is empty.'); - throw new NotFoundHttpException; // @codeCoverageIgnore + throw new NotFoundHttpException; } + // @codeCoverageIgnoreEnd + /** @var TagRepositoryInterface $repository */ $repository = app(TagRepositoryInterface::class); $repository->setUser(auth()->user()); $allTags = $repository->get(); $collection = $allTags->filter( - function (Tag $tag) use ($list) { - if (\in_array(strtolower($tag->tag), $list, true)) { + static function (Tag $tag) use ($list) { + if (in_array(strtolower($tag->tag), $list, true)) { return true; } - if (\in_array((string)$tag->id, $list, true)) { + if (in_array((string)$tag->id, $list, true)) { return true; } diff --git a/app/Support/Binder/UnfinishedJournal.php b/app/Support/Binder/UnfinishedJournal.php deleted file mode 100644 index b255144d8a..0000000000 --- a/app/Support/Binder/UnfinishedJournal.php +++ /dev/null @@ -1,55 +0,0 @@ -. - */ -declare(strict_types=1); - -namespace FireflyIII\Support\Binder; - -use FireflyIII\Models\TransactionJournal; -use Illuminate\Routing\Route; -use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; - -/** - * Class Date. - */ -class UnfinishedJournal implements BinderInterface -{ - /** - * @param string $value - * @param Route $route - * - * @return TransactionJournal - * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException - */ - public static function routeBinder(string $value, Route $route): TransactionJournal - { - if (auth()->check()) { - $journal = auth()->user()->transactionJournals()->where('transaction_journals.id', $value) - ->leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id') - ->where('completed', 0) - ->where('user_id', auth()->user()->id)->first(['transaction_journals.*']); - if (null !== $journal) { - return $journal; - } - } - - throw new NotFoundHttpException; - } -} diff --git a/app/Support/CacheProperties.php b/app/Support/CacheProperties.php index c2fb3a179a..b25a4f520e 100644 --- a/app/Support/CacheProperties.php +++ b/app/Support/CacheProperties.php @@ -27,6 +27,7 @@ use Illuminate\Support\Collection; /** * Class CacheProperties. + * @codeCoverageIgnore */ class CacheProperties { diff --git a/app/Support/ChartColour.php b/app/Support/ChartColour.php index f64ea93eb5..8b1c4d1d47 100644 --- a/app/Support/ChartColour.php +++ b/app/Support/ChartColour.php @@ -24,6 +24,7 @@ namespace FireflyIII\Support; /** * Class ChartColour. + * @codeCoverageIgnore */ class ChartColour { @@ -58,7 +59,7 @@ class ChartColour */ public static function getColour(int $index): string { - $index %= \count(self::$colours); + $index %= count(self::$colours); $row = self::$colours[$index]; return sprintf('rgba(%d, %d, %d, 0.7)', $row[0], $row[1], $row[2]); diff --git a/app/Support/Cronjobs/AbstractCronjob.php b/app/Support/Cronjobs/AbstractCronjob.php index bfd6904908..37d2fbd0a8 100644 --- a/app/Support/Cronjobs/AbstractCronjob.php +++ b/app/Support/Cronjobs/AbstractCronjob.php @@ -25,6 +25,7 @@ namespace FireflyIII\Support\Cronjobs; /** * Class AbstractCronjob + * @codeCoverageIgnore */ abstract class AbstractCronjob { diff --git a/app/Support/Cronjobs/RecurringCronjob.php b/app/Support/Cronjobs/RecurringCronjob.php index 9dea5b326b..863a9670fc 100644 --- a/app/Support/Cronjobs/RecurringCronjob.php +++ b/app/Support/Cronjobs/RecurringCronjob.php @@ -34,6 +34,37 @@ use Log; */ class RecurringCronjob extends AbstractCronjob { + /** @var bool */ + private $force; + + /** @var Carbon */ + private $date; + + /** + * RecurringCronjob constructor. + * @throws \Exception + */ + public function __construct() + { + $this->force = false; + $this->date = new Carbon; + } + + /** + * @param bool $force + */ + public function setForce(bool $force): void + { + $this->force = $force; + } + + /** + * @param Carbon $date + */ + public function setDate(Carbon $date): void + { + $this->date = $date; + } /** * @return bool @@ -47,38 +78,46 @@ class RecurringCronjob extends AbstractCronjob $diff = time() - $lastTime; $diffForHumans = Carbon::now()->diffForHumans(Carbon::createFromTimestamp($lastTime), true); if (0 === $lastTime) { - Log::info('Recurring transactions cronjob has never fired before.'); + Log::info('Recurring transactions cron-job has never fired before.'); } // less than half a day ago: if ($lastTime > 0 && $diff <= 43200) { - Log::info(sprintf('It has been %s since the recurring transactions cronjob has fired. It will not fire now.', $diffForHumans)); + Log::info(sprintf('It has been %s since the recurring transactions cron-job has fired.', $diffForHumans)); + if (false === $this->force) { + Log::info('The cron-job will not fire now.'); - return false; + return false; + } + + // fire job regardless. + if (true === $this->force) { + Log::info('Execution of the recurring transaction cron-job has been FORCED.'); + } } if ($lastTime > 0 && $diff > 43200) { - Log::info(sprintf('It has been %s since the recurring transactions cronjob has fired. It will fire now!', $diffForHumans)); + Log::info(sprintf('It has been %s since the recurring transactions cron-job has fired. It will fire now!', $diffForHumans)); } - try { - $this->fireRecurring(); - } catch (FireflyException $e) { - Log::error($e->getMessage()); - Log::error($e->getTraceAsString()); - throw new FireflyException(sprintf('Could not run recurring transaction cron job: %s', $e->getMessage())); - } + $this->fireRecurring(); + + app('preferences')->mark(); return true; } /** * - * @throws FireflyException */ private function fireRecurring(): void { - $job = new CreateRecurringTransactions(new Carbon); + Log::info(sprintf('Will now fire recurring cron job task for date "%s".', $this->date->format('Y-m-d'))); + /** @var CreateRecurringTransactions $job */ + $job = app(CreateRecurringTransactions::class); + $job->setDate($this->date); + $job->setForce($this->force); $job->handle(); - app('fireflyconfig')->set('last_rt_job', time()); + app('fireflyconfig')->set('last_rt_job', (int)$this->date->format('U')); + Log::info('Done with recurring cron job task.'); } } diff --git a/app/Support/Domain.php b/app/Support/Domain.php index 6ccc9a07f3..3cc6f62acd 100644 --- a/app/Support/Domain.php +++ b/app/Support/Domain.php @@ -24,6 +24,7 @@ namespace FireflyIII\Support; /** * Class Domain. + * @codeCoverageIgnore */ class Domain { diff --git a/app/Support/ExpandedForm.php b/app/Support/ExpandedForm.php index ce40e55925..603127fe04 100644 --- a/app/Support/ExpandedForm.php +++ b/app/Support/ExpandedForm.php @@ -48,13 +48,14 @@ use Throwable; * * @SuppressWarnings(PHPMD.TooManyMethods) * @SuppressWarnings(PHPMD.TooManyPublicMethods) + * @codeCoverageIgnore */ class ExpandedForm { /** * @param string $name - * @param mixed $value - * @param array $options + * @param mixed $value + * @param array $options * * @return string */ @@ -75,7 +76,7 @@ class ExpandedForm $balance = app('steam')->balance($account, new Carbon); $currencyId = (int)$repository->getMetaValue($account, 'currency_id'); $currency = $currencyRepos->findNull($currencyId); - $role = $repository->getMetaValue($account, 'accountRole'); + $role = $repository->getMetaValue($account, 'account_role'); if ('' === $role) { $role = 'no_account_type'; // @codeCoverageIgnore } @@ -93,8 +94,8 @@ class ExpandedForm /** * @param string $name - * @param mixed $value - * @param array $options + * @param mixed $value + * @param array $options * * @return string */ @@ -102,10 +103,7 @@ class ExpandedForm { // make repositories /** @var AccountRepositoryInterface $repository */ - $repository = app(AccountRepositoryInterface::class); - /** @var CurrencyRepositoryInterface $currencyRepos */ - $currencyRepos = app(CurrencyRepositoryInterface::class); - + $repository = app(AccountRepositoryInterface::class); $accountList = $repository->getActiveAccountsByType( [AccountType::ASSET, AccountType::DEFAULT, AccountType::MORTGAGE, AccountType::DEBT, AccountType::CREDITCARD, AccountType::LOAN,] ); @@ -113,17 +111,78 @@ class ExpandedForm $defaultCurrency = app('amount')->getDefaultCurrency(); $grouped = []; // group accounts: + /** @var Account $account */ foreach ($accountList as $account) { - $balance = app('steam')->balance($account, new Carbon); - $currencyId = (int)$repository->getMetaValue($account, 'currency_id'); - $currency = $currencyRepos->findNull($currencyId); - $role = $repository->getMetaValue($account, 'accountRole'); - if ('' === $role && !\in_array($account->accountType->type, $liabilityTypes, true)) { + $balance = app('steam')->balance($account, new Carbon); + + $currency = $repository->getAccountCurrency($account) ?? $defaultCurrency; + + $role = $repository->getMetaValue($account, 'account_role'); + + if ('' === $role && !in_array($account->accountType->type, $liabilityTypes, true)) { $role = 'no_account_type'; // @codeCoverageIgnore } - if (\in_array($account->accountType->type, $liabilityTypes, true)) { + if (in_array($account->accountType->type, $liabilityTypes, true)) { + $role = 'l_' . $account->accountType->type; // @codeCoverageIgnore + } + $key = (string)trans('firefly.opt_group_' . $role); + $grouped[$key][$account->id] = $account->name . ' (' . app('amount')->formatAnything($currency, $balance, false) . ')'; + } + + + return $this->select($name, $grouped, $value, $options); + } + + /** + * Grouped dropdown list of all accounts that are valid as the destination of a withdrawal. + * + * @param string $name + * @param mixed $value + * @param array $options + * + * @return string + */ + public function activeWithdrawalDestinations(string $name, $value = null, array $options = null): string + { + // make repositories + /** @var AccountRepositoryInterface $repository */ + $repository = app(AccountRepositoryInterface::class); + + $accountList = $repository->getActiveAccountsByType( + [ + AccountType::MORTGAGE, + AccountType::DEBT, + AccountType::CREDITCARD, + AccountType::LOAN, + AccountType::EXPENSE, + ] + ); + $liabilityTypes = [AccountType::MORTGAGE, AccountType::DEBT, AccountType::CREDITCARD, AccountType::LOAN]; + $defaultCurrency = app('amount')->getDefaultCurrency(); + $grouped = []; + + // add cash account first: + $cash = $repository->getCashAccount(); + $key = (string)trans('firefly.cash_account_type'); + $grouped[$key][$cash->id] = sprintf('(%s)', (string)trans('firefly.cash')); + + // group accounts: + /** @var Account $account */ + foreach ($accountList as $account) { + $balance = app('steam')->balance($account, new Carbon); + $currency = $repository->getAccountCurrency($account) ?? $defaultCurrency; + $role = (string)$repository->getMetaValue($account, 'account_role'); + if ('' === $role && !in_array($account->accountType->type, $liabilityTypes, true)) { + $role = 'no_account_type'; // @codeCoverageIgnore + } + if ('no_account_type' === $role && AccountType::EXPENSE === $account->accountType->type) { + $role = 'expense_account'; // @codeCoverageIgnore + + } + + if (in_array($account->accountType->type, $liabilityTypes, true)) { $role = 'l_' . $account->accountType->type; // @codeCoverageIgnore } @@ -139,12 +198,69 @@ class ExpandedForm } /** + * Grouped dropdown list of all accounts that are valid as the destination of a withdrawal. + * * @param string $name - * @param mixed $value - * @param array $options + * @param mixed $value + * @param array $options + * + * @return string + */ + public function activeDepositDestinations(string $name, $value = null, array $options = null): string + { + // make repositories + /** @var AccountRepositoryInterface $repository */ + $repository = app(AccountRepositoryInterface::class); + + $accountList = $repository->getActiveAccountsByType( + [ + AccountType::MORTGAGE, + AccountType::DEBT, + AccountType::CREDITCARD, + AccountType::LOAN, + AccountType::REVENUE, + ] + ); + $liabilityTypes = [AccountType::MORTGAGE, AccountType::DEBT, AccountType::CREDITCARD, AccountType::LOAN]; + $defaultCurrency = app('amount')->getDefaultCurrency(); + $grouped = []; + + // add cash account first: + $cash = $repository->getCashAccount(); + $key = (string)trans('firefly.cash_account_type'); + $grouped[$key][$cash->id] = sprintf('(%s)', (string)trans('firefly.cash')); + + // group accounts: + /** @var Account $account */ + foreach ($accountList as $account) { + $balance = app('steam')->balance($account, new Carbon); + $currency = $repository->getAccountCurrency($account) ?? $defaultCurrency; + $role = (string)$repository->getMetaValue($account, 'account_role'); + if ('' === $role && !in_array($account->accountType->type, $liabilityTypes, true)) { + $role = 'no_account_type'; // @codeCoverageIgnore + } + if ('no_account_type' === $role && AccountType::REVENUE === $account->accountType->type) { + $role = 'revenue_account'; // @codeCoverageIgnore + + } + + if (in_array($account->accountType->type, $liabilityTypes, true)) { + $role = 'l_' . $account->accountType->type; // @codeCoverageIgnore + } + + $key = (string)trans('firefly.opt_group_' . $role); + $grouped[$key][$account->id] = $account->name . ' (' . app('amount')->formatAnything($currency, $balance, false) . ')'; + } + + return $this->select($name, $grouped, $value, $options); + } + + /** + * @param string $name + * @param mixed $value + * @param array $options * * @return string - * @throws FireflyException */ public function amount(string $name, $value = null, array $options = null): string { @@ -153,8 +269,8 @@ class ExpandedForm /** * @param string $name - * @param mixed $value - * @param array $options + * @param mixed $value + * @param array $options * * @return string * @@ -185,7 +301,7 @@ class ExpandedForm /** * @param string $name - * @param array $options + * @param array $options * * @return string * @@ -206,7 +322,7 @@ class ExpandedForm // group accounts: /** @var Account $account */ foreach ($assetAccounts as $account) { - $role = $repository->getMetaValue($account, 'accountRole'); + $role = $repository->getMetaValue($account, 'account_role'); if (null === $role) { $role = 'no_account_type'; // @codeCoverageIgnore } @@ -227,8 +343,8 @@ class ExpandedForm /** * @param string $name - * @param mixed $value - * @param array $options + * @param mixed $value + * @param array $options * * @return string */ @@ -249,7 +365,7 @@ class ExpandedForm $balance = app('steam')->balance($account, new Carbon); $currencyId = (int)$repository->getMetaValue($account, 'currency_id'); $currency = $currencyRepos->findNull($currencyId); - $role = (string)$repository->getMetaValue($account, 'accountRole'); + $role = (string)$repository->getMetaValue($account, 'account_role'); if ('' === $role) { $role = 'no_account_type'; // @codeCoverageIgnore } @@ -267,8 +383,8 @@ class ExpandedForm /** * @param string $name - * @param mixed $value - * @param array $options + * @param mixed $value + * @param array $options * * @return string * @throws FireflyException @@ -280,8 +396,8 @@ class ExpandedForm /** * @param string $name - * @param mixed $value - * @param array $options + * @param mixed $value + * @param array $options * * @return string * @throws FireflyException @@ -293,9 +409,9 @@ class ExpandedForm /** * @param string $name - * @param int $value - * @param mixed $checked - * @param array $options + * @param int $value + * @param mixed $checked + * @param array $options * * @return string * @@ -331,8 +447,8 @@ class ExpandedForm /** * @param string $name - * @param mixed $value - * @param array $options + * @param mixed $value + * @param array $options * * @return string */ @@ -354,8 +470,8 @@ class ExpandedForm /** * @param string $name - * @param mixed $value - * @param array $options + * @param mixed $value + * @param array $options * * @return string */ @@ -379,8 +495,8 @@ class ExpandedForm /** * @param string $name - * @param mixed $value - * @param array $options + * @param mixed $value + * @param array $options * * @return string * @@ -404,7 +520,7 @@ class ExpandedForm /** * @param string $name - * @param array $options + * @param array $options * * @return string * @@ -427,8 +543,8 @@ class ExpandedForm /** * @param string $name - * @param mixed $value - * @param array $options + * @param mixed $value + * @param array $options * * @return string * @@ -453,8 +569,8 @@ class ExpandedForm /** * @param string $name - * @param mixed $value - * @param array $options + * @param mixed $value + * @param array $options * * @return string * @@ -478,8 +594,8 @@ class ExpandedForm /** * @param string $name - * @param mixed $value - * @param array $options + * @param mixed $value + * @param array $options * * @return string */ @@ -503,12 +619,12 @@ class ExpandedForm $balance = app('steam')->balance($account, new Carbon); $currencyId = (int)$repository->getMetaValue($account, 'currency_id'); $currency = $currencyRepos->findNull($currencyId); - $role = (string)$repository->getMetaValue($account, 'accountRole'); + $role = (string)$repository->getMetaValue($account, 'account_role'); // TODO bad form for currency if ('' === $role) { $role = 'no_account_type'; // @codeCoverageIgnore } - if (\in_array($account->accountType->type, $liabilityTypes, true)) { + if (in_array($account->accountType->type, $liabilityTypes, true)) { $role = 'l_' . $account->accountType->type; // @codeCoverageIgnore } @@ -580,8 +696,8 @@ class ExpandedForm /** * @param string $name - * @param mixed $value - * @param array $options + * @param mixed $value + * @param array $options * * @return string */ @@ -611,8 +727,8 @@ class ExpandedForm /** * @param string $name - * @param mixed $value - * @param array $options + * @param mixed $value + * @param array $options * * @return string * @@ -656,7 +772,7 @@ class ExpandedForm /** * @param string $name - * @param array $options + * @param array $options * * @return string * @@ -681,8 +797,8 @@ class ExpandedForm * Function to render a percentage. * * @param string $name - * @param mixed $value - * @param array $options + * @param mixed $value + * @param array $options * * @return string * @@ -707,8 +823,8 @@ class ExpandedForm /** * @param string $name - * @param mixed $value - * @param array $options + * @param mixed $value + * @param array $options * * @return string */ @@ -732,8 +848,8 @@ class ExpandedForm /** * @param string $name - * @param mixed $value - * @param array $options + * @param mixed $value + * @param array $options * * @return string */ @@ -754,8 +870,8 @@ class ExpandedForm } /** - * @param string $name - * @param null $value + * @param string $name + * @param null $value * @param array|null $options * * @return HtmlString @@ -785,9 +901,9 @@ class ExpandedForm /** @noinspection MoreThanThreeArgumentsInspection */ /** * @param string $name - * @param array $list - * @param mixed $selected - * @param array $options + * @param array $list + * @param mixed $selected + * @param array $options * * @return string */ @@ -811,8 +927,8 @@ class ExpandedForm /** * @param string $name - * @param mixed $value - * @param array $options + * @param mixed $value + * @param array $options * * @return string * @@ -834,8 +950,8 @@ class ExpandedForm /** * @param string $name - * @param mixed $value - * @param array $options + * @param mixed $value + * @param array $options * * @return string * @@ -859,8 +975,8 @@ class ExpandedForm /** * @param string $name - * @param mixed $value - * @param array $options + * @param mixed $value + * @param array $options * * @return string * @@ -883,8 +999,8 @@ class ExpandedForm /** * @param string $name - * @param mixed $value - * @param array $options + * @param mixed $value + * @param array $options * * @return string * @@ -916,8 +1032,8 @@ class ExpandedForm /** * @param string $name * @param string $view - * @param mixed $value - * @param array $options + * @param mixed $value + * @param array $options * * @return string * @SuppressWarnings(PHPMD.CyclomaticComplexity) @@ -969,8 +1085,8 @@ class ExpandedForm /** * @param string $name * @param string $view - * @param mixed $value - * @param array $options + * @param mixed $value + * @param array $options * * @return string * @SuppressWarnings(PHPMD.CyclomaticComplexity) diff --git a/app/Support/Facades/Preferences.php b/app/Support/Facades/Preferences.php index 3686144b5f..92887c269e 100644 --- a/app/Support/Facades/Preferences.php +++ b/app/Support/Facades/Preferences.php @@ -26,7 +26,7 @@ use FireflyIII\Models\Preference; use FireflyIII\User; use Illuminate\Support\Collection; use Illuminate\Support\Facades\Facade; - +use Log; /** * @codeCoverageIgnore * Class Preferences. @@ -44,6 +44,11 @@ use Illuminate\Support\Facades\Facade; */ class Preferences extends Facade { + public function __construct() + { + Log::warning('Hi there'); + } + /** * Get the registered name of the component. * diff --git a/app/Support/Facades/Steam.php b/app/Support/Facades/Steam.php index e598027bf5..1de7ed92f1 100644 --- a/app/Support/Facades/Steam.php +++ b/app/Support/Facades/Steam.php @@ -40,6 +40,7 @@ use Illuminate\Support\Facades\Facade; * @method string|null opposite(string $amount = null) * @method int phpBytes(string $string) * @method string positive(string $amount) + * @method array balancesPerCurrencyByAccounts(Collection $accounts, Carbon $date) * * @codeCoverageIgnore */ diff --git a/app/Support/FinTS/FinTS.php b/app/Support/FinTS/FinTS.php index 456a9f9b07..dff4c57b53 100644 --- a/app/Support/FinTS/FinTS.php +++ b/app/Support/FinTS/FinTS.php @@ -27,7 +27,7 @@ use FireflyIII\Exceptions\FireflyException; use Illuminate\Support\Facades\Crypt; /** - * + * @codeCoverageIgnore * Class FinTS */ class FinTS diff --git a/app/Support/FireflyConfig.php b/app/Support/FireflyConfig.php index debd591f77..11a51616d5 100644 --- a/app/Support/FireflyConfig.php +++ b/app/Support/FireflyConfig.php @@ -29,14 +29,19 @@ use Log; /** * Class FireflyConfig. + * @codeCoverageIgnore */ class FireflyConfig { + /** * @param string $name */ public function delete(string $name): void { + if ('testing' === config('app.env')) { + Log::warning(sprintf('%s should NOT be called in the TEST environment!', __METHOD__)); + } $fullName = 'ff-config-' . $name; if (Cache::has($fullName)) { Cache::forget($fullName); @@ -51,12 +56,15 @@ class FireflyConfig /** * @param string $name - * @param mixed $default + * @param mixed $default * * @return \FireflyIII\Models\Configuration|null */ public function get(string $name, $default = null): ?Configuration { + if ('testing' === config('app.env')) { + Log::warning(sprintf('%s should NOT be called in the TEST environment!', __METHOD__)); + } $fullName = 'ff-config-' . $name; if (Cache::has($fullName)) { return Cache::get($fullName); @@ -79,12 +87,15 @@ class FireflyConfig /** * @param string $name - * @param mixed $default + * @param mixed $default * * @return \FireflyIII\Models\Configuration|null */ public function getFresh(string $name, $default = null): ?Configuration { + if ('testing' === config('app.env')) { + Log::warning(sprintf('%s should NOT be called in the TEST environment!', __METHOD__)); + } $config = Configuration::where('name', $name)->first(['id', 'name', 'data']); if ($config) { @@ -106,6 +117,10 @@ class FireflyConfig */ public function put(string $name, $value): Configuration { + if ('testing' === config('app.env')) { + Log::warning(sprintf('%s should NOT be called in the TEST environment!', __METHOD__)); + } + return $this->set($name, $value); } @@ -117,6 +132,9 @@ class FireflyConfig */ public function set(string $name, $value): Configuration { + if ('testing' === config('app.env')) { + Log::warning(sprintf('%s should NOT be called in the TEST environment!', __METHOD__)); + } Log::debug('Set new value for ', ['name' => $name]); /** @var Configuration $config */ $config = Configuration::whereName($name)->first(); diff --git a/app/Support/Http/Api/AccountFilter.php b/app/Support/Http/Api/AccountFilter.php index f9594baf29..70fc6da847 100644 --- a/app/Support/Http/Api/AccountFilter.php +++ b/app/Support/Http/Api/AccountFilter.php @@ -27,6 +27,7 @@ use FireflyIII\Models\AccountType; /** * Trait AccountFilter + * @codeCoverageIgnore */ trait AccountFilter { @@ -78,10 +79,8 @@ trait AccountFilter 'credit-card' => [AccountType::CREDITCARD], 'creditcard' => [AccountType::CREDITCARD], 'cc' => [AccountType::CREDITCARD], - ]; - $return = $types[$type] ?? $types['all']; - return $return; // @codeCoverageIgnore + return $types[$type] ?? $types['all']; } } diff --git a/app/Support/Http/Api/ApiSupport.php b/app/Support/Http/Api/ApiSupport.php new file mode 100644 index 0000000000..544a5fb22a --- /dev/null +++ b/app/Support/Http/Api/ApiSupport.php @@ -0,0 +1,67 @@ +. + */ + +namespace FireflyIII\Support\Http\Api; + +use FireflyIII\Models\Account; +use Illuminate\Support\Collection; + +/** + * Trait ApiSupport + * @codeCoverageIgnore + */ +trait ApiSupport +{ + /** + * Small helper function for the revenue and expense account charts. + * + * @param array $names + * + * @return array + */ + protected function expandNames(array $names): array + { + $result = []; + foreach ($names as $entry) { + $result[$entry['name']] = 0; + } + + return $result; + } + + /** + * Small helper function for the revenue and expense account charts. + * + * @param Collection $accounts + * + * @return array + */ + protected function extractNames(Collection $accounts): array + { + $return = []; + /** @var Account $account */ + foreach ($accounts as $account) { + $return[$account->id] = $account->name; + } + + return $return; + } +} \ No newline at end of file diff --git a/app/Support/Http/Api/TransactionFilter.php b/app/Support/Http/Api/TransactionFilter.php index 109611c416..07147161da 100644 --- a/app/Support/Http/Api/TransactionFilter.php +++ b/app/Support/Http/Api/TransactionFilter.php @@ -27,6 +27,7 @@ use FireflyIII\Models\TransactionType; /** * Trait TransactionFilter + * @codeCoverageIgnore */ trait TransactionFilter { @@ -58,9 +59,8 @@ trait TransactionFilter 'specials' => [TransactionType::OPENING_BALANCE, TransactionType::RECONCILIATION,], 'default' => [TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::TRANSFER,], ]; - $return = $types[$type] ?? $types['default']; + return $types[$type] ?? $types['default']; - return $return; } } diff --git a/app/Support/Http/Controllers/AugumentData.php b/app/Support/Http/Controllers/AugumentData.php index 29ca5dd501..dc5b7d912d 100644 --- a/app/Support/Http/Controllers/AugumentData.php +++ b/app/Support/Http/Controllers/AugumentData.php @@ -24,13 +24,11 @@ declare(strict_types=1); namespace FireflyIII\Support\Http\Controllers; use Carbon\Carbon; -use FireflyIII\Helpers\Collector\TransactionCollectorInterface; +use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Models\Account; use FireflyIII\Models\AccountType; use FireflyIII\Models\Budget; use FireflyIII\Models\BudgetLimit; -use FireflyIII\Models\Tag; -use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionType; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; @@ -44,8 +42,6 @@ use Illuminate\Support\Collection; */ trait AugumentData { - - /** * Searches for the opposing account. * @@ -78,8 +74,8 @@ trait AugumentData * * @param Collection $assets * @param Collection $opposing - * @param Carbon $start - * @param Carbon $end + * @param Carbon $start + * @param Carbon $end * * @return array * @@ -88,22 +84,19 @@ trait AugumentData */ protected function earnedByCategory(Collection $assets, Collection $opposing, Carbon $start, Carbon $end): array // get data + augment with info { - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setRange($start, $end)->setTypes([TransactionType::DEPOSIT])->setAccounts($assets); - $collector->setOpposingAccounts($opposing)->withCategoryInformation(); - $set = $collector->getTransactions(); - $sum = []; + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + + $total = $assets->merge($opposing); + $collector->setRange($start, $end)->setTypes([TransactionType::DEPOSIT])->setAccounts($total); + $collector->withCategoryInformation(); + $journals = $collector->getExtractedJournals(); + $sum = []; // loop to support multi currency - foreach ($set as $transaction) { - $currencyId = $transaction->transaction_currency_id; - $categoryName = $transaction->transaction_category_name; - $categoryId = (int)$transaction->transaction_category_id; - // if null, grab from journal: - if (0 === $categoryId) { - $categoryName = $transaction->transaction_journal_category_name; - $categoryId = (int)$transaction->transaction_journal_category_id; - } + foreach ($journals as $journal) { + $currencyId = $journal['currency_id']; + $categoryName = $journal['category_name']; + $categoryId = (int)$journal['category_id']; // if not set, set to zero: if (!isset($sum[$categoryId][$currencyId])) { @@ -118,8 +111,8 @@ trait AugumentData 'name' => $categoryName, ], 'currency' => [ - 'symbol' => $transaction->transaction_currency_symbol, - 'dp' => $transaction->transaction_currency_dp, + 'symbol' => $journal['currency_symbol'], + 'dp' => $journal['currency_decimal_places'], ], ], ], @@ -128,9 +121,9 @@ trait AugumentData // add amount $sum[$categoryId]['per_currency'][$currencyId]['sum'] = bcadd( - $sum[$categoryId]['per_currency'][$currencyId]['sum'], $transaction->transaction_amount + $sum[$categoryId]['per_currency'][$currencyId]['sum'], $journal['amount'] ); - $sum[$categoryId]['grand_total'] = bcadd($sum[$categoryId]['grand_total'], $transaction->transaction_amount); + $sum[$categoryId]['grand_total'] = bcadd($sum[$categoryId]['grand_total'], $journal['amount']); } return $sum; @@ -141,40 +134,41 @@ trait AugumentData * * @param Collection $assets * @param Collection $opposing - * @param Carbon $start - * @param Carbon $end + * @param Carbon $start + * @param Carbon $end * * @return array */ protected function earnedInPeriod(Collection $assets, Collection $opposing, Carbon $start, Carbon $end): array // get data + augment with info { - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setRange($start, $end)->setTypes([TransactionType::DEPOSIT])->setAccounts($assets); - $collector->setOpposingAccounts($opposing); - $set = $collector->getTransactions(); - $sum = [ + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + + $total = $assets->merge($opposing); + $collector->setRange($start, $end)->setTypes([TransactionType::DEPOSIT])->setAccounts($total); + $journals = $collector->getExtractedJournals(); + $sum = [ 'grand_sum' => '0', 'per_currency' => [], ]; // loop to support multi currency - foreach ($set as $transaction) { - $currencyId = $transaction->transaction_currency_id; + foreach ($journals as $journal) { + $currencyId = (int)$journal['currency_id']; // if not set, set to zero: if (!isset($sum['per_currency'][$currencyId])) { $sum['per_currency'][$currencyId] = [ 'sum' => '0', 'currency' => [ - 'symbol' => $transaction->transaction_currency_symbol, - 'dp' => $transaction->transaction_currency_dp, + 'symbol' => $journal['currency_symbol'], + 'decimal_places' => $journal['currency_decimal_places'], ], ]; } // add amount - $sum['per_currency'][$currencyId]['sum'] = bcadd($sum['per_currency'][$currencyId]['sum'], $transaction->transaction_amount); - $sum['grand_sum'] = bcadd($sum['grand_sum'], $transaction->transaction_amount); + $sum['per_currency'][$currencyId]['sum'] = bcadd($sum['per_currency'][$currencyId]['sum'], $journal['amount']); + $sum['grand_sum'] = bcadd($sum['grand_sum'], $journal['amount']); } return $sum; @@ -219,16 +213,16 @@ trait AugumentData * Returns the budget limits belonging to the given budget and valid on the given day. * * @param Collection $budgetLimits - * @param Budget $budget - * @param Carbon $start - * @param Carbon $end + * @param Budget $budget + * @param Carbon $start + * @param Carbon $end * * @return Collection */ protected function filterBudgetLimits(Collection $budgetLimits, Budget $budget, Carbon $start, Carbon $end): Collection // filter data { $set = $budgetLimits->filter( - function (BudgetLimit $budgetLimit) use ($budget, $start, $end) { + static function (BudgetLimit $budgetLimit) use ($budget, $start, $end) { if ($budgetLimit->budget_id === $budget->id && $budgetLimit->start_date->lte($start) // start of budget limit is on or before start && $budgetLimit->end_date->gte($end) // end of budget limit is on or after end @@ -352,9 +346,9 @@ trait AugumentData * Get the expenses for a budget in a date range. * * @param Collection $limits - * @param Budget $budget - * @param Carbon $start - * @param Carbon $end + * @param Budget $budget + * @param Carbon $start + * @param Carbon $end * * @return array * @@ -388,316 +382,6 @@ trait AugumentData return $return; } - /** - * Gets all budget limits for a budget. - * - * @param Budget $budget - * @param Carbon $start - * @param Carbon $end - * - * @return Collection - */ - protected function getLimits(Budget $budget, Carbon $start, Carbon $end): Collection // get data + augment with info - { - /** @var BudgetRepositoryInterface $repository */ - $repository = app(BudgetRepositoryInterface::class); - // properties for cache - $cache = new CacheProperties; - $cache->addProperty($start); - $cache->addProperty($end); - $cache->addProperty($budget->id); - $cache->addProperty('get-limits'); - - if ($cache->has()) { - return $cache->get(); // @codeCoverageIgnore - } - - $set = $repository->getBudgetLimits($budget, $start, $end); - $limits = new Collection(); - - /** @var BudgetLimit $entry */ - foreach ($set as $entry) { - $entry->spent = $repository->spentInPeriod(new Collection([$budget]), new Collection(), $entry->start_date, $entry->end_date); - $limits->push($entry); - } - $cache->store($limits); - - return $set; - } - - - /** - * Helper function that groups expenses. - * - * @param Collection $set - * - * @return array - */ - protected function groupByBudget(Collection $set): array // filter + group data - { - // group by category ID: - $grouped = []; - /** @var Transaction $transaction */ - foreach ($set as $transaction) { - $jrnlBudId = (int)$transaction->transaction_journal_budget_id; - $transBudId = (int)$transaction->transaction_budget_id; - $budgetId = max($jrnlBudId, $transBudId); - $grouped[$budgetId] = $grouped[$budgetId] ?? '0'; - $grouped[$budgetId] = bcadd($transaction->transaction_amount, $grouped[$budgetId]); - } - - return $grouped; - } - - /** - * Group transactions by category. - * - * @param Collection $set - * - * @return array - */ - protected function groupByCategory(Collection $set): array // filter + group data - { - // group by category ID: - $grouped = []; - /** @var Transaction $transaction */ - foreach ($set as $transaction) { - $jrnlCatId = (int)$transaction->transaction_journal_category_id; - $transCatId = (int)$transaction->transaction_category_id; - $categoryId = max($jrnlCatId, $transCatId); - $grouped[$categoryId] = $grouped[$categoryId] ?? '0'; - $grouped[$categoryId] = bcadd($transaction->transaction_amount, $grouped[$categoryId]); - } - - return $grouped; - } - - /** - * Group set of transactions by name of opposing account. - * - * @param Collection $set - * - * @return array - */ - protected function groupByName(Collection $set): array // filter + group data - { - // group by opposing account name. - $grouped = []; - /** @var Transaction $transaction */ - foreach ($set as $transaction) { - $name = $transaction->opposing_account_name; - $grouped[$name] = $grouped[$name] ?? '0'; - $grouped[$name] = bcadd($transaction->transaction_amount, $grouped[$name]); - } - - return $grouped; - } - - /** - * Group transactions by tag. - * - * @param Collection $set - * - * @return array - */ - protected function groupByTag(Collection $set): array // filter + group data - { - // group by category ID: - $grouped = []; - /** @var Transaction $transaction */ - foreach ($set as $transaction) { - $journal = $transaction->transactionJournal; - $journalTags = $journal->tags; - /** @var Tag $journalTag */ - foreach ($journalTags as $journalTag) { - $journalTagId = $journalTag->id; - $grouped[$journalTagId] = $grouped[$journalTagId] ?? '0'; - $grouped[$journalTagId] = bcadd($transaction->transaction_amount, $grouped[$journalTagId]); - } - } - - return $grouped; - } - - - - /** @noinspection MoreThanThreeArgumentsInspection */ - - /** - * Spent by budget. - * - * @param Collection $assets - * @param Collection $opposing - * @param Carbon $start - * @param Carbon $end - * - * @return array - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - */ - protected function spentByBudget(Collection $assets, Collection $opposing, Carbon $start, Carbon $end): array // get data + augment with info - { - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])->setAccounts($assets); - $collector->setOpposingAccounts($opposing)->withBudgetInformation(); - $set = $collector->getTransactions(); - $sum = []; - // loop to support multi currency - foreach ($set as $transaction) { - $currencyId = $transaction->transaction_currency_id; - $budgetName = $transaction->transaction_budget_name; - $budgetId = (int)$transaction->transaction_budget_id; - // if null, grab from journal: - if (0 === $budgetId) { - $budgetName = $transaction->transaction_journal_budget_name; - $budgetId = (int)$transaction->transaction_journal_budget_id; - } - - // if not set, set to zero: - if (!isset($sum[$budgetId][$currencyId])) { - $sum[$budgetId] = [ - 'grand_total' => '0', - 'name' => $budgetName, - 'per_currency' => [ - $currencyId => [ - 'sum' => '0', - 'budget' => [ - 'id' => $budgetId, - 'name' => $budgetName, - ], - 'currency' => [ - 'symbol' => $transaction->transaction_currency_symbol, - 'dp' => $transaction->transaction_currency_dp, - ], - ], - ], - ]; - } - - // add amount - $sum[$budgetId]['per_currency'][$currencyId]['sum'] = bcadd( - $sum[$budgetId]['per_currency'][$currencyId]['sum'], $transaction->transaction_amount - ); - $sum[$budgetId]['grand_total'] = bcadd($sum[$budgetId]['grand_total'], $transaction->transaction_amount); - } - - return $sum; - } - - /** @noinspection MoreThanThreeArgumentsInspection */ - - /** - * Spent by category. - * - * @param Collection $assets - * @param Collection $opposing - * @param Carbon $start - * @param Carbon $end - * - * @return array - * - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - */ - protected function spentByCategory(Collection $assets, Collection $opposing, Carbon $start, Carbon $end): array // get data + augment with info - { - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])->setAccounts($assets); - $collector->setOpposingAccounts($opposing)->withCategoryInformation(); - $set = $collector->getTransactions(); - $sum = []; - // loop to support multi currency - foreach ($set as $transaction) { - $currencyId = $transaction->transaction_currency_id; - $categoryName = $transaction->transaction_category_name; - $categoryId = (int)$transaction->transaction_category_id; - // if null, grab from journal: - if (0 === $categoryId) { - $categoryName = $transaction->transaction_journal_category_name; - $categoryId = (int)$transaction->transaction_journal_category_id; - } - - // if not set, set to zero: - if (!isset($sum[$categoryId][$currencyId])) { - $sum[$categoryId] = [ - 'grand_total' => '0', - 'name' => $categoryName, - 'per_currency' => [ - $currencyId => [ - 'sum' => '0', - 'category' => [ - 'id' => $categoryId, - 'name' => $categoryName, - ], - 'currency' => [ - 'symbol' => $transaction->transaction_currency_symbol, - 'dp' => $transaction->transaction_currency_dp, - ], - ], - ], - ]; - } - - // add amount - $sum[$categoryId]['per_currency'][$currencyId]['sum'] = bcadd( - $sum[$categoryId]['per_currency'][$currencyId]['sum'], $transaction->transaction_amount - ); - $sum[$categoryId]['grand_total'] = bcadd($sum[$categoryId]['grand_total'], $transaction->transaction_amount); - } - - return $sum; - } - - /** @noinspection MoreThanThreeArgumentsInspection */ - - /** - * Spent in a period. - * - * @param Collection $assets - * @param Collection $opposing - * @param Carbon $start - * @param Carbon $end - * - * @return array - */ - protected function spentInPeriod(Collection $assets, Collection $opposing, Carbon $start, Carbon $end): array // get data + augment with info - { - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])->setAccounts($assets); - $collector->setOpposingAccounts($opposing); - $set = $collector->getTransactions(); - $sum = [ - 'grand_sum' => '0', - 'per_currency' => [], - ]; - // loop to support multi currency - foreach ($set as $transaction) { - $currencyId = (int)$transaction->transaction_currency_id; - - // if not set, set to zero: - if (!isset($sum['per_currency'][$currencyId])) { - $sum['per_currency'][$currencyId] = [ - 'sum' => '0', - 'currency' => [ - 'symbol' => $transaction->transaction_currency_symbol, - 'dp' => $transaction->transaction_currency_dp, - ], - ]; - } - - // add amount - $sum['per_currency'][$currencyId]['sum'] = bcadd($sum['per_currency'][$currencyId]['sum'], $transaction->transaction_amount); - $sum['grand_sum'] = bcadd($sum['grand_sum'], $transaction->transaction_amount); - } - - return $sum; - } - - /** @noinspection MoreThanThreeArgumentsInspection */ - /** * * Returns an array with the following values: @@ -708,7 +392,7 @@ trait AugumentData * 'spent' => actually spent in period for budget * 1 => (etc) * - * @param Budget $budget + * @param Budget $budget * @param Collection $limits * * @return array @@ -755,6 +439,311 @@ trait AugumentData return $return; } + /** + * Gets all budget limits for a budget. + * + * @param Budget $budget + * @param Carbon $start + * @param Carbon $end + * + * @return Collection + */ + protected function getLimits(Budget $budget, Carbon $start, Carbon $end): Collection // get data + augment with info + { + /** @var BudgetRepositoryInterface $repository */ + $repository = app(BudgetRepositoryInterface::class); + // properties for cache + $cache = new CacheProperties; + $cache->addProperty($start); + $cache->addProperty($end); + $cache->addProperty($budget->id); + $cache->addProperty('get-limits'); + + if ($cache->has()) { + return $cache->get(); // @codeCoverageIgnore + } + + $set = $repository->getBudgetLimits($budget, $start, $end); + $limits = new Collection(); + + /** @var BudgetLimit $entry */ + foreach ($set as $entry) { + $entry->spent = $repository->spentInPeriod(new Collection([$budget]), new Collection(), $entry->start_date, $entry->end_date); + $limits->push($entry); + } + $cache->store($limits); + + return $set; + } + + /** + * Helper function that groups expenses. + * + * @param array $array + * + * @return array + */ + protected function groupByBudget(array $array): array // filter + group data + { + // group by category ID: + $grouped = []; + /** @var array $journal */ + foreach ($array as $journal) { + $budgetId = (int)$journal['budget_id']; + $grouped[$budgetId] = $grouped[$budgetId] ?? '0'; + $grouped[$budgetId] = bcadd($journal['amount'], $grouped[$budgetId]); + } + + return $grouped; + } + + /** + * Group transactions by category. + * + * @param array $array + * + * @return array + */ + protected function groupByCategory(array $array): array // filter + group data + { + // group by category ID: + $grouped = []; + /** @var array $journal */ + foreach ($array as $journal) { + $categoryId = (int)$journal['category_id']; + $grouped[$categoryId] = $grouped[$categoryId] ?? '0'; + $grouped[$categoryId] = bcadd($journal['amount'], $grouped[$categoryId]); + } + + return $grouped; + } + + /** + * Group set of transactions by name of opposing account. + * + * @param array $array + * + * @return array + */ + protected function groupByName(array $array): array // filter + group data + { + + // group by opposing account name. + $grouped = []; + /** @var array $journal */ + foreach ($array as $journal) { + $name = '(no name)'; + if (TransactionType::WITHDRAWAL === $journal['transaction_type_type']) { + $name = $journal['destination_account_name']; + } + if (TransactionType::WITHDRAWAL !== $journal['transaction_type_type']) { + $name = $journal['source_account_name']; + } + + $grouped[$name] = $grouped[$name] ?? '0'; + $grouped[$name] = bcadd($journal['amount'], $grouped[$name]); + } + + return $grouped; + } + + + /** + * Group transactions by tag. + * + * @param array $array + * + * @return array + */ + protected function groupByTag(array $array): array // filter + group data + { + // group by category ID: + $grouped = []; + /** @var array $journal */ + foreach ($array as $journal) { + $tags = $journal['tags'] ?? []; + /** + * @var int $id + * @var array $tag + */ + foreach ($tags as $id => $tag) { + $grouped[$id] = $grouped[$id] ?? '0'; + $grouped[$id] = bcadd($journal['amount'], $grouped[$id]); + } + } + + return $grouped; + } + + /** @noinspection MoreThanThreeArgumentsInspection */ + + /** + * Spent by budget. + * + * @param Collection $assets + * @param Collection $opposing + * @param Carbon $start + * @param Carbon $end + * + * @return array + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + protected function spentByBudget(Collection $assets, Collection $opposing, Carbon $start, Carbon $end): array // get data + augment with info + { + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $total = $assets->merge($opposing); + $collector->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])->setAccounts($total); + $collector->withBudgetInformation(); + $journals = $collector->getExtractedJournals(); + $sum = []; + // loop to support multi currency + foreach ($journals as $journal) { + $currencyId = $journal['currency_id']; + $budgetName = $journal['budget_name']; + $budgetId = (int)$journal['budget_id']; + + // if not set, set to zero: + if (!isset($sum[$budgetId][$currencyId])) { + $sum[$budgetId] = [ + 'grand_total' => '0', + 'name' => $budgetName, + 'per_currency' => [ + $currencyId => [ + 'sum' => '0', + 'budget' => [ + 'id' => $budgetId, + 'name' => $budgetName, + ], + 'currency' => [ + 'symbol' => $journal['currency_symbol'], + 'dp' => $journal['currency_decimal_places'], + ], + ], + ], + ]; + } + + // add amount + $sum[$budgetId]['per_currency'][$currencyId]['sum'] = bcadd( + $sum[$budgetId]['per_currency'][$currencyId]['sum'], $journal['amount'] + ); + $sum[$budgetId]['grand_total'] = bcadd($sum[$budgetId]['grand_total'], $journal['amount']); + } + + return $sum; + } + + /** @noinspection MoreThanThreeArgumentsInspection */ + + /** + * Spent by category. + * + * @param Collection $assets + * @param Collection $opposing + * @param Carbon $start + * @param Carbon $end + * + * @return array + * + * @SuppressWarnings(PHPMD.CyclomaticComplexity) + * @SuppressWarnings(PHPMD.ExcessiveMethodLength) + */ + protected function spentByCategory(Collection $assets, Collection $opposing, Carbon $start, Carbon $end): array // get data + augment with info + { + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $total = $assets->merge($opposing); + $collector->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])->setAccounts($total); + $collector->withCategoryInformation(); + $journals = $collector->getExtractedJournals(); + $sum = []; + // loop to support multi currency + foreach ($journals as $journal) { + $currencyId = (int)$journal['currency_id']; + $categoryName = $journal['category_name']; + $categoryId = (int)$journal['category_id']; + + // if not set, set to zero: + if (!isset($sum[$categoryId][$currencyId])) { + $sum[$categoryId] = [ + 'grand_total' => '0', + 'name' => $categoryName, + 'per_currency' => [ + $currencyId => [ + 'sum' => '0', + 'category' => [ + 'id' => $categoryId, + 'name' => $categoryName, + ], + 'currency' => [ + 'symbol' => $journal['currency_symbol'], + 'dp' => $journal['currency_decimal_places'], + ], + ], + ], + ]; + } + + // add amount + $sum[$categoryId]['per_currency'][$currencyId]['sum'] = bcadd( + $sum[$categoryId]['per_currency'][$currencyId]['sum'], $journal['amount'] + ); + $sum[$categoryId]['grand_total'] = bcadd($sum[$categoryId]['grand_total'], $journal['amount']); + } + + return $sum; + } + + /** @noinspection MoreThanThreeArgumentsInspection */ + + /** + * Spent in a period. + * + * @param Collection $assets + * @param Collection $opposing + * @param Carbon $start + * @param Carbon $end + * + * @return array + */ + protected function spentInPeriod(Collection $assets, Collection $opposing, Carbon $start, Carbon $end): array // get data + augment with info + { + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + + $total = $assets->merge($opposing); + $collector->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])->setAccounts($total); + $journals = $collector->getExtractedJournals(); + $sum = [ + 'grand_sum' => '0', + 'per_currency' => [], + ]; + // loop to support multi currency + foreach ($journals as $journal) { + $currencyId = (int)$journal['currency_id']; + + // if not set, set to zero: + if (!isset($sum['per_currency'][$currencyId])) { + $sum['per_currency'][$currencyId] = [ + 'sum' => '0', + 'currency' => [ + 'name' => $journal['currency_name'], + 'symbol' => $journal['currency_symbol'], + 'decimal_places' => $journal['currency_decimal_places'], + ], + ]; + } + + // add amount + $sum['per_currency'][$currencyId]['sum'] = bcadd($sum['per_currency'][$currencyId]['sum'], $journal['amount']); + $sum['grand_sum'] = bcadd($sum['grand_sum'], $journal['amount']); + } + + return $sum; + } + /** @noinspection MoreThanThreeArgumentsInspection */ /** @@ -772,17 +761,11 @@ trait AugumentData protected function spentInPeriodWithout(Carbon $start, Carbon $end): string // get data + augment with info { // collector - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); $types = [TransactionType::WITHDRAWAL]; - $collector->setAllAssetAccounts()->setTypes($types)->setRange($start, $end)->withoutBudget(); - $transactions = $collector->getTransactions(); - $sum = '0'; - /** @var Transaction $entry */ - foreach ($transactions as $entry) { - $sum = bcadd($entry->transaction_amount, $sum); - } + $collector->setTypes($types)->setRange($start, $end)->withoutBudget(); - return $sum; + return $collector->getSum(); } } diff --git a/app/Support/Http/Controllers/AutoCompleteCollector.php b/app/Support/Http/Controllers/AutoCompleteCollector.php deleted file mode 100644 index 719640f082..0000000000 --- a/app/Support/Http/Controllers/AutoCompleteCollector.php +++ /dev/null @@ -1,155 +0,0 @@ -. - */ - -declare(strict_types=1); - -namespace FireflyIII\Support\Http\Controllers; - -use FireflyIII\Models\Account; -use FireflyIII\Repositories\Account\AccountRepositoryInterface; -use FireflyIII\Repositories\Bill\BillRepositoryInterface; -use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; -use FireflyIII\Repositories\Category\CategoryRepositoryInterface; -use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; -use FireflyIII\Repositories\Journal\JournalRepositoryInterface; -use FireflyIII\Repositories\Tag\TagRepositoryInterface; -use Illuminate\Support\Collection; - -/** - * Trait AutoCompleteCollector - */ -trait AutoCompleteCollector -{ - - /** - * @param array $unfiltered - * @param string $query - * - * @return array|null - */ - protected function filterResult(?array $unfiltered, string $query): ?array - { - if (null === $unfiltered) { - return null; // @codeCoverageIgnore - } - if ('' === $query) { - sort($unfiltered); - - return $unfiltered; - } - $return = []; - if ('' !== $query) { - $return = array_values( - array_filter( - $unfiltered, function (string $value) use ($query) { - return !(false === stripos($value, $query)); - }, ARRAY_FILTER_USE_BOTH - ) - ); - } - sort($return); - - - return $return; - } - - /** - * @param array $types - * - * @return array - */ - protected function getAccounts(array $types): array - { - $repository = app(AccountRepositoryInterface::class); - // find everything: - /** @var Collection $collection */ - $collection = $repository->getAccountsByType($types); - $filtered = $collection->filter( - function (Account $account) { - return true === $account->active; - } - ); - - return array_values(array_unique($filtered->pluck('name')->toArray())); - } - - /** - * @return array - */ - protected function getBills(): array - { - $repository = app(BillRepositoryInterface::class); - - return array_unique($repository->getActiveBills()->pluck('name')->toArray()); - } - - /** - * @return array - */ - protected function getBudgets(): array - { - $repository = app(BudgetRepositoryInterface::class); - - return array_unique($repository->getBudgets()->pluck('name')->toArray()); - } - - /** - * @return array - */ - protected function getCategories(): array - { - $repository = app(CategoryRepositoryInterface::class); - - return array_unique($repository->getCategories()->pluck('name')->toArray()); - } - - /** - * @return array - */ - protected function getCurrencyNames(): array - { - /** @var CurrencyRepositoryInterface $repository */ - $repository = app(CurrencyRepositoryInterface::class); - - return $repository->get()->pluck('name')->toArray(); - } - - /** - * @return array - */ - protected function getTags(): array - { - /** @var TagRepositoryInterface $repository */ - $repository = app(TagRepositoryInterface::class); - - return array_unique($repository->get()->pluck('tag')->toArray()); - } - - /** - * @return array - */ - protected function getTransactionTypes(): array - { - $repository = app(JournalRepositoryInterface::class); - - return array_unique($repository->getTransactionTypes()->pluck('type')->toArray()); - } -} diff --git a/app/Support/Http/Controllers/BasicDataSupport.php b/app/Support/Http/Controllers/BasicDataSupport.php index ac13cba8c7..ea627dde8c 100644 --- a/app/Support/Http/Controllers/BasicDataSupport.php +++ b/app/Support/Http/Controllers/BasicDataSupport.php @@ -30,23 +30,6 @@ namespace FireflyIII\Support\Http\Controllers; trait BasicDataSupport { - /** - * Sum up an array. - * - * @param array $array - * - * @return string - */ - protected function arraySum(array $array): string // filter + group data - { - $sum = '0'; - foreach ($array as $entry) { - $sum = bcadd($sum, $entry); - } - - return $sum; - } - /** * Filters empty results from getBudgetPeriodReport. * @@ -70,7 +53,6 @@ trait BasicDataSupport unset($data[$entryId]); } } - return $data; } diff --git a/app/Support/Http/Controllers/ChartGeneration.php b/app/Support/Http/Controllers/ChartGeneration.php index efdcc4198c..516f7a330d 100644 --- a/app/Support/Http/Controllers/ChartGeneration.php +++ b/app/Support/Http/Controllers/ChartGeneration.php @@ -236,6 +236,7 @@ trait ChartGeneration $chartData[1]['entries'][$label] = round($earned, 12); $chartData[2]['entries'][$label] = round($sum, 12); + // @codeCoverageIgnoreStart switch ($step) { default: case '1D': @@ -251,6 +252,7 @@ trait ChartGeneration $start->addYear(); break; } + // @codeCoverageIgnoreEnd } $data = $generator->multiSet($chartData); diff --git a/app/Support/Http/Controllers/CreateStuff.php b/app/Support/Http/Controllers/CreateStuff.php index 94d3a5b997..ca8ce86963 100644 --- a/app/Support/Http/Controllers/CreateStuff.php +++ b/app/Support/Http/Controllers/CreateStuff.php @@ -47,7 +47,7 @@ trait CreateStuff /** * Creates an asset account. * - * @param NewUserFormRequest $request + * @param NewUserFormRequest $request * @param TransactionCurrency $currency * * @return bool @@ -57,16 +57,16 @@ trait CreateStuff /** @var AccountRepositoryInterface $repository */ $repository = app(AccountRepositoryInterface::class); $assetAccount = [ - 'name' => $request->get('bank_name'), - 'iban' => null, - 'accountType' => 'asset', - 'virtualBalance' => 0, - 'account_type_id' => null, - 'active' => true, - 'accountRole' => 'defaultAsset', - 'openingBalance' => $request->input('bank_balance'), - 'openingBalanceDate' => new Carbon, - 'currency_id' => $currency->id, + 'name' => $request->get('bank_name'), + 'iban' => null, + 'account_type' => 'asset', + 'virtual_balance' => 0, + 'account_type_id' => null, + 'active' => true, + 'account_role' => 'defaultAsset', + 'opening_balance' => $request->input('bank_balance'), + 'opening_balance_date' => new Carbon, + 'currency_id' => $currency->id, ]; $repository->store($assetAccount); @@ -78,7 +78,7 @@ trait CreateStuff * Creates a cash wallet. * * @param TransactionCurrency $currency - * @param string $language + * @param string $language * * @return bool */ @@ -87,16 +87,16 @@ trait CreateStuff /** @var AccountRepositoryInterface $repository */ $repository = app(AccountRepositoryInterface::class); $assetAccount = [ - 'name' => (string)trans('firefly.cash_wallet', [], $language), - 'iban' => null, - 'accountType' => 'asset', - 'virtualBalance' => 0, - 'account_type_id' => null, - 'active' => true, - 'accountRole' => 'cashWalletAsset', - 'openingBalance' => null, - 'openingBalanceDate' => null, - 'currency_id' => $currency->id, + 'name' => (string)trans('firefly.cash_wallet', [], $language), + 'iban' => null, + 'account_type' => 'asset', + 'virtual_balance' => 0, + 'account_type_id' => null, + 'active' => true, + 'account_role' => 'cashWalletAsset', + 'opening_balance' => null, + 'opening_balance_date' => null, + 'currency_id' => $currency->id, ]; $repository->store($assetAccount); @@ -130,9 +130,9 @@ trait CreateStuff /** * Create a savings account. * - * @param NewUserFormRequest $request + * @param NewUserFormRequest $request * @param TransactionCurrency $currency - * @param string $language + * @param string $language * * @return bool */ @@ -141,16 +141,16 @@ trait CreateStuff /** @var AccountRepositoryInterface $repository */ $repository = app(AccountRepositoryInterface::class); $savingsAccount = [ - 'name' => (string)trans('firefly.new_savings_account', ['bank_name' => $request->get('bank_name')], $language), - 'iban' => null, - 'accountType' => 'asset', - 'account_type_id' => null, - 'virtualBalance' => 0, - 'active' => true, - 'accountRole' => 'savingAsset', - 'openingBalance' => $request->input('savings_balance'), - 'openingBalanceDate' => new Carbon, - 'currency_id' => $currency->id, + 'name' => (string)trans('firefly.new_savings_account', ['bank_name' => $request->get('bank_name')], $language), + 'iban' => null, + 'account_type' => 'asset', + 'account_type_id' => null, + 'virtual_balance' => 0, + 'active' => true, + 'account_role' => 'savingAsset', + 'opening_balance' => $request->input('savings_balance'), + 'opening_balance_date' => new Carbon, + 'currency_id' => $currency->id, ]; $repository->store($savingsAccount); diff --git a/app/Support/Http/Controllers/GetConfigurationData.php b/app/Support/Http/Controllers/GetConfigurationData.php index 4d1de2ed58..94f7472d15 100644 --- a/app/Support/Http/Controllers/GetConfigurationData.php +++ b/app/Support/Http/Controllers/GetConfigurationData.php @@ -68,7 +68,7 @@ trait GetConfigurationData $routeKey = str_replace('.', '_', $route); $elements = config(sprintf('intro.%s', $routeKey)); $steps = []; - if (\is_array($elements) && \count($elements) > 0) { + if (is_array($elements) && count($elements) > 0) { foreach ($elements as $key => $options) { $currentStep = $options; @@ -79,7 +79,7 @@ trait GetConfigurationData $steps[] = $currentStep; } } - Log::debug(sprintf('Total basic steps for %s is %d', $routeKey, \count($steps))); + Log::debug(sprintf('Total basic steps for %s is %d', $routeKey, count($steps))); return $steps; } @@ -106,12 +106,12 @@ trait GetConfigurationData // first range is the current range: $title => [$start, $end], ]; - Log::debug(sprintf('viewRange is %s', $viewRange)); - Log::debug(sprintf('isCustom is %s', var_export($isCustom, true))); + //Log::debug(sprintf('viewRange is %s', $viewRange)); + //Log::debug(sprintf('isCustom is %s', var_export($isCustom, true))); // when current range is a custom range, add the current period as the next range. if ($isCustom) { - Log::debug('Custom is true.'); + //Log::debug('Custom is true.'); $index = app('navigation')->periodShow($start, $viewRange); $customPeriodStart = app('navigation')->startOfPeriod($start, $viewRange); $customPeriodEnd = app('navigation')->endOfPeriod($customPeriodStart, $viewRange); @@ -188,7 +188,7 @@ trait GetConfigurationData if ('' !== $specificPage) { $routeKey = str_replace('.', '_', $route); $elements = config(sprintf('intro.%s', $routeKey . '_' . $specificPage)); - if (\is_array($elements) && \count($elements) > 0) { + if (is_array($elements) && count($elements) > 0) { foreach ($elements as $key => $options) { $currentStep = $options; @@ -200,36 +200,10 @@ trait GetConfigurationData } } } - Log::debug(sprintf('Total specific steps for route "%s" and page "%s" (routeKey is "%s") is %d', $route, $specificPage, $routeKey, \count($steps))); + Log::debug(sprintf('Total specific steps for route "%s" and page "%s" (routeKey is "%s") is %d', $route, $specificPage, $routeKey, count($steps))); return $steps; } - - /** - * Check if forbidden functions are set. - * - * @return bool - */ - protected function hasForbiddenFunctions(): bool // validate system config - { - $list = ['proc_close']; - $forbidden = explode(',', ini_get('disable_functions')); - $trimmed = array_map( - function (string $value) { - return trim($value); - }, $forbidden - ); - foreach ($list as $entry) { - if (\in_array($entry, $trimmed, true)) { - Log::error('Method "%s" is FORBIDDEN, so the console command cannot be executed.'); - - return true; - } - } - - return false; - } - /** * */ diff --git a/app/Support/Http/Controllers/ModelInformation.php b/app/Support/Http/Controllers/ModelInformation.php index 658c2b8da7..771ab35033 100644 --- a/app/Support/Http/Controllers/ModelInformation.php +++ b/app/Support/Http/Controllers/ModelInformation.php @@ -25,6 +25,7 @@ namespace FireflyIII\Support\Http\Controllers; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Models\Account; +use FireflyIII\Models\AccountType; use FireflyIII\Models\Bill; use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionType; @@ -70,130 +71,6 @@ trait ModelInformation return [$result]; } - /** - * Get the destination account. Is complex. - * - * @param TransactionJournal $journal - * @param TransactionType $destinationType - * @param array $data - * - * @return Account - * - * @throws FireflyException - * - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - */ - protected function getDestinationAccount(TransactionJournal $journal, TransactionType $destinationType, array $data - ): Account // helper for conversion. Get info from obj. - { - /** @var AccountRepositoryInterface $accountRepository */ - $accountRepository = app(AccountRepositoryInterface::class); - /** @var JournalRepositoryInterface $journalRepos */ - $journalRepos = app(JournalRepositoryInterface::class); - $sourceAccount = $journalRepos->getJournalSourceAccounts($journal)->first(); - $destinationAccount = $journalRepos->getJournalDestinationAccounts($journal)->first(); - $sourceType = $journal->transactionType; - $joined = $sourceType->type . '-' . $destinationType->type; - switch ($joined) { - default: - throw new FireflyException('Cannot handle ' . $joined); // @codeCoverageIgnore - case TransactionType::WITHDRAWAL . '-' . TransactionType::DEPOSIT: - // one - $destination = $sourceAccount; - break; - case TransactionType::WITHDRAWAL . '-' . TransactionType::TRANSFER: - // two - $destination = $accountRepository->findNull((int)$data['destination_account_asset']); - break; - case TransactionType::DEPOSIT . '-' . TransactionType::WITHDRAWAL: - case TransactionType::TRANSFER . '-' . TransactionType::WITHDRAWAL: - // three and five - if ('' === $data['destination_account_expense'] || null === $data['destination_account_expense']) { - // destination is a cash account. - return $accountRepository->getCashAccount(); - } - $data = [ - 'name' => $data['destination_account_expense'], - 'accountType' => 'expense', - 'account_type_id' => null, - 'virtualBalance' => 0, - 'active' => true, - 'iban' => null, - ]; - $destination = $accountRepository->store($data); - break; - case TransactionType::DEPOSIT . '-' . TransactionType::TRANSFER: - case TransactionType::TRANSFER . '-' . TransactionType::DEPOSIT: - // four and six - $destination = $destinationAccount; - break; - } - - return $destination; - } - - /** - * Get the source account. - * - * @param TransactionJournal $journal - * @param TransactionType $destinationType - * @param array $data - * - * @return Account - * - * @throws FireflyException - * - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - */ - protected function getSourceAccount(TransactionJournal $journal, TransactionType $destinationType, array $data - ): Account // helper for conversion. Get info from obj. - { - /** @var AccountRepositoryInterface $accountRepository */ - $accountRepository = app(AccountRepositoryInterface::class); - /** @var JournalRepositoryInterface $journalRepos */ - $journalRepos = app(JournalRepositoryInterface::class); - $sourceAccount = $journalRepos->getJournalSourceAccounts($journal)->first(); - $destinationAccount = $journalRepos->getJournalDestinationAccounts($journal)->first(); - $sourceType = $journal->transactionType; - $joined = $sourceType->type . '-' . $destinationType->type; - switch ($joined) { - default: - throw new FireflyException('Cannot handle ' . $joined); // @codeCoverageIgnore - case TransactionType::WITHDRAWAL . '-' . TransactionType::DEPOSIT: - case TransactionType::TRANSFER . '-' . TransactionType::DEPOSIT: - - if ('' === $data['source_account_revenue'] || null === $data['source_account_revenue']) { - // destination is a cash account. - return $accountRepository->getCashAccount(); - } - - $data = [ - 'name' => $data['source_account_revenue'], - 'accountType' => 'revenue', - 'virtualBalance' => 0, - 'active' => true, - 'account_type_id' => null, - 'iban' => null, - ]; - $source = $accountRepository->store($data); - break; - case TransactionType::WITHDRAWAL . '-' . TransactionType::TRANSFER: - case TransactionType::TRANSFER . '-' . TransactionType::WITHDRAWAL: - $source = $sourceAccount; - break; - case TransactionType::DEPOSIT . '-' . TransactionType::WITHDRAWAL: - $source = $destinationAccount; - break; - case TransactionType::DEPOSIT . '-' . TransactionType::TRANSFER: - $source = $accountRepository->findNull((int)$data['source_account_asset']); - break; - } - - return $source; - } - /** * Create fake triggers to match the bill's properties * @@ -239,32 +116,39 @@ trait ModelInformation } /** - * Is transaction opening balance? - * - * @param TransactionJournal $journal - * - * @return bool + * @codeCoverageIgnore + * @return array */ - protected function isOpeningBalance(TransactionJournal $journal): bool + protected function getRoles(): array { - return TransactionType::OPENING_BALANCE === $journal->transactionType->type; + $roles = []; + foreach (config('firefly.accountRoles') as $role) { + $roles[$role] = (string)trans(sprintf('firefly.account_role_%s', $role)); + } + + return $roles; } /** - * Checks if journal is split. - * - * @param TransactionJournal $journal - * - * @return bool + * @codeCoverageIgnore + * @return array */ - protected function isSplitJournal(TransactionJournal $journal): bool // validate objects + protected function getLiabilityTypes(): array { - /** @var JournalRepositoryInterface $repository */ - $repository = app(JournalRepositoryInterface::class); - $repository->setUser($journal->user); - $count = $repository->countTransactions($journal); + /** @var AccountRepositoryInterface $repository */ + $repository = app(AccountRepositoryInterface::class); + // types of liability: + $debt = $repository->getAccountTypeByType(AccountType::DEBT); + $loan = $repository->getAccountTypeByType(AccountType::LOAN); + $mortgage = $repository->getAccountTypeByType(AccountType::MORTGAGE); + /** @noinspection NullPointerExceptionInspection */ + $liabilityTypes = [ + $debt->id => (string)trans(sprintf('firefly.account_type_%s', AccountType::DEBT)), + $loan->id => (string)trans(sprintf('firefly.account_type_%s', AccountType::LOAN)), + $mortgage->id => (string)trans(sprintf('firefly.account_type_%s', AccountType::MORTGAGE)), + ]; + asort($liabilityTypes); - return $count > 2; + return $liabilityTypes; } - } diff --git a/app/Support/Http/Controllers/PeriodOverview.php b/app/Support/Http/Controllers/PeriodOverview.php index 352f0d56bb..285d611040 100644 --- a/app/Support/Http/Controllers/PeriodOverview.php +++ b/app/Support/Http/Controllers/PeriodOverview.php @@ -24,18 +24,12 @@ declare(strict_types=1); namespace FireflyIII\Support\Http\Controllers; use Carbon\Carbon; -use FireflyIII\Helpers\Collector\TransactionCollectorInterface; -use FireflyIII\Helpers\Filter\InternalTransferFilter; +use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Models\Account; use FireflyIII\Models\Category; use FireflyIII\Models\Tag; use FireflyIII\Models\Transaction; -use FireflyIII\Models\TransactionCurrency; use FireflyIII\Models\TransactionType; -use FireflyIII\Repositories\Account\AccountRepositoryInterface; -use FireflyIII\Repositories\Category\CategoryRepositoryInterface; -use FireflyIII\Repositories\Journal\JournalRepositoryInterface; -use FireflyIII\Repositories\Tag\TagRepositoryInterface; use FireflyIII\Support\CacheProperties; use Illuminate\Support\Collection; use Log; @@ -45,15 +39,27 @@ use Log; * * TODO verify this all works as expected. * + * - Always request start date and end date. * - Group expenses, income, etc. under this period. - * - Returns collection of arrays. Possible fields are: - * - start (string), - * end (string), + * - Returns collection of arrays. Fields * title (string), - * spent (string), - * earned (string), - * transferred (string) + * route (string) + * total_transactions (int) + * spent (array), + * earned (array), + * transferred_away (array) + * transferred_in (array) + * transferred (array) * + * each array has the following format: + * currency_id => [ + * currency_id : 1, (int) + * currency_symbol : X (str) + * currency_name: Euro (str) + * currency_code: EUR (str) + * amount: -1234 (str) + * count: 23 + * ] * */ trait PeriodOverview @@ -64,24 +70,16 @@ trait PeriodOverview * and for each period, the amount of money spent and earned. This is a complex operation which is cached for * performance reasons. * - * The method has been refactored recently for better performance. - * * @param Account $account The account involved - * @param Carbon $date The start date. + * @param Carbon $date The start date. + * @param Carbon $end The end date. * - * @return Collection + * @return array */ - protected function getAccountPeriodOverview(Account $account, Carbon $date): Collection + protected function getAccountPeriodOverview(Account $account, Carbon $start, Carbon $end): array { - /** @var AccountRepositoryInterface $repository */ - $repository = app(AccountRepositoryInterface::class); - $range = app('preferences')->get('viewRange', '1M')->data; - $end = $repository->oldestJournalDate($account) ?? Carbon::now()->subMonth()->startOfMonth(); - $start = clone $date; - - if ($end < $start) { - [$start, $end] = [$end, $start]; // @codeCoverageIgnore - } + $range = app('preferences')->get('viewRange', '1M')->data; + [$start, $end] = $end < $start ? [$end, $start] : [$start, $end]; // properties for cache $cache = new CacheProperties; @@ -94,37 +92,52 @@ trait PeriodOverview } /** @var array $dates */ $dates = app('navigation')->blockPeriods($start, $end, $range); - $entries = new Collection; + $entries = []; + + // collect all expenses in this period: + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setAccounts(new Collection([$account])); + $collector->setRange($start, $end); + $collector->setTypes([TransactionType::DEPOSIT]); + $earnedSet = $collector->getExtractedJournals(); + + // collect all income in this period: + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setAccounts(new Collection([$account])); + $collector->setRange($start, $end); + $collector->setTypes([TransactionType::WITHDRAWAL]); + $spentSet = $collector->getExtractedJournals(); + + // collect all transfers in this period: + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setAccounts(new Collection([$account])); + $collector->setRange($start, $end); + $collector->setTypes([TransactionType::TRANSFER]); + $transferSet = $collector->getExtractedJournals(); + // loop dates foreach ($dates as $currentDate) { - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setAccounts(new Collection([$account]))->setRange($currentDate['start'], $currentDate['end'])->setTypes([TransactionType::DEPOSIT]) - ->withOpposingAccount(); - $earnedSet = $collector->getTransactions(); - $earned = $this->groupByCurrency($earnedSet); - - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setAccounts(new Collection([$account]))->setRange($currentDate['start'], $currentDate['end'])->setTypes([TransactionType::WITHDRAWAL]) - ->withOpposingAccount(); - $spentSet = $collector->getTransactions(); - $spent = $this->groupByCurrency($spentSet); - - $title = app('navigation')->periodShow($currentDate['start'], $currentDate['period']); - /** @noinspection PhpUndefinedMethodInspection */ - $entries->push( + $title = app('navigation')->periodShow($currentDate['start'], $currentDate['period']); + $earned = $this->filterJournalsByDate($earnedSet, $currentDate['start'], $currentDate['end']); + $spent = $this->filterJournalsByDate($spentSet, $currentDate['start'], $currentDate['end']); + $transferredAway = $this->filterTransferredAway($account, $this->filterJournalsByDate($transferSet, $currentDate['start'], $currentDate['end'])); + $transferredIn = $this->filterTransferredIn($account, $this->filterJournalsByDate($transferSet, $currentDate['start'], $currentDate['end'])); + $entries[] = [ - 'transactions' => 0, - 'title' => $title, - 'spent' => $spent, - 'earned' => $earned, - 'transferred' => '0', - 'route' => route('accounts.show', [$account->id, $currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]), - ] - ); - } + 'title' => $title, + 'route' => + route('accounts.show', [$account->id, $currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]), + 'total_transactions' => count($spent) + count($earned) + count($transferredAway) + count($transferredIn), + 'spent' => $this->groupByCurrency($spent), + 'earned' => $this->groupByCurrency($earned), + 'transferred_away' => $this->groupByCurrency($transferredAway), + 'transferred_in' => $this->groupByCurrency($transferredIn), + ]; + } $cache->store($entries); return $entries; @@ -134,22 +147,14 @@ trait PeriodOverview * Overview for single category. Has been refactored recently. * * @param Category $category - * @param Carbon $date - * - * @return Collection + * @param Carbon $start + * @param Carbon $end + * @return array */ - protected function getCategoryPeriodOverview(Category $category, Carbon $date): Collection + protected function getCategoryPeriodOverview(Category $category, Carbon $start, Carbon $end): array { - /** @var JournalRepositoryInterface $journalRepository */ - $journalRepository = app(JournalRepositoryInterface::class); - $range = app('preferences')->get('viewRange', '1M')->data; - $first = $journalRepository->firstNull(); - $end = null === $first ? new Carbon : $first->date; - $start = clone $date; - - if ($end < $start) { - [$start, $end] = [$end, $start]; // @codeCoverageIgnore - } + $range = app('preferences')->get('viewRange', '1M')->data; + [$start, $end] = $end < $start ? [$end, $start] : [$start, $end]; // properties for entries with their amounts. $cache = new CacheProperties(); @@ -164,35 +169,49 @@ trait PeriodOverview } /** @var array $dates */ $dates = app('navigation')->blockPeriods($start, $end, $range); - $entries = new Collection; - /** @var CategoryRepositoryInterface $categoryRepository */ - $categoryRepository = app(CategoryRepositoryInterface::class); + $entries = []; + + // collect all expenses in this period: + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setCategory($category); + $collector->setRange($start, $end); + $collector->setTypes([TransactionType::DEPOSIT]); + $earnedSet = $collector->getExtractedJournals(); + + // collect all income in this period: + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setCategory($category); + $collector->setRange($start, $end); + $collector->setTypes([TransactionType::WITHDRAWAL]); + $spentSet = $collector->getExtractedJournals(); + + // collect all transfers in this period: + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setCategory($category); + $collector->setRange($start, $end); + $collector->setTypes([TransactionType::TRANSFER]); + $transferSet = $collector->getExtractedJournals(); + foreach ($dates as $currentDate) { - $spent = $categoryRepository->spentInPeriodCollection(new Collection([$category]), new Collection, $currentDate['start'], $currentDate['end']); - $earned = $categoryRepository->earnedInPeriodCollection(new Collection([$category]), new Collection, $currentDate['start'], $currentDate['end']); - $spent = $this->groupByCurrency($spent); - $earned = $this->groupByCurrency($earned); - - // amount transferred - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setAllAssetAccounts()->setRange($currentDate['start'], $currentDate['end'])->setCategory($category) - ->withOpposingAccount()->setTypes([TransactionType::TRANSFER]); - $collector->removeFilter(InternalTransferFilter::class); - $transferred = $this->groupByCurrency($collector->getTransactions()); - - $title = app('navigation')->periodShow($currentDate['end'], $currentDate['period']); - $entries->push( + $spent = $this->filterJournalsByDate($spentSet, $currentDate['start'], $currentDate['end']); + $earned = $this->filterJournalsByDate($earnedSet, $currentDate['start'], $currentDate['end']); + $transferred = $this->filterJournalsByDate($transferSet, $currentDate['start'], $currentDate['end']); + $title = app('navigation')->periodShow($currentDate['end'], $currentDate['period']); + $entries[] = [ - 'transactions' => 0, - 'title' => $title, - 'spent' => $spent, - 'earned' => $earned, - 'transferred' => $transferred, - 'route' => route('categories.show', [$category->id, $currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]), - ] - ); + 'transactions' => 0, + 'title' => $title, + 'route' => route('categories.show', + [$category->id, $currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]), + 'total_transactions' => count($spent) + count($earned) + count($transferred), + 'spent' => $this->groupByCurrency($spent), + 'earned' => $this->groupByCurrency($earned), + 'transferred' => $this->groupByCurrency($transferred), + ]; } $cache->store($entries); @@ -204,22 +223,16 @@ trait PeriodOverview * * This method has been refactored recently. * + * @param Carbon $start * @param Carbon $date * - * @return Collection + * @return array */ - protected function getNoBudgetPeriodOverview(Carbon $date): Collection + protected function getNoBudgetPeriodOverview(Carbon $start, Carbon $end): array { - /** @var JournalRepositoryInterface $repository */ - $repository = app(JournalRepositoryInterface::class); - $first = $repository->firstNull(); - $end = null === $first ? new Carbon : $first->date; - $start = clone $date; - $range = app('preferences')->get('viewRange', '1M')->data; + $range = app('preferences')->get('viewRange', '1M')->data; - if ($end < $start) { - [$start, $end] = [$end, $start]; // @codeCoverageIgnore - } + [$start, $end] = $end < $start ? [$end, $start] : [$start, $end]; $cache = new CacheProperties; $cache->addProperty($start); @@ -232,27 +245,28 @@ trait PeriodOverview /** @var array $dates */ $dates = app('navigation')->blockPeriods($start, $end, $range); - $entries = new Collection; + $entries = []; + + + // get all expenses without a budget. + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setRange($start, $end)->withoutBudget()->withAccountInformation()->setTypes([TransactionType::WITHDRAWAL]); + $journals = $collector->getExtractedJournals(); + foreach ($dates as $currentDate) { - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setAllAssetAccounts()->setRange($currentDate['start'], $currentDate['end'])->withoutBudget()->withOpposingAccount()->setTypes( - [TransactionType::WITHDRAWAL] - ); - $set = $collector->getTransactions(); - $count = $set->count(); - $spent = $this->groupByCurrency($set); - $title = app('navigation')->periodShow($currentDate['end'], $currentDate['period']); - $entries->push( + $set = $this->filterJournalsByDate($journals, $currentDate['start'], $currentDate['end']); + $title = app('navigation')->periodShow($currentDate['end'], $currentDate['period']); + $entries[] = [ - 'transactions' => $count, - 'title' => $title, - 'spent' => $spent, - 'earned' => '0', - 'transferred' => '0', - 'route' => route('budgets.no-budget', [$currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]), - ] - ); + 'title' => $title, + 'route' => route('budgets.no-budget', [$currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]), + 'total_transactions' => count($set), + 'spent' => $this->groupByCurrency($set), + 'earned' => [], + 'transferred_away' => [], + 'transferred_in' => [], + ]; } $cache->store($entries); @@ -260,16 +274,16 @@ trait PeriodOverview } /** - * TODO has to be synced with the others. + * TODO fix date. * * Show period overview for no category view. * * @param Carbon $theDate * - * @return Collection + * @return array * @SuppressWarnings(PHPMD.ExcessiveMethodLength) */ - protected function getNoCategoryPeriodOverview(Carbon $theDate): Collection // period overview method. + protected function getNoCategoryPeriodOverview(Carbon $theDate): array { Log::debug(sprintf('Now in getNoCategoryPeriodOverview(%s)', $theDate->format('Y-m-d'))); $range = app('preferences')->get('viewRange', '1M')->data; @@ -291,56 +305,47 @@ trait PeriodOverview } $dates = app('navigation')->blockPeriods($start, $end, $range); - $entries = new Collection; + $entries = []; - foreach ($dates as $date) { + // collect all expenses in this period: + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->withoutCategory(); + $collector->setRange($start, $end); + $collector->setTypes([TransactionType::DEPOSIT]); + $earnedSet = $collector->getExtractedJournals(); - // count journals without category in this period: - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setAllAssetAccounts()->setRange($date['start'], $date['end'])->withoutCategory() - ->withOpposingAccount()->setTypes([TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::TRANSFER]); - $collector->removeFilter(InternalTransferFilter::class); - $count = $collector->getTransactions()->count(); + // collect all income in this period: + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->withoutCategory(); + $collector->setRange($start, $end); + $collector->setTypes([TransactionType::WITHDRAWAL]); + $spentSet = $collector->getExtractedJournals(); - // amount transferred - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setAllAssetAccounts()->setRange($date['start'], $date['end'])->withoutCategory() - ->withOpposingAccount()->setTypes([TransactionType::TRANSFER]); - $collector->removeFilter(InternalTransferFilter::class); - $transferred = app('steam')->positive((string)$collector->getTransactions()->sum('transaction_amount')); + // collect all transfers in this period: + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->withoutCategory(); + $collector->setRange($start, $end); + $collector->setTypes([TransactionType::TRANSFER]); + $transferSet = $collector->getExtractedJournals(); - // amount spent - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setAllAssetAccounts()->setRange($date['start'], $date['end'])->withoutCategory()->withOpposingAccount()->setTypes( - [TransactionType::WITHDRAWAL] - ); - $spent = $collector->getTransactions()->sum('transaction_amount'); - - // amount earned - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setAllAssetAccounts()->setRange($date['start'], $date['end'])->withoutCategory()->withOpposingAccount()->setTypes( - [TransactionType::DEPOSIT] - ); - $earned = $collector->getTransactions()->sum('transaction_amount'); - /** @noinspection PhpUndefinedMethodInspection */ - $dateStr = $date['end']->format('Y-m-d'); - $dateName = app('navigation')->periodShow($date['end'], $date['period']); - $entries->push( + /** @var array $currentDate */ + foreach ($dates as $currentDate) { + $spent = $this->filterJournalsByDate($spentSet, $currentDate['start'], $currentDate['end']); + $earned = $this->filterJournalsByDate($earnedSet, $currentDate['start'], $currentDate['end']); + $transferred = $this->filterJournalsByDate($transferSet, $currentDate['start'], $currentDate['end']); + $title = app('navigation')->periodShow($currentDate['end'], $currentDate['period']); + $entries[] = [ - 'string' => $dateStr, - 'name' => $dateName, - 'count' => $count, - 'spent' => $spent, - 'earned' => $earned, - 'transferred' => $transferred, - 'start' => clone $date['start'], - 'end' => clone $date['end'], - ] - ); + 'title' => $title, + 'route' => route('categories.no-category', [$currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]), + 'total_transactions' => count($spent) + count($earned) + count($transferred), + 'spent' => $this->groupByCurrency($spent), + 'earned' => $this->groupByCurrency($earned), + 'transferred' => $this->groupByCurrency($transferred), + ]; } Log::debug('End of loops'); $cache->store($entries); @@ -351,204 +356,246 @@ trait PeriodOverview /** * This shows a period overview for a tag. It goes back in time and lists all relevant transactions and sums. * - * @param Tag $tag + * @param Tag $tag * * @param Carbon $date * - * @return Collection + * @return array */ - protected function getTagPeriodOverview(Tag $tag, Carbon $date): Collection // period overview for tags. + protected function getTagPeriodOverview(Tag $tag, Carbon $start, Carbon $end): array // period overview for tags. { - /** @var TagRepositoryInterface $repository */ - $repository = app(TagRepositoryInterface::class); - $range = app('preferences')->get('viewRange', '1M')->data; - /** @var Carbon $end */ - $start = clone $date; - $end = $repository->firstUseDate($tag) ?? new Carbon; + $range = app('preferences')->get('viewRange', '1M')->data; + [$start, $end] = $end < $start ? [$end, $start] : [$start, $end]; - if ($end < $start) { - [$start, $end] = [$end, $start]; // @codeCoverageIgnore - } - - // properties for entries with their amounts. + // properties for cache $cache = new CacheProperties; $cache->addProperty($start); $cache->addProperty($end); $cache->addProperty('tag-period-entries'); $cache->addProperty($tag->id); - if ($cache->has()) { - return $cache->get(); // @codeCoverageIgnore + // return $cache->get(); // @codeCoverageIgnore } - /** @var array $dates */ $dates = app('navigation')->blockPeriods($start, $end, $range); - $entries = new Collection; - // while end larger or equal to start + $entries = []; + + // collect all expenses in this period: + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setTag($tag); + $collector->setRange($start, $end); + $collector->setTypes([TransactionType::DEPOSIT]); + $earnedSet = $collector->getExtractedJournals(); + + // collect all income in this period: + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setTag($tag); + $collector->setRange($start, $end); + $collector->setTypes([TransactionType::WITHDRAWAL]); + $spentSet = $collector->getExtractedJournals(); + + // collect all transfers in this period: + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setTag($tag); + $collector->setRange($start, $end); + $collector->setTypes([TransactionType::TRANSFER]); + $transferSet = $collector->getExtractedJournals(); + foreach ($dates as $currentDate) { - - $spentSet = $repository->expenseInPeriod($tag, $currentDate['start'], $currentDate['end']); - $spent = $this->groupByCurrency($spentSet); - $earnedSet = $repository->incomeInPeriod($tag, $currentDate['start'], $currentDate['end']); - $earned = $this->groupByCurrency($earnedSet); - $transferredSet = $repository->transferredInPeriod($tag, $currentDate['start'], $currentDate['end']); - $transferred = $this->groupByCurrency($transferredSet); - $title = app('navigation')->periodShow($currentDate['end'], $currentDate['period']); - - $entries->push( + $spent = $this->filterJournalsByDate($spentSet, $currentDate['start'], $currentDate['end']); + $earned = $this->filterJournalsByDate($earnedSet, $currentDate['start'], $currentDate['end']); + $transferred = $this->filterJournalsByDate($transferSet, $currentDate['start'], $currentDate['end']); + $title = app('navigation')->periodShow($currentDate['end'], $currentDate['period']); + $entries[] = [ - 'transactions' => $spentSet->count() + $earnedSet->count() + $transferredSet->count(), - 'title' => $title, - 'spent' => $spent, - 'earned' => $earned, - 'transferred' => $transferred, - 'route' => route('tags.show', [$tag->id, $currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]), - ] - ); - + 'transactions' => 0, + 'title' => $title, + 'route' => route('tags.show', + [$tag->id, $currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]), + 'total_transactions' => count($spent) + count($earned) + count($transferred), + 'spent' => $this->groupByCurrency($spent), + 'earned' => $this->groupByCurrency($earned), + 'transferred' => $this->groupByCurrency($transferred), + ]; } - $cache->store($entries); return $entries; } /** - * This list shows the overview of a type of transaction, for the period blocks on the list of transactions. + * @param string $transactionType + * @param Carbon $endDate * - * @param string $what - * @param Carbon $date - * - * @return Collection + * @return array */ - protected function getTransactionPeriodOverview(string $what, Carbon $date): Collection // period overview for transactions. + protected function getTransactionPeriodOverview(string $transactionType, Carbon $start, Carbon $end): array { - /** @var JournalRepositoryInterface $repository */ - $repository = app(JournalRepositoryInterface::class); - $range = app('preferences')->get('viewRange', '1M')->data; - $endJournal = $repository->firstNull(); - $end = null === $endJournal ? new Carbon : $endJournal->date; - $start = clone $date; - $types = config('firefly.transactionTypesByWhat.' . $what); + $range = app('preferences')->get('viewRange', '1M')->data; + $types = config(sprintf('firefly.transactionTypesByType.%s', $transactionType)); + [$start, $end] = $end < $start ? [$end, $start] : [$start, $end]; - - if ($end < $start) { - [$start, $end] = [$end, $start]; // @codeCoverageIgnore - } - - // properties for entries with their amounts. + // properties for cache $cache = new CacheProperties; $cache->addProperty($start); $cache->addProperty($end); $cache->addProperty('transactions-period-entries'); - $cache->addProperty($what); - - + $cache->addProperty($transactionType); + if ($cache->has()) { + return $cache->get(); // @codeCoverageIgnore + } /** @var array $dates */ $dates = app('navigation')->blockPeriods($start, $end, $range); - $entries = new Collection; + $entries = []; + + // collect all journals in this period (regardless of type) + $collector = app(GroupCollectorInterface::class); + $collector->setTypes($types)->setRange($start, $end); + $genericSet = $collector->getExtractedJournals(); foreach ($dates as $currentDate) { + $spent = []; + $earned = []; + $transferred = []; + $title = app('navigation')->periodShow($currentDate['end'], $currentDate['period']); - // get all expenses, income or transfers: - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setAllAssetAccounts()->setRange($currentDate['start'], $currentDate['end'])->withOpposingAccount()->setTypes($types); - $collector->removeFilter(InternalTransferFilter::class); - $transactions = $collector->getTransactions(); - $title = app('navigation')->periodShow($currentDate['end'], $currentDate['period']); - $grouped = $this->groupByCurrency($transactions); - $spent = []; - $earned = []; - $transferred = []; - if ('expenses' === $what || 'withdrawal' === $what) { - $spent = $grouped; + // set to correct array + if ('expenses' === $transactionType || 'withdrawal' === $transactionType) { + $spent = $this->filterJournalsByDate($genericSet, $currentDate['start'], $currentDate['end']); } - if ('revenue' === $what || 'deposit' === $what) { - $earned = $grouped; + if ('revenue' === $transactionType || 'deposit' === $transactionType) { + $earned = $this->filterJournalsByDate($genericSet, $currentDate['start'], $currentDate['end']); } - if ('transfer' === $what || 'transfers' === $what) { - $transferred = $grouped; + if ('transfer' === $transactionType || 'transfers' === $transactionType) { + $transferred = $this->filterJournalsByDate($genericSet, $currentDate['start'], $currentDate['end']); } - $entries->push( + + + $entries[] = [ - 'transactions' => $transactions->count(), - 'title' => $title, - 'spent' => $spent, - 'earned' => $earned, - 'transferred' => $transferred, - 'route' => route('transactions.index', [$what, $currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]), - ] - ); + 'title' => $title, + 'route' => + route('transactions.index', [$transactionType, $currentDate['start']->format('Y-m-d'), $currentDate['end']->format('Y-m-d')]), + 'total_transactions' => count($spent) + count($earned) + count($transferred), + 'spent' => $this->groupByCurrency($spent), + 'earned' => $this->groupByCurrency($earned), + 'transferred' => $this->groupByCurrency($transferred), + ]; } return $entries; } /** - * Collect the sum per currency. - * - * @param Collection $collection - * + * Return only transactions where $account is the source. + * @param Account $account + * @param array $journals * @return array */ - protected function sumPerCurrency(Collection $collection): array // helper for transactions (math, calculations) + private function filterTransferredAway(Account $account, array $journals): array { $return = []; - /** @var Transaction $transaction */ - foreach ($collection as $transaction) { - $currencyId = (int)$transaction->transaction_currency_id; - - // save currency information: - if (!isset($return[$currencyId])) { - $currencySymbol = $transaction->transaction_currency_symbol; - $decimalPlaces = $transaction->transaction_currency_dp; - $currencyCode = $transaction->transaction_currency_code; - $return[$currencyId] = [ - 'currency' => [ - 'id' => $currencyId, - 'code' => $currencyCode, - 'symbol' => $currencySymbol, - 'dp' => $decimalPlaces, - ], - 'sum' => '0', - 'count' => 0, - ]; + /** @var array $journal */ + foreach ($journals as $journal) { + if ($account->id === (int)$journal['source_account_id']) { + $return[] = $journal; } - // save amount: - $return[$currencyId]['sum'] = bcadd($return[$currencyId]['sum'], $transaction->transaction_amount); - ++$return[$currencyId]['count']; } - asort($return); return $return; } /** - * @param Collection $transactions - * + * Return only transactions where $account is the source. + * @param Account $account + * @param array $journals * @return array + * @codeCoverageIgnore */ - private function groupByCurrency(Collection $transactions): array + private function filterTransferredIn(Account $account, array $journals): array { $return = []; - /** @var Transaction $transaction */ - foreach ($transactions as $transaction) { - $currencyId = (int)$transaction->transaction_currency_id; - if (!isset($return[$currencyId])) { - $currency = new TransactionCurrency; - $currency->symbol = $transaction->transaction_currency_symbol; - $currency->decimal_places = $transaction->transaction_currency_dp; - $currency->name = $transaction->transaction_currency_name; - $return[$currencyId] = [ - 'amount' => '0', - 'currency' => $currency, - ]; + /** @var array $journal */ + foreach ($journals as $journal) { + if ($account->id === (int)$journal['destination_account_id']) { + $return[] = $journal; } - $return[$currencyId]['amount'] = bcadd($return[$currencyId]['amount'], $transaction->transaction_amount); } return $return; } + /** + * Filter a list of journals by a set of dates, and then group them by currency. + * + * @param array $array + * @param Carbon $start + * @param Carbon $end + * @return array + */ + private function filterJournalsByDate(array $array, Carbon $start, Carbon $end): array + { + $result = []; + /** @var array $journal */ + foreach ($array as $journal) { + if ($journal['date'] <= $end && $journal['date'] >= $start) { + $result[] = $journal; + } + } + + return $result; + } + + /** + * @param array $journals + * + * @return array + * @codeCoverageIgnore + */ + private function groupByCurrency(array $journals): array + { + $return = []; + /** @var array $journal */ + foreach ($journals as $journal) { + $currencyId = (int)$journal['currency_id']; + $foreignCurrencyId = $journal['foreign_currency_id']; + if (!isset($return[$currencyId])) { + $return[$currencyId] = [ + 'amount' => '0', + 'count' => 0, + 'currency_id' => $currencyId, + 'currency_name' => $journal['currency_name'], + 'currency_code' => $journal['currency_code'], + 'currency_symbol' => $journal['currency_symbol'], + 'currency_decimal_places' => $journal['currency_decimal_places'], + ]; + } + $return[$currencyId]['amount'] = bcadd($return[$currencyId]['amount'], $journal['amount'] ?? '0'); + $return[$currencyId]['count']++; + + + if (null !== $foreignCurrencyId) { + if (!isset($return[$foreignCurrencyId])) { + $return[$foreignCurrencyId] = [ + 'amount' => '0', + 'count' => 0, + 'currency_id' => (int)$foreignCurrencyId, + 'currency_name' => $journal['foreign_currency_name'], + 'currency_code' => $journal['foreign_currency_code'], + 'currency_symbol' => $journal['foreign_currency_symbol'], + 'currency_decimal_places' => $journal['foreign_currency_decimal_places'], + ]; + + } + $return[$foreignCurrencyId]['count']++; + $return[$foreignCurrencyId]['amount'] = bcadd($return[$foreignCurrencyId]['amount'], $journal['foreign_amount']); + } + + } + + return $return; + } } diff --git a/app/Support/Http/Controllers/RenderPartialViews.php b/app/Support/Http/Controllers/RenderPartialViews.php index 2b74f5d64e..4605e9d8eb 100644 --- a/app/Support/Http/Controllers/RenderPartialViews.php +++ b/app/Support/Http/Controllers/RenderPartialViews.php @@ -30,7 +30,6 @@ use FireflyIII\Models\Budget; use FireflyIII\Models\Rule; use FireflyIII\Models\RuleAction; use FireflyIII\Models\RuleTrigger; -use FireflyIII\Models\Tag; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Budget\BudgetRepositoryInterface; use FireflyIII\Repositories\Category\CategoryRepositoryInterface; @@ -60,10 +59,11 @@ trait RenderPartialViews $set = new Collection; $names = $revenue->pluck('name')->toArray(); foreach ($expense as $exp) { - if (\in_array($exp->name, $names, true)) { + if (in_array($exp->name, $names, true)) { $set->push($exp); } } + // @codeCoverageIgnoreStart try { $result = view('reports.options.account', compact('set'))->render(); } catch (Throwable $e) { @@ -71,6 +71,8 @@ trait RenderPartialViews $result = 'Could not render view.'; } + // @codeCoverageIgnoreEnd + return $result; } @@ -94,7 +96,6 @@ trait RenderPartialViews /** @var PopupReportInterface $popupHelper */ $popupHelper = app(PopupReportInterface::class); - $budget = $budgetRepository->findNull((int)$attributes['budgetId']); $account = $accountRepository->findNull((int)$attributes['accountId']); @@ -114,12 +115,14 @@ trait RenderPartialViews // row with tag info. return 'Firefly cannot handle this type of info-button (BalanceLine::TagRole)'; } + // @codeCoverageIgnoreStart try { $view = view('popup.report.balance-amount', compact('journals', 'budget', 'account'))->render(); } catch (Throwable $e) { Log::error(sprintf('Could not render: %s', $e->getMessage())); $view = 'Firefly III could not render the view. Please see the log files.'; } + // @codeCoverageIgnoreEnd return $view; } @@ -134,6 +137,7 @@ trait RenderPartialViews /** @var BudgetRepositoryInterface $repository */ $repository = app(BudgetRepositoryInterface::class); $budgets = $repository->getBudgets(); + // @codeCoverageIgnoreStart try { $result = view('reports.options.budget', compact('budgets'))->render(); } catch (Throwable $e) { @@ -141,6 +145,8 @@ trait RenderPartialViews $result = 'Could not render view.'; } + // @codeCoverageIgnoreEnd + return $result; } @@ -164,12 +170,14 @@ trait RenderPartialViews $budget = new Budget; } $journals = $popupHelper->byBudget($budget, $attributes); + // @codeCoverageIgnoreStart try { $view = view('popup.report.budget-spent-amount', compact('journals', 'budget'))->render(); } catch (Throwable $e) { Log::error(sprintf('Could not render: %s', $e->getMessage())); $view = 'Firefly III could not render the view. Please see the log files.'; } + // @codeCoverageIgnoreEnd return $view; } @@ -195,12 +203,14 @@ trait RenderPartialViews } $journals = $popupHelper->byCategory($category, $attributes); + // @codeCoverageIgnoreStart try { $view = view('popup.report.category-entry', compact('journals', 'category'))->render(); } catch (Throwable $e) { Log::error(sprintf('Could not render: %s', $e->getMessage())); $view = 'Firefly III could not render the view. Please see the log files.'; } + // @codeCoverageIgnoreEnd return $view; } @@ -215,6 +225,7 @@ trait RenderPartialViews /** @var CategoryRepositoryInterface $repository */ $repository = app(CategoryRepositoryInterface::class); $categories = $repository->getCategories(); + // @codeCoverageIgnoreStart try { $result = view('reports.options.category', compact('categories'))->render(); } catch (Throwable $e) { @@ -222,6 +233,8 @@ trait RenderPartialViews $result = 'Could not render view.'; } + // @codeCoverageIgnoreEnd + return $result; } @@ -247,12 +260,14 @@ trait RenderPartialViews } $journals = $popupHelper->byExpenses($account, $attributes); + // @codeCoverageIgnoreStart try { $view = view('popup.report.expense-entry', compact('journals', 'account'))->render(); } catch (Throwable $e) { Log::error(sprintf('Could not render: %s', $e->getMessage())); $view = 'Firefly III could not render the view. Please see the log files.'; } + // @codeCoverageIgnoreEnd return $view; } @@ -350,7 +365,6 @@ trait RenderPartialViews /** @var PopupReportInterface $popupHelper */ $popupHelper = app(PopupReportInterface::class); - $account = $accountRepository->findNull((int)$attributes['accountId']); if (null === $account) { @@ -358,12 +372,14 @@ trait RenderPartialViews } $journals = $popupHelper->byIncome($account, $attributes); + // @codeCoverageIgnoreStart try { $view = view('popup.report.income-entry', compact('journals', 'account'))->render(); } catch (Throwable $e) { Log::error(sprintf('Could not render: %s', $e->getMessage())); $view = 'Firefly III could not render the view. Please see the log files.'; } + // @codeCoverageIgnoreEnd return $view; } @@ -375,6 +391,7 @@ trait RenderPartialViews */ protected function noReportOptions(): string // render a view { + // @codeCoverageIgnoreStart try { $result = view('reports.options.no-options')->render(); } catch (Throwable $e) { @@ -382,6 +399,8 @@ trait RenderPartialViews $result = 'Could not render view.'; } + // @codeCoverageIgnoreEnd + return $result; } @@ -394,11 +413,9 @@ trait RenderPartialViews { /** @var TagRepositoryInterface $repository */ $repository = app(TagRepositoryInterface::class); - $tags = $repository->get()->sortBy( - function (Tag $tag) { - return $tag->tag; - } - ); + $tags = $repository->get(); + + // @codeCoverageIgnoreStart try { $result = view('reports.options.tag', compact('tags'))->render(); } catch (Throwable $e) { @@ -406,6 +423,8 @@ trait RenderPartialViews $result = 'Could not render view.'; } + // @codeCoverageIgnoreEnd + return $result; } } diff --git a/app/Support/Http/Controllers/RequestInformation.php b/app/Support/Http/Controllers/RequestInformation.php index 4cb20510cd..419286429b 100644 --- a/app/Support/Http/Controllers/RequestInformation.php +++ b/app/Support/Http/Controllers/RequestInformation.php @@ -24,29 +24,18 @@ declare(strict_types=1); namespace FireflyIII\Support\Http\Controllers; use Carbon\Carbon; -use FireflyIII\Exceptions\FireflyException; use FireflyIII\Exceptions\ValidationException; -use FireflyIII\Helpers\Collector\TransactionCollectorInterface; use FireflyIII\Helpers\Help\HelpInterface; -use FireflyIII\Http\Requests\SplitJournalFormRequest; use FireflyIII\Http\Requests\TestRuleFormRequest; -use FireflyIII\Models\Transaction; -use FireflyIII\Models\TransactionJournal; -use FireflyIII\Models\TransactionType; -use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use FireflyIII\Support\Binder\AccountList; -use FireflyIII\Transformers\TransactionTransformer; use FireflyIII\User; use Hash; use Illuminate\Contracts\Validation\Validator as ValidatorContract; -use Illuminate\Http\Request; use Illuminate\Routing\Route; -use Illuminate\Support\Collection; use Illuminate\Support\Facades\Validator; use InvalidArgumentException; use Log; use Route as RouteFacade; -use Symfony\Component\HttpFoundation\ParameterBag; /** * Trait RequestInformation @@ -54,56 +43,7 @@ use Symfony\Component\HttpFoundation\ParameterBag; */ trait RequestInformation { - /** - * Create data-array from a journal. - * - * @param SplitJournalFormRequest|Request $request - * @param TransactionJournal $journal - * - * @return array - * @throws FireflyException - */ - protected function arrayFromJournal(Request $request, TransactionJournal $journal): array // convert user input. - { - /** @var JournalRepositoryInterface $repository */ - $repository = app(JournalRepositoryInterface::class); - $sourceAccounts = $repository->getJournalSourceAccounts($journal); - $destinationAccounts = $repository->getJournalDestinationAccounts($journal); - $array = [ - 'journal_description' => $request->old('journal_description', $journal->description), - 'journal_amount' => '0', - 'journal_foreign_amount' => '0', - 'sourceAccounts' => $sourceAccounts, - 'journal_source_id' => $request->old('journal_source_id', $sourceAccounts->first()->id), - 'journal_source_name' => $request->old('journal_source_name', $sourceAccounts->first()->name), - 'journal_destination_id' => $request->old('journal_destination_id', $destinationAccounts->first()->id), - 'destinationAccounts' => $destinationAccounts, - 'what' => strtolower($this->repository->getTransactionType($journal)), - 'date' => $request->old('date', $this->repository->getJournalDate($journal, null)), - 'tags' => implode(',', $journal->tags->pluck('tag')->toArray()), - // all custom fields: - 'interest_date' => $request->old('interest_date', $repository->getMetaField($journal, 'interest_date')), - 'book_date' => $request->old('book_date', $repository->getMetaField($journal, 'book_date')), - 'process_date' => $request->old('process_date', $repository->getMetaField($journal, 'process_date')), - 'due_date' => $request->old('due_date', $repository->getMetaField($journal, 'due_date')), - 'payment_date' => $request->old('payment_date', $repository->getMetaField($journal, 'payment_date')), - 'invoice_date' => $request->old('invoice_date', $repository->getMetaField($journal, 'invoice_date')), - 'internal_reference' => $request->old('internal_reference', $repository->getMetaField($journal, 'internal_reference')), - 'notes' => $request->old('notes', $repository->getNoteText($journal)), - - // transactions. - 'transactions' => $this->getTransactionDataFromJournal($journal), - ]; - // update transactions array with old request data. - $array['transactions'] = $this->updateWithPrevious($array['transactions'], $request->old()); - - // update journal amount and foreign amount: - $array['journal_amount'] = array_sum(array_column($array['transactions'], 'amount')); - $array['journal_foreign_amount'] = array_sum(array_column($array['transactions'], 'foreign_amount')); - - return $array; - } /** * Get the domain of FF system. @@ -165,7 +105,7 @@ trait RequestInformation } $baseHref = route('index'); $helpString = sprintf( - '

%s

', $baseHref, $originalLanguage, (string)trans('firefly.help_translating') + '

%s

', $baseHref, $originalLanguage, (string)trans('firefly.help_translating') ); $content = $helpString . $help->getFromGitHub($route, $language); } @@ -177,7 +117,7 @@ trait RequestInformation return $content; } - return '

' . trans('firefly.route_has_no_help') . '

'; + return '

' . trans('firefly.route_has_no_help') . '

'; // @codeCoverageIgnore } /** @@ -193,68 +133,6 @@ trait RequestInformation return $language; } - /** - * @return string - */ - protected function getPageName(): string // get request info - { - return str_replace('.', '_', RouteFacade::currentRouteName()); - } - - /** - * Get the specific name of a page for intro. - * - * @return string - */ - protected function getSpecificPageName(): string // get request info - { - return null === RouteFacade::current()->parameter('what') ? '' : '_' . RouteFacade::current()->parameter('what'); - } - - /** - * Get transaction overview from journal. - * - * @param TransactionJournal $journal - * - * @return array - * @throws FireflyException - * - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - */ - protected function getTransactionDataFromJournal(TransactionJournal $journal): array // convert object - { - // use collector to collect transactions. - $collector = app(TransactionCollectorInterface::class); - $collector->setUser(auth()->user()); - $collector->withOpposingAccount()->withCategoryInformation()->withBudgetInformation(); - // filter on specific journals. - $collector->setJournals(new Collection([$journal])); - $set = $collector->getTransactions(); - $transactions = []; - - /** @var TransactionTransformer $transformer */ - $transformer = app(TransactionTransformer::class); - $transformer->setParameters(new ParameterBag()); - /** @var Transaction $transaction */ - foreach ($set as $transaction) { - $res = []; - if ((float)$transaction->transaction_amount > 0 && $journal->transactionType->type === TransactionType::DEPOSIT) { - $res = $transformer->transform($transaction); - } - if ((float)$transaction->transaction_amount < 0 && $journal->transactionType->type !== TransactionType::DEPOSIT) { - $res = $transformer->transform($transaction); - } - - if (\count($res) > 0) { - $res['amount'] = app('steam')->positive((string)$res['amount']); - $res['foreign_amount'] = app('steam')->positive((string)$res['foreign_amount']); - $transactions[] = $res; - } - } - - return $transactions; - } - /** * Get a list of triggers. * @@ -266,7 +144,7 @@ trait RequestInformation { $triggers = []; $data = $request->get('triggers'); - if (\is_array($data)) { + if (is_array($data)) { foreach ($data as $index => $triggerInfo) { $triggers[] = [ 'type' => $triggerInfo['type'] ?? '', @@ -289,23 +167,46 @@ trait RequestInformation $page = $this->getPageName(); $specificPage = $this->getSpecificPageName(); + + // indicator if user has seen the help for this page ( + special page): - $key = 'shown_demo_' . $page . $specificPage; + $key = sprintf('shown_demo_%s%s', $page, $specificPage); // is there an intro for this route? - $intro = config('intro.' . $page) ?? []; - $specialIntro = config('intro.' . $page . $specificPage) ?? []; + $intro = config(sprintf('intro.%s', $page)) ?? []; + $specialIntro = config(sprintf('intro.%s%s', $page, $specificPage)) ?? []; // some routes have a "what" parameter, which indicates a special page: $shownDemo = true; // both must be array and either must be > 0 - if (\count($intro) > 0 || \count($specialIntro) > 0) { + if (count($intro) > 0 || count($specialIntro) > 0) { $shownDemo = app('preferences')->get($key, false)->data; - Log::debug(sprintf('Check if user has already seen intro with key "%s". Result is %d', $key, $shownDemo)); + //Log::debug(sprintf('Check if user has already seen intro with key "%s". Result is %s', $key, var_export($shownDemo, true))); + } + if (!is_bool($shownDemo)) { + $shownDemo = true; // @codeCoverageIgnore } return $shownDemo; } + /** + * @return string + */ + protected function getPageName(): string // get request info + { + return str_replace('.', '_', RouteFacade::currentRouteName()); + } + + /** + * Get the specific name of a page for intro. + * + * @return string + */ + protected function getSpecificPageName(): string // get request info + { + return null === RouteFacade::current()->parameter('objectType') ? '' : '_' . RouteFacade::current()->parameter('objectType'); + } + /** * Check if date is outside session range. * @@ -362,48 +263,10 @@ trait RequestInformation return $attributes; } - /** - * Get info from old input. - * - * @param $array - * @param $old - * - * @return array - * - * @SuppressWarnings(PHPMD.ExcessiveMethodLength) - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - */ - protected function updateWithPrevious($array, $old): array // update object with new info - { - if (0 === \count($old) || !isset($old['transactions'])) { - return $array; - } - $old = $old['transactions']; - - foreach ($old as $index => $row) { - if (isset($array[$index])) { - /** @noinspection SlowArrayOperationsInLoopInspection */ - $array[$index] = array_merge($array[$index], $row); - continue; - } - // take some info from first transaction, that should at least exist. - $array[$index] = $row; - $array[$index]['currency_id'] = $array[0]['currency_id']; - $array[$index]['currency_code'] = $array[0]['currency_code'] ?? ''; - $array[$index]['currency_symbol'] = $array[0]['currency_symbol'] ?? ''; - $array[$index]['foreign_amount'] = round($array[0]['foreign_destination_amount'] ?? '0', 12); - $array[$index]['foreign_currency_id'] = $array[0]['foreign_currency_id']; - $array[$index]['foreign_currency_code'] = $array[0]['foreign_currency_code']; - $array[$index]['foreign_currency_symbol'] = $array[0]['foreign_currency_symbol']; - } - - return $array; - } - /** * Validate users new password. * - * @param User $user + * @param User $user * @param string $current * @param string $new * @@ -430,6 +293,7 @@ trait RequestInformation * @param array $data * * @return ValidatorContract + * @codeCoverageIgnore */ protected function validator(array $data): ValidatorContract { diff --git a/app/Support/Http/Controllers/RuleManagement.php b/app/Support/Http/Controllers/RuleManagement.php index 60851e30b3..d27aba4151 100644 --- a/app/Support/Http/Controllers/RuleManagement.php +++ b/app/Support/Http/Controllers/RuleManagement.php @@ -89,14 +89,14 @@ trait RuleManagement * @param Request $request * * @return array - * + * @codeCoverageIgnore */ protected function getPreviousActions(Request $request): array { $index = 0; $triggers = []; $oldInput = $request->old('actions'); - if (\is_array($oldInput)) { + if (is_array($oldInput)) { foreach ($oldInput as $oldAction) { try { $triggers[] = view( @@ -123,13 +123,14 @@ trait RuleManagement * @param Request $request * * @return array + * @codeCoverageIgnore */ protected function getPreviousTriggers(Request $request): array { $index = 0; $triggers = []; $oldInput = $request->old('triggers'); - if (\is_array($oldInput)) { + if (is_array($oldInput)) { foreach ($oldInput as $oldTrigger) { try { $triggers[] = view( diff --git a/app/Support/Http/Controllers/TransactionCalculation.php b/app/Support/Http/Controllers/TransactionCalculation.php index 16280749b3..d4642e7c2a 100644 --- a/app/Support/Http/Controllers/TransactionCalculation.php +++ b/app/Support/Http/Controllers/TransactionCalculation.php @@ -24,11 +24,7 @@ declare(strict_types=1); namespace FireflyIII\Support\Http\Controllers; use Carbon\Carbon; -use FireflyIII\Helpers\Collector\TransactionCollectorInterface; -use FireflyIII\Helpers\Filter\NegativeAmountFilter; -use FireflyIII\Helpers\Filter\OpposingAccountFilter; -use FireflyIII\Helpers\Filter\PositiveAmountFilter; -use FireflyIII\Helpers\Filter\TransferFilter; +use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Models\TransactionType; use Illuminate\Support\Collection; @@ -43,18 +39,23 @@ trait TransactionCalculation * * @param Collection $accounts * @param Collection $opposing - * @param Carbon $start - * @param Carbon $end + * @param Carbon $start + * @param Carbon $end * - * @return Collection + * @return array */ - protected function getExpensesForOpposing(Collection $accounts, Collection $opposing, Carbon $start, Carbon $end): Collection // get data + augument + protected function getExpensesForOpposing(Collection $accounts, Collection $opposing, Carbon $start, Carbon $end): array { - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL])->setOpposingAccounts($opposing); + $total = $accounts->merge($opposing); - return $collector->getTransactions(); + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setAccounts($total) + ->setRange($start, $end) + ->withAccountInformation() + ->setTypes([TransactionType::WITHDRAWAL]); + + return $collector->getExtractedJournals(); } /** @@ -62,24 +63,21 @@ trait TransactionCalculation * * @param Collection $accounts * @param Collection $tags - * @param Carbon $start - * @param Carbon $end + * @param Carbon $start + * @param Carbon $end * - * @return Collection + * @return array * */ - protected function getExpensesForTags(Collection $accounts, Collection $tags, Carbon $start, Carbon $end): Collection // get data + augument + protected function getExpensesForTags(Collection $accounts, Collection $tags, Carbon $start, Carbon $end): array { - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER]) - ->setTags($tags)->withOpposingAccount(); - $collector->removeFilter(TransferFilter::class); + ->setTags($tags)->withAccountInformation(); - $collector->addFilter(OpposingAccountFilter::class); - $collector->addFilter(PositiveAmountFilter::class); - - return $collector->getTransactions(); + return $collector->getExtractedJournals(); } /** @@ -87,23 +85,19 @@ trait TransactionCalculation * * @param Collection $accounts * @param Collection $budgets - * @param Carbon $start - * @param Carbon $end + * @param Carbon $start + * @param Carbon $end * - * @return Collection + * @return array */ - protected function getExpensesInBudgets(Collection $accounts, Collection $budgets, Carbon $start, Carbon $end): Collection // get data + augment with info + protected function getExpensesInBudgets(Collection $accounts, Collection $budgets, Carbon $start, Carbon $end): array { - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); $collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER]) - ->setBudgets($budgets)->withOpposingAccount(); - $collector->removeFilter(TransferFilter::class); + ->setBudgets($budgets)->withAccountInformation(); - $collector->addFilter(OpposingAccountFilter::class); - $collector->addFilter(PositiveAmountFilter::class); - - return $collector->getTransactions(); + return $collector->getExtractedJournals(); } /** @@ -111,25 +105,25 @@ trait TransactionCalculation * * @param Collection $accounts * @param Collection $categories - * @param Carbon $start - * @param Carbon $end + * @param Carbon $start + * @param Carbon $end * - * @return Collection + * @return array * * */ - protected function getExpensesInCategories(Collection $accounts, Collection $categories, Carbon $start, Carbon $end): Collection // get data + augument + protected function getExpensesInCategories(Collection $accounts, Collection $categories, Carbon $start, Carbon $end): array { - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER]) - ->setCategories($categories)->withOpposingAccount(); - $collector->removeFilter(TransferFilter::class); + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector + ->setAccounts($accounts) + ->setRange($start, $end) + ->setTypes([TransactionType::WITHDRAWAL, TransactionType::TRANSFER]) + ->setCategories($categories) + ->withAccountInformation(); - $collector->addFilter(OpposingAccountFilter::class); - $collector->addFilter(PositiveAmountFilter::class); - - return $collector->getTransactions(); + return $collector->getExtractedJournals(); } /** @@ -137,22 +131,19 @@ trait TransactionCalculation * * @param Collection $accounts * @param Collection $categories - * @param Carbon $start - * @param Carbon $end + * @param Carbon $start + * @param Carbon $end * - * @return Collection + * @return array */ - protected function getIncomeForCategories(Collection $accounts, Collection $categories, Carbon $start, Carbon $end): Collection // get data + augument + protected function getIncomeForCategories(Collection $accounts, Collection $categories, Carbon $start, Carbon $end): array { - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); $collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::DEPOSIT, TransactionType::TRANSFER]) - ->setCategories($categories)->withOpposingAccount(); + ->setCategories($categories)->withAccountInformation(); - $collector->addFilter(OpposingAccountFilter::class); - $collector->addFilter(NegativeAmountFilter::class); - - return $collector->getTransactions(); + return $collector->getExtractedJournals(); } /** @@ -160,18 +151,19 @@ trait TransactionCalculation * * @param Collection $accounts * @param Collection $opposing - * @param Carbon $start - * @param Carbon $end + * @param Carbon $start + * @param Carbon $end * - * @return Collection + * @return array */ - protected function getIncomeForOpposing(Collection $accounts, Collection $opposing, Carbon $start, Carbon $end): Collection // get data + augument + protected function getIncomeForOpposing(Collection $accounts, Collection $opposing, Carbon $start, Carbon $end): array { - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::DEPOSIT])->setOpposingAccounts($opposing); + $total =$accounts->merge($opposing); + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setAccounts($total)->setRange($start, $end)->withAccountInformation()->setTypes([TransactionType::DEPOSIT]); - return $collector->getTransactions(); + return $collector->getExtractedJournals(); } /** @@ -179,24 +171,21 @@ trait TransactionCalculation * * @param Collection $accounts * @param Collection $tags - * @param Carbon $start - * @param Carbon $end + * @param Carbon $start + * @param Carbon $end * * @return Collection * * @SuppressWarnings(PHPMD.ExcessiveParameterList) */ - protected function getIncomeForTags(Collection $accounts, Collection $tags, Carbon $start, Carbon $end): Collection // get data + augument + protected function getIncomeForTags(Collection $accounts, Collection $tags, Carbon $start, Carbon $end): array { - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); $collector->setAccounts($accounts)->setRange($start, $end)->setTypes([TransactionType::DEPOSIT, TransactionType::TRANSFER]) - ->setTags($tags)->withOpposingAccount(); + ->setTags($tags)->withAccountInformation(); - $collector->addFilter(OpposingAccountFilter::class); - $collector->addFilter(NegativeAmountFilter::class); - - return $collector->getTransactions(); + return $collector->getExtractedJournals(); } } diff --git a/app/Support/Http/Controllers/UserNavigation.php b/app/Support/Http/Controllers/UserNavigation.php index 1cac4ed6b0..b23e90fc1f 100644 --- a/app/Support/Http/Controllers/UserNavigation.php +++ b/app/Support/Http/Controllers/UserNavigation.php @@ -51,41 +51,45 @@ trait UserNavigation protected function getPreviousUri(string $identifier): string { Log::debug(sprintf('Trying to retrieve URL stored under "%s"', $identifier)); - // "forbidden" words for specific identifiers: - // if these are in the previous URI, don't refer back there. - $array = [ - 'accounts.delete.uri' => '/accounts/show/', - 'transactions.delete.uri' => '/transactions/show/', - 'attachments.delete.uri' => '/attachments/show/', - 'bills.delete.uri' => '/bills/show/', - 'budgets.delete.uri' => '/budgets/show/', - 'categories.delete.uri' => '/categories/show/', - 'currencies.delete.uri' => '/currencies/show/', - 'piggy-banks.delete.uri' => '/piggy-banks/show/', - 'tags.delete.uri' => '/tags/show/', - 'rules.delete.uri' => '/rules/edit/', - 'transactions.mass-delete.uri' => '/transactions/show/', - ]; - $forbidden = $array[$identifier] ?? '/show/'; - Log::debug(sprintf('The forbidden word for %s is "%s"', $identifier, $forbidden)); - $uri = (string)session($identifier); Log::debug(sprintf('The URI is %s', $uri)); - if ( - !(false === strpos($identifier, 'delete')) - && !(false === strpos($uri, $forbidden))) { - $uri = $this->redirectUri; - Log::debug(sprintf('URI is now %s (identifier contains "delete")', $uri)); - } + if (!(false === strpos($uri, 'jscript'))) { $uri = $this->redirectUri; // @codeCoverageIgnore Log::debug(sprintf('URI is now %s (uri contains jscript)', $uri)); } - // more debug notes: - Log::debug(sprintf('strpos($identifier, "delete"): %s', var_export(strpos($identifier, 'delete'), true))); - Log::debug(sprintf('strpos($uri, $forbidden): %s', var_export(strpos($uri, $forbidden), true))); + // "forbidden" words for specific identifiers: + // if these are in the previous URI, don't refer back there. + // $array = [ + // 'accounts.delete.uri' => '/accounts/show/', + // 'transactions.delete.uri' => '/transactions/show/', + // 'attachments.delete.uri' => '/attachments/show/', + // 'bills.delete.uri' => '/bills/show/', + // 'budgets.delete.uri' => '/budgets/show/', + // 'categories.delete.uri' => '/categories/show/', + // 'currencies.delete.uri' => '/currencies/show/', + // 'piggy-banks.delete.uri' => '/piggy-banks/show/', + // 'tags.delete.uri' => '/tags/show/', + // 'rules.delete.uri' => '/rules/edit/', + // 'transactions.mass-delete.uri' => '/transactions/show/', + // ]; + //$forbidden = $array[$identifier] ?? '/show/'; + //Log::debug(sprintf('The forbidden word for %s is "%s"', $identifier, $forbidden)); + + // if ( + // !(false === strpos($identifier, 'delete')) + // && !(false === strpos($uri, $forbidden))) { + // $uri = $this->redirectUri; + // //Log::debug(sprintf('URI is now %s (identifier contains "delete")', $uri)); + // } + + + // more debug notes: + //Log::debug(sprintf('strpos($identifier, "delete"): %s', var_export(strpos($identifier, 'delete'), true))); + //Log::debug(sprintf('strpos($uri, $forbidden): %s', var_export(strpos($uri, $forbidden), true))); + Log::debug(sprintf('Return direct link %s', $uri)); return $uri; } @@ -95,6 +99,7 @@ trait UserNavigation * @param TransactionJournal $journal * * @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector + * @codeCoverageIgnore */ protected function redirectToAccount(TransactionJournal $journal) { @@ -103,7 +108,7 @@ trait UserNavigation /** @var Transaction $transaction */ foreach ($transactions as $transaction) { $account = $transaction->account; - if (\in_array($account->accountType->type, $valid, true)) { + if (in_array($account->accountType->type, $valid, true)) { return redirect(route('accounts.show', [$account->id])); } } @@ -118,6 +123,7 @@ trait UserNavigation * @param Account $account * * @return RedirectResponse|\Illuminate\Routing\Redirector + * @codeCoverageIgnore */ protected function redirectToOriginalAccount(Account $account) { @@ -143,21 +149,25 @@ trait UserNavigation } /** - * Remember previous URL. - * * @param string $identifier + * + * @return string|null */ - protected function rememberPreviousUri(string $identifier): void + protected function rememberPreviousUri(string $identifier): ?string { + $return = null; /** @var ViewErrorBag $errors */ $errors = session()->get('errors'); if (null === $errors || (null !== $errors && 0 === $errors->count())) { - $url = app('url')->previous(); - session()->put($identifier, $url); - Log::debug(sprintf('Will put previous URI in cache under key %s: %s', $identifier, $url)); + $return = app('url')->previous(); - return; + // TODO URL might not be one we *want* to remember. + + session()->put($identifier, $return); + //Log::debug(sprintf('Will put previous URI in cache under key %s: %s', $identifier, $url)); + //return; } - Log::debug(sprintf('The users session contains errors somehow so we will not remember the URI!: %s', var_export($errors, true))); + //Log::debug(sprintf('The users session contains errors somehow so we will not remember the URI!: %s', var_export($errors, true))); + return $return; } } diff --git a/app/Support/Import/Information/GetSpectreCustomerTrait.php b/app/Support/Import/Information/GetSpectreCustomerTrait.php index e638d7ce53..9f065359fc 100644 --- a/app/Support/Import/Information/GetSpectreCustomerTrait.php +++ b/app/Support/Import/Information/GetSpectreCustomerTrait.php @@ -32,7 +32,7 @@ use Log; /** * Trait GetSpectreCustomerTrait - * + * @codeCoverageIgnore */ trait GetSpectreCustomerTrait { @@ -88,7 +88,7 @@ trait GetSpectreCustomerTrait $request->call(); $customers = $request->getCustomers(); - Log::debug(sprintf('Found %d customer(s)', \count($customers))); + Log::debug(sprintf('Found %d customer(s)', count($customers))); /** @var Customer $current */ foreach ($customers as $current) { if ('default_ff3_customer' === $current->getIdentifier()) { diff --git a/app/Support/Import/Information/GetSpectreTokenTrait.php b/app/Support/Import/Information/GetSpectreTokenTrait.php index dd866cc814..e29c10f19f 100644 --- a/app/Support/Import/Information/GetSpectreTokenTrait.php +++ b/app/Support/Import/Information/GetSpectreTokenTrait.php @@ -31,7 +31,7 @@ use Log; /** * Trait GetSpectreTokenTrait - * + * @codeCoverageIgnore */ trait GetSpectreTokenTrait { diff --git a/app/Support/Import/JobConfiguration/Bunq/ChooseAccountsHandler.php b/app/Support/Import/JobConfiguration/Bunq/ChooseAccountsHandler.php index fe81936b11..48bf73f351 100644 --- a/app/Support/Import/JobConfiguration/Bunq/ChooseAccountsHandler.php +++ b/app/Support/Import/JobConfiguration/Bunq/ChooseAccountsHandler.php @@ -58,7 +58,7 @@ class ChooseAccountsHandler implements BunqJobConfigurationInterface { $config = $this->repository->getConfiguration($this->importJob); $mapping = $config['mapping'] ?? []; - $complete = \count($mapping) > 0; + $complete = count($mapping) > 0; if (true === $complete) { // move job to correct stage to download transactions $this->repository->setStage($this->importJob, 'go-for-import'); @@ -94,10 +94,10 @@ class ChooseAccountsHandler implements BunqJobConfigurationInterface */ $ibanToAsset = []; Log::debug('Going to map IBANs for easy mapping later on.'); - if (0 === \count($accounts)) { + if (0 === count($accounts)) { throw new FireflyException('No bunq accounts found. Import cannot continue.'); // @codeCoverageIgnore } - if (0 === \count($mapping)) { + if (0 === count($mapping)) { $messages = new MessageBag; $messages->add('nomap', (string)trans('import.bunq_no_mapping')); @@ -119,7 +119,7 @@ class ChooseAccountsHandler implements BunqJobConfigurationInterface Log::debug(sprintf('IBAN for bunq account #%d is "%s"', $bunqId, $bunqIban)); if (null !== $bunqIban) { - $ibanToAsset[$bunqIban] = $accountId; + $ibanToAsset[$bunqIban] = $accountId; // @codeCoverageIgnore } $final[$bunqId] = $accountId; } @@ -144,7 +144,7 @@ class ChooseAccountsHandler implements BunqJobConfigurationInterface { $config = $this->repository->getConfiguration($this->importJob); $accounts = $config['accounts'] ?? []; - if (0 === \count($accounts)) { + if (0 === count($accounts)) { throw new FireflyException('No bunq accounts found. Import cannot continue.'); // @codeCoverageIgnore } // list the users accounts: diff --git a/app/Support/Import/JobConfiguration/File/ConfigureMappingHandler.php b/app/Support/Import/JobConfiguration/File/ConfigureMappingHandler.php index e8803b31b0..9eedad889a 100644 --- a/app/Support/Import/JobConfiguration/File/ConfigureMappingHandler.php +++ b/app/Support/Import/JobConfiguration/File/ConfigureMappingHandler.php @@ -67,7 +67,7 @@ class ConfigureMappingHandler implements FileConfigurationInterface $specifics = $config['specifics'] ?? []; $names = array_keys($specifics); foreach ($names as $name) { - if (!\in_array($name, $validSpecifics, true)) { + if (!in_array($name, $validSpecifics, true)) { continue; } $class = config(sprintf('csv.import_specifics.%s', $name)); @@ -92,7 +92,7 @@ class ConfigureMappingHandler implements FileConfigurationInterface { $config = $this->importJob->configuration; - if (isset($data['mapping']) && \is_array($data['mapping'])) { + if (isset($data['mapping']) && is_array($data['mapping'])) { foreach ($data['mapping'] as $index => $array) { $config['column-mapping-config'][$index] = []; foreach ($array as $value => $mapId) { @@ -311,8 +311,8 @@ class ConfigureMappingHandler implements FileConfigurationInterface $columnConfig[$columnIndex]['values'] = array_unique($columnConfig[$columnIndex]['values']); asort($columnConfig[$columnIndex]['values']); // if the count of this array is zero, there is nothing to map. - if (0 === \count($columnConfig[$columnIndex]['values'])) { - unset($columnConfig[$columnIndex]); + if (0 === count($columnConfig[$columnIndex]['values'])) { + unset($columnConfig[$columnIndex]); // @codeCoverageIgnore } } @@ -331,7 +331,7 @@ class ConfigureMappingHandler implements FileConfigurationInterface { /** @var array $validColumns */ $validColumns = array_keys(config('csv.import_roles')); - if (!\in_array($name, $validColumns, true)) { + if (!in_array($name, $validColumns, true)) { $name = '_ignore'; } diff --git a/app/Support/Import/JobConfiguration/File/ConfigureRolesHandler.php b/app/Support/Import/JobConfiguration/File/ConfigureRolesHandler.php index c7e0fa14e0..3d5c9ca4c4 100644 --- a/app/Support/Import/JobConfiguration/File/ConfigureRolesHandler.php +++ b/app/Support/Import/JobConfiguration/File/ConfigureRolesHandler.php @@ -74,7 +74,7 @@ class ConfigureRolesHandler implements FileConfigurationInterface if ('_ignore' !== $role) { ++$assigned; } - if (\in_array($role, ['amount', 'amount_credit', 'amount_debit', 'amount_negated'])) { + if (in_array($role, ['amount', 'amount_credit', 'amount_debit', 'amount_negated'])) { $hasAmount = true; } if ('foreign-currency-code' === $role) { @@ -194,7 +194,7 @@ class ConfigureRolesHandler implements FileConfigurationInterface foreach ($records as $line) { $line = array_values($line); $line = $this->processSpecifics($config, $line); - $count = \count($line); + $count = count($line); $this->totalColumns = $count > $this->totalColumns ? $count : $this->totalColumns; $this->getExampleFromLine($line); } @@ -372,7 +372,7 @@ class ConfigureRolesHandler implements FileConfigurationInterface $specifics = $config['specifics'] ?? []; $names = array_keys($specifics); foreach ($names as $name) { - if (!\in_array($name, $validSpecifics, true)) { + if (!in_array($name, $validSpecifics, true)) { continue; } /** @var SpecificInterface $specific */ diff --git a/app/Support/Import/JobConfiguration/File/ConfigureUploadHandler.php b/app/Support/Import/JobConfiguration/File/ConfigureUploadHandler.php index 0c205ca4a2..473e6b9bfc 100644 --- a/app/Support/Import/JobConfiguration/File/ConfigureUploadHandler.php +++ b/app/Support/Import/JobConfiguration/File/ConfigureUploadHandler.php @@ -131,7 +131,7 @@ class ConfigureUploadHandler implements FileConfigurationInterface { $return = []; // check if specifics given are correct: - if (isset($data['specifics']) && \is_array($data['specifics'])) { + if (isset($data['specifics']) && is_array($data['specifics'])) { foreach ($data['specifics'] as $name) { // verify their content. diff --git a/app/Support/Import/JobConfiguration/FinTS/ChooseAccountHandler.php b/app/Support/Import/JobConfiguration/FinTS/ChooseAccountHandler.php index 67ac1caaab..e657526d8d 100644 --- a/app/Support/Import/JobConfiguration/FinTS/ChooseAccountHandler.php +++ b/app/Support/Import/JobConfiguration/FinTS/ChooseAccountHandler.php @@ -34,8 +34,8 @@ use FireflyIII\Support\FinTS\FinTS; use Illuminate\Support\MessageBag; /** - * * Class ChooseAccountHandler + * @codeCoverageIgnore */ class ChooseAccountHandler implements FinTSConfigurationInterface { diff --git a/app/Support/Import/JobConfiguration/FinTS/NewFinTSJobHandler.php b/app/Support/Import/JobConfiguration/FinTS/NewFinTSJobHandler.php index 51da2ed0f0..7fa93a0189 100644 --- a/app/Support/Import/JobConfiguration/FinTS/NewFinTSJobHandler.php +++ b/app/Support/Import/JobConfiguration/FinTS/NewFinTSJobHandler.php @@ -32,8 +32,8 @@ use Illuminate\Support\Facades\Crypt; use Illuminate\Support\MessageBag; /** - * * Class NewFinTSJobHandler + * @codeCoverageIgnore */ class NewFinTSJobHandler implements FinTSConfigurationInterface { @@ -65,7 +65,6 @@ class NewFinTSJobHandler implements FinTSConfigurationInterface $this->repository->setConfiguration($this->importJob, $config); - $incomplete = false; foreach ($config as $value) { $incomplete = '' === $value or $incomplete; diff --git a/app/Support/Import/JobConfiguration/Spectre/ChooseAccountsHandler.php b/app/Support/Import/JobConfiguration/Spectre/ChooseAccountsHandler.php index a781e1fb4d..268c789c36 100644 --- a/app/Support/Import/JobConfiguration/Spectre/ChooseAccountsHandler.php +++ b/app/Support/Import/JobConfiguration/Spectre/ChooseAccountsHandler.php @@ -63,7 +63,7 @@ class ChooseAccountsHandler implements SpectreJobConfigurationInterface Log::debug('Now in ChooseAccountsHandler::configurationComplete()'); $config = $this->importJob->configuration; $importAccounts = $config['account_mapping'] ?? []; - $complete = \count($importAccounts) > 0 && $importAccounts !== [0 => 0]; + $complete = count($importAccounts) > 0 && $importAccounts !== [0 => 0]; if ($complete) { Log::debug('Looks like user has mapped import accounts to Firefly III accounts', $importAccounts); $this->repository->setStage($this->importJob, 'go-for-import'); @@ -98,7 +98,7 @@ class ChooseAccountsHandler implements SpectreJobConfigurationInterface $config['account_mapping'] = $final; $config['apply-rules'] = $applyRules; $this->repository->setConfiguration($this->importJob, $config); - if ($final === [0 => 0] || 0 === \count($final)) { + if ($final === [0 => 0] || 0 === count($final)) { $messages->add('count', (string)trans('import.spectre_no_mapping')); } @@ -117,7 +117,7 @@ class ChooseAccountsHandler implements SpectreJobConfigurationInterface Log::debug('Now in ChooseAccountsHandler::getnextData()'); $config = $this->importJob->configuration; $accounts = $config['accounts'] ?? []; - if (0 === \count($accounts)) { + if (0 === count($accounts)) { throw new FireflyException('It seems you have no accounts with this bank. The import cannot continue.'); // @codeCoverageIgnore } $converted = []; @@ -129,7 +129,7 @@ class ChooseAccountsHandler implements SpectreJobConfigurationInterface $login = null; $logins = $config['all-logins'] ?? []; $selected = $config['selected-login'] ?? 0; - if (0 === \count($logins)) { + if (0 === count($logins)) { throw new FireflyException('It seems you have no configured logins in this import job. The import cannot continue.'); // @codeCoverageIgnore } Log::debug(sprintf('Selected login to use is %d', $selected)); diff --git a/app/Support/Import/JobConfiguration/Spectre/ChooseLoginHandler.php b/app/Support/Import/JobConfiguration/Spectre/ChooseLoginHandler.php index 39b6e5b565..e0ed2b6bf0 100644 --- a/app/Support/Import/JobConfiguration/Spectre/ChooseLoginHandler.php +++ b/app/Support/Import/JobConfiguration/Spectre/ChooseLoginHandler.php @@ -112,7 +112,7 @@ class ChooseLoginHandler implements SpectreJobConfigurationInterface $config = $this->importJob->configuration; $data = ['logins' => []]; $logins = $config['all-logins'] ?? []; - Log::debug(sprintf('Count of logins in configuration is %d.', \count($logins))); + Log::debug(sprintf('Count of logins in configuration is %d.', count($logins))); foreach ($logins as $login) { $data['logins'][] = new Login($login); } diff --git a/app/Support/Import/JobConfiguration/Ynab/SelectAccountsHandler.php b/app/Support/Import/JobConfiguration/Ynab/SelectAccountsHandler.php index 87dc940a14..1602a4de96 100644 --- a/app/Support/Import/JobConfiguration/Ynab/SelectAccountsHandler.php +++ b/app/Support/Import/JobConfiguration/Ynab/SelectAccountsHandler.php @@ -59,7 +59,7 @@ class SelectAccountsHandler implements YnabJobConfigurationInterface Log::debug('Now in SelectAccountsHandler::configurationComplete()'); $config = $this->importJob->configuration; $mapping = $config['mapping'] ?? []; - if (\count($mapping) > 0) { + if (count($mapping) > 0) { // mapping is complete. Log::debug('Looks like user has mapped YNAB accounts to Firefly III accounts', $mapping); $this->repository->setStage($this->importJob, 'go-for-import'); @@ -97,7 +97,7 @@ class SelectAccountsHandler implements YnabJobConfigurationInterface $config['mapping'] = $final; $config['apply-rules'] = $applyRules; $this->repository->setConfiguration($this->importJob, $config); - if ($final === ['' => 0] || 0 === \count($final)) { + if ($final === ['' => 0] || 0 === count($final)) { $messages->add('count', (string)trans('import.ynab_no_mapping')); } @@ -117,7 +117,7 @@ class SelectAccountsHandler implements YnabJobConfigurationInterface $config = $this->importJob->configuration; $ynabAccounts = $config['accounts'] ?? []; $budget = $this->getSelectedBudget(); - if (0 === \count($ynabAccounts)) { + if (0 === count($ynabAccounts)) { throw new FireflyException('It seems you have no accounts with this budget. The import cannot continue.'); // @codeCoverageIgnore } // list the users accounts: diff --git a/app/Support/Import/JobConfiguration/Ynab/SelectBudgetHandler.php b/app/Support/Import/JobConfiguration/Ynab/SelectBudgetHandler.php index 4a3ebadba7..e04ea1b84c 100644 --- a/app/Support/Import/JobConfiguration/Ynab/SelectBudgetHandler.php +++ b/app/Support/Import/JobConfiguration/Ynab/SelectBudgetHandler.php @@ -104,7 +104,7 @@ class SelectBudgetHandler implements YnabJobConfigurationInterface $budgets = $configuration['budgets'] ?? []; $available = []; $notAvailable = []; - $total = \count($budgets); + $total = count($budgets); foreach ($budgets as $budget) { if ($this->haveAssetWithCurrency($budget['currency_code'])) { Log::debug('Add budget to available list.'); diff --git a/app/Support/Import/Placeholder/ImportTransaction.php b/app/Support/Import/Placeholder/ImportTransaction.php index 6a4204b24a..2ae4d3b3f6 100644 --- a/app/Support/Import/Placeholder/ImportTransaction.php +++ b/app/Support/Import/Placeholder/ImportTransaction.php @@ -33,6 +33,7 @@ use Log; /** * Class ImportTransaction + * @codeCoverageIgnore */ class ImportTransaction { @@ -160,6 +161,29 @@ class ImportTransaction 'opposing-bic' => 'opposingBic', 'opposing-number' => 'opposingNumber', ]; + + $replaceOldRoles = [ + 'original-source' => 'original_source', + 'sepa-cc' => 'sepa_cc', + 'sepa-ct-op' => 'sepa_ct_op', + 'sepa-ct-id' => 'sepa_ct_id', + 'sepa-db' => 'sepa_db', + 'sepa-country' => 'sepa_country', + 'sepa-ep' => 'sepa_ep', + 'sepa-ci' => 'sepa_ci', + 'sepa-batch-id' => 'sepa_batch_id', + 'internal-reference' => 'internal_reference', + 'date-interest' => 'date_interest', + 'date-invoice' => 'date_invoice', + 'date-book' => 'date_book', + 'date-payment' => 'date_payment', + 'date-process' => 'date_process', + 'date-due' => 'date_due', + ]; + if (array_key_exists($role, $replaceOldRoles)) { + $role = $replaceOldRoles[$role]; + } + if (isset($basics[$role])) { $field = $basics[$role]; $this->$field = $columnValue->getValue(); @@ -185,18 +209,18 @@ class ImportTransaction return; } - $meta = ['sepa-ct-id', 'sepa-ct-op', 'sepa-db', 'sepa-cc', 'sepa-country', 'sepa-batch-id', 'sepa-ep', 'sepa-ci', 'internal-reference', 'date-interest', - 'date-invoice', 'date-book', 'date-payment', 'date-process', 'date-due', 'original-source']; + $meta = ['sepa_ct_id', 'sepa_ct_op', 'sepa_db', 'sepa_cc', 'sepa_country', 'sepa_batch_id', 'sepa_ep', 'sepa_ci', 'internal_reference', 'date_interest', + 'date_invoice', 'date_book', 'date_payment', 'date_process', 'date_due', 'original_source']; Log::debug(sprintf('Now going to check role "%s".', $role)); - if (\in_array($role, $meta, true)) { + if (in_array($role, $meta, true)) { Log::debug(sprintf('Role "%s" is in allowed meta roles, so store its value "%s".', $role, $columnValue->getValue())); $this->meta[$role] = $columnValue->getValue(); return; } - $modifiers = ['rabo-debit-credit', 'ing-debit-credit']; - if (\in_array($role, $modifiers, true)) { + $modifiers = ['generic-debit-credit', 'ing-debit-credit', 'rabo-debit-credit']; + if (in_array($role, $modifiers, true)) { $this->modifiers[$role] = $columnValue->getValue(); return; diff --git a/app/Support/Import/Routine/Bunq/PaymentConverter.php b/app/Support/Import/Routine/Bunq/PaymentConverter.php index 8345f8561c..c6928d062d 100644 --- a/app/Support/Import/Routine/Bunq/PaymentConverter.php +++ b/app/Support/Import/Routine/Bunq/PaymentConverter.php @@ -58,7 +58,7 @@ class PaymentConverter $this->importJobRepos = app(ImportJobRepositoryInterface::class); $this->accountFactory = app(AccountFactory::class); if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } @@ -228,8 +228,8 @@ class PaymentConverter 'iban' => $party->getIban(), 'name' => $party->getLabelUser()->getDisplayName(), 'account_type_id' => null, - 'accountType' => $expectedType, - 'virtualBalance' => null, + 'account_type' => $expectedType, + 'virtual_balance' => null, 'active' => true, ]; $account = $this->accountFactory->create($data); diff --git a/app/Support/Import/Routine/Bunq/StageImportDataHandler.php b/app/Support/Import/Routine/Bunq/StageImportDataHandler.php index 76ba7d42ab..5581066fd6 100644 --- a/app/Support/Import/Routine/Bunq/StageImportDataHandler.php +++ b/app/Support/Import/Routine/Bunq/StageImportDataHandler.php @@ -325,7 +325,7 @@ class StageImportDataHandler /* * After the loop, check if Firefly III must loop again. */ - Log::debug(sprintf('Count of result is now %d', \count($return))); + Log::debug(sprintf('Count of result is now %d', count($return))); $count++; if (null === $olderId) { Log::debug('Older ID is NULL, so stop looping cause we are done!'); @@ -351,7 +351,7 @@ class StageImportDataHandler // store newest and oldest tranasction ID to be used later: \Preferences::setForUser($this->importJob->user, sprintf('bunq-oldest-transaction-%d', $bunqAccountId), $oldestTransaction); \Preferences::setForUser($this->importJob->user, sprintf('bunq-newest-transaction-%d', $bunqAccountId), $newestTransaction); - Log::info(sprintf('Downloaded and parsed %d transactions from bunq.', \count($return))); + Log::info(sprintf('Downloaded and parsed %d transactions from bunq.', count($return))); return $return; } @@ -428,7 +428,7 @@ class StageImportDataHandler /* * After the loop, check if Firefly III must loop again. */ - Log::debug(sprintf('Count of result is now %d', \count($return))); + Log::debug(sprintf('Count of result is now %d', count($return))); $count++; if (null === $newerId) { Log::debug('Newer ID is NULL, so stop looping cause we are done!'); @@ -446,7 +446,7 @@ class StageImportDataHandler // store newest tranasction ID to be used later: \Preferences::setForUser($this->importJob->user, sprintf('bunq-newest-transaction-%d', $bunqAccountId), $newestTransaction); - Log::info(sprintf('Downloaded and parsed %d transactions from bunq.', \count($return))); + Log::info(sprintf('Downloaded and parsed %d transactions from bunq.', count($return))); return $return; } diff --git a/app/Support/Import/Routine/Bunq/StageNewHandler.php b/app/Support/Import/Routine/Bunq/StageNewHandler.php index 30933bea73..e37fcd71c6 100644 --- a/app/Support/Import/Routine/Bunq/StageNewHandler.php +++ b/app/Support/Import/Routine/Bunq/StageNewHandler.php @@ -111,7 +111,7 @@ class StageNewHandler // @codeCoverageIgnoreEnd if (null !== $object) { $array = null; - switch (\get_class($object)) { + switch (get_class($object)) { case MonetaryAccountBank::class: Log::debug('Going to convert a MonetaryAccountBank'); /** @var MonetaryAccountBank $object */ @@ -135,7 +135,7 @@ class StageNewHandler break; default: // @codeCoverageIgnoreStart - throw new FireflyException(sprintf('Bunq import routine cannot handle account of type "%s".', \get_class($object))); + throw new FireflyException(sprintf('Bunq import routine cannot handle account of type "%s".', get_class($object))); // @codeCoverageIgnoreEnd } if (null !== $array) { @@ -145,7 +145,7 @@ class StageNewHandler } } } - Log::info(sprintf('Found %d account(s) at bunq', \count($accounts)), $accounts); + Log::info(sprintf('Found %d account(s) at bunq', count($accounts)), $accounts); return $accounts; } @@ -224,7 +224,7 @@ class StageNewHandler Log::debug('Setting is not null.'); } if (null !== $maj->getAlias()) { - Log::debug(sprintf('Alias is not NULL. Count is %d', \count($maj->getAlias()))); + Log::debug(sprintf('Alias is not NULL. Count is %d', count($maj->getAlias()))); /** @var Pointer $alias */ foreach ($maj->getAlias() as $alias) { $return['aliases'][] = [ @@ -239,7 +239,7 @@ class StageNewHandler } } $coOwners = $maj->getAllCoOwner() ?? []; - Log::debug(sprintf('Count of getAllCoOwner is %d', \count($coOwners))); + Log::debug(sprintf('Count of getAllCoOwner is %d', count($coOwners))); /** @var CoOwner $coOwner */ foreach ($coOwners as $coOwner) { $alias = $coOwner->getAlias(); diff --git a/app/Support/Import/Routine/File/AssetAccountMapper.php b/app/Support/Import/Routine/File/AssetAccountMapper.php index 800ee89ebd..4c0e472b35 100644 --- a/app/Support/Import/Routine/File/AssetAccountMapper.php +++ b/app/Support/Import/Routine/File/AssetAccountMapper.php @@ -31,6 +31,7 @@ use Log; /** * Class AssetAccountMapper + * Can also handle liability accounts. */ class AssetAccountMapper { @@ -41,6 +42,17 @@ class AssetAccountMapper /** @var User */ private $user; + /** @var array */ + private $types; + + /** + * AssetAccountMapper constructor. + */ + public function __construct() + { + $this->types = [AccountType::ASSET, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE]; + } + /** * Based upon data in the importable, try to find or create the asset account account. * @@ -56,12 +68,12 @@ class AssetAccountMapper if ((int)$accountId > 0) { // find asset account with this ID: $result = $this->repository->findNull($accountId); - if (null !== $result && $result->accountType->type === AccountType::ASSET) { - Log::debug(sprintf('Found asset account "%s" based on given ID %d', $result->name, $accountId)); + if (null !== $result && in_array($result->accountType->type, $this->types, true)) { + Log::debug(sprintf('Found %s "%s" based on given ID %d', $result->accountType->type, $result->name, $accountId)); return $result; } - if (null !== $result && $result->accountType->type !== AccountType::ASSET) { + if (null !== $result && in_array($result->accountType->type, $this->types, true)) { Log::warning( sprintf('Found account "%s" based on given ID %d but its a %s, return nothing.', $result->name, $accountId, $result->accountType->type) ); @@ -76,8 +88,8 @@ class AssetAccountMapper Log::debug(sprintf('Array does not contain a value for %s. Continue', $field)); continue; } - $result = $this->repository->$function($value, [AccountType::ASSET]); - Log::debug(sprintf('Going to run %s() with argument "%s" (asset account)', $function, $value)); + $result = $this->repository->$function($value, $this->types); + Log::debug(sprintf('Going to run %s() with argument "%s" (asset account or liability)', $function, $value)); if (null !== $result) { Log::debug(sprintf('Found asset account "%s". Return it!', $result->name)); diff --git a/app/Support/Import/Routine/File/ImportableConverter.php b/app/Support/Import/Routine/File/ImportableConverter.php index 99c33680d6..69025d24f3 100644 --- a/app/Support/Import/Routine/File/ImportableConverter.php +++ b/app/Support/Import/Routine/File/ImportableConverter.php @@ -69,7 +69,7 @@ class ImportableConverter */ public function convert(array $importables): array { - $total = \count($importables); + $total = count($importables); Log::debug(sprintf('Going to convert %d import transactions', $total)); $result = []; /** @var ImportTransaction $importable */ @@ -89,6 +89,210 @@ class ImportableConverter return $result; } + /** + * @param ImportTransaction $importable + * + * @return array + * @throws FireflyException + */ + private function convertSingle(ImportTransaction $importable): array + { + Log::debug(sprintf('Description is: "%s"', $importable->description)); + $foreignAmount = $importable->calculateForeignAmount(); + $amount = $importable->calculateAmount(); + + Log::debug('All meta data: ', $importable->meta); + + if ('' === $amount) { + $amount = $foreignAmount; + } + if ('' === $amount) { + throw new FireflyException('No transaction amount information.'); + } + + $source = $this->assetMapper->map($importable->accountId, $importable->getAccountData()); + $destination = $this->opposingMapper->map($importable->opposingId, $amount, $importable->getOpposingAccountData()); + $currency = $this->currencyMapper->map($importable->currencyId, $importable->getCurrencyData()); + $foreignCurrency = $this->currencyMapper->map($importable->foreignCurrencyId, $importable->getForeignCurrencyData()); + + Log::debug(sprintf('"%s" (#%d) is source and "%s" (#%d) is destination.', $source->name, $source->id, $destination->name, $destination->id)); + + + // amount is positive? Then switch: + if (1 === bccomp($amount, '0')) { + [$destination, $source] = [$source, $destination]; + Log::debug( + sprintf( + '%s is positive, so "%s" (#%d) is now source and "%s" (#%d) is now destination.', + $amount, $source->name, $source->id, $destination->name, $destination->id + ) + ); + } + + if ($destination->id === $source->id) { + throw new FireflyException( + sprintf( + 'Source ("%s", #%d) and destination ("%s", #%d) are the same account.', $source->name, $source->id, $destination->name, $destination->id + ) + ); + } + + $transactionType = $this->getTransactionType($source->accountType->type, $destination->accountType->type); + $currency = $currency ?? $this->getCurrency($source, $destination); + + if ('unknown' === $transactionType) { + $message = sprintf( + 'Cannot determine transaction type. Source account is a %s, destination is a %s', $source->accountType->type, $destination->accountType->type + ); + Log::error($message, ['source' => $source->toArray(), 'dest' => $destination->toArray()]); + throw new FireflyException($message); + } + + return [ + + 'user' => $this->importJob->user_id, + 'group_title' => null, + 'transactions' => [ + [ + 'user' => $this->importJob->user_id, + 'type' => strtolower($transactionType), + 'date' => $this->convertDateValue($importable->date) ?? Carbon::now()->format('Y-m-d H:i:s'), + 'order' => 0, + + 'currency_id' => $currency->id, + 'currency_code' => null, + + 'foreign_currency_id' => $importable->foreignCurrencyId, + 'foreign_currency_code' => null === $foreignCurrency ? null : $foreignCurrency->code, + + 'amount' => $amount, + 'foreign_amount' => $foreignAmount, + + 'description' => $importable->description, + + 'source_id' => $source->id, + 'source_name' => null, + 'destination_id' => $destination->id, + 'destination_name' => null, + + 'budget_id' => $importable->budgetId, + 'budget_name' => $importable->budgetName, + + 'category_id' => $importable->categoryId, + 'category_name' => $importable->categoryName, + + 'bill_id' => $importable->billId, + 'bill_name' => $importable->billName, + + 'piggy_bank_id' => null, + 'piggy_bank_name' => null, + + 'reconciled' => false, + + 'notes' => $importable->note, + 'tags' => $importable->tags, + + 'internal_reference' => $importable->meta['internal-reference'] ?? null, + 'external_id' => $importable->externalId, + 'original_source' => $importable->meta['original-source'] ?? null, + + 'sepa_cc' => $importable->meta['sepa_cc'] ?? null, + 'sepa_ct_op' => $importable->meta['sepa_ct_op'] ?? null, + 'sepa_ct_id' => $importable->meta['sepa_ct_id'] ?? null, + 'sepa_db' => $importable->meta['sepa_db'] ?? null, + 'sepa_country' => $importable->meta['sepa_country'] ?? null, + 'sepa_ep' => $importable->meta['sepa_ep'] ?? null, + 'sepa_ci' => $importable->meta['sepa_ci'] ?? null, + 'sepa_batch_id' => $importable->meta['sepa_batch_id'] ?? null, + + 'interest_date' => $this->convertDateValue($importable->meta['date-interest'] ?? null), + 'book_date' => $this->convertDateValue($importable->meta['date-book'] ?? null), + 'process_date' => $this->convertDateValue($importable->meta['date-process'] ?? null), + 'due_date' => $this->convertDateValue($importable->meta['date-due'] ?? null), + 'payment_date' => $this->convertDateValue($importable->meta['date-payment'] ?? null), + 'invoice_date' => $this->convertDateValue($importable->meta['date-invoice'] ?? null), + ], + ], + ]; + + } + + /** + * @param string $source + * @param string $destination + * + * @return string + */ + private function getTransactionType(string $source, string $destination): string + { + $type = 'unknown'; + + $newType = config(sprintf('firefly.account_to_transaction.%s.%s', $source, $destination)); + if (null !== $newType) { + Log::debug(sprintf('Source is %s, destination is %s, so this is a %s.', $source, $destination, $newType)); + + return (string)$newType; + } + return $type; + } + + /** + * @param Account $source + * @param Account $destination + * + * @return TransactionCurrency + */ + private function getCurrency(Account $source, Account $destination): TransactionCurrency + { + $currency = null; + if ($destination->accountType->type === AccountType::ASSET) { + // destination is asset, might have currency preference: + $destinationCurrencyId = (int)$this->accountRepository->getMetaValue($destination, 'currency_id'); + $currency = 0 === $destinationCurrencyId ? $this->defaultCurrency : $this->currencyMapper->map($destinationCurrencyId, []); + Log::debug(sprintf('Destination is an asset account, and has currency preference %s', $currency->code)); + } + + if ($source->accountType->type === AccountType::ASSET) { + // source is asset, might have currency preference: + $sourceCurrencyId = (int)$this->accountRepository->getMetaValue($source, 'currency_id'); + $currency = 0 === $sourceCurrencyId ? $this->defaultCurrency : $this->currencyMapper->map($sourceCurrencyId, []); + Log::debug(sprintf('Source is an asset account, and has currency preference %s', $currency->code)); + } + if (null === $currency) { + Log::debug(sprintf('Could not map currency, use default (%s)', $this->defaultCurrency->code)); + $currency = $this->defaultCurrency; + } + + return $currency; + } + + /** + * @param string|null $date + * + * @return string|null + */ + private function convertDateValue(string $date = null): ?string + { + $result = null; + if (null !== $date) { + try { + // add exclamation mark for better parsing. http://php.net/manual/en/datetime.createfromformat.php + $dateFormat = $this->config['date-format'] ?? 'Ymd'; + if ('!' !== $dateFormat{0}) { + $dateFormat = '!' . $dateFormat; + } + $object = Carbon::createFromFormat($dateFormat, $date); + $result = $object->format('Y-m-d H:i:s'); + Log::debug(sprintf('createFromFormat: Turning "%s" into "%s" using "%s"', $date, $result, $this->config['date-format'] ?? 'Ymd')); + } catch (InvalidDateException|InvalidArgumentException $e) { + Log::error($e->getMessage()); + Log::error($e->getTraceAsString()); + } + } + + return $result; + } + /** * @param ImportJob $importJob */ @@ -129,204 +333,4 @@ class ImportableConverter { $this->mappedValues = $mappedValues; } - - /** - * @param string|null $date - * - * @return string|null - */ - private function convertDateValue(string $date = null): ?string - { - $result = null; - if (null !== $date) { - try { - // add exclamation mark for better parsing. http://php.net/manual/en/datetime.createfromformat.php - $dateFormat = $this->config['date-format'] ?? 'Ymd'; - if ('!' !== $dateFormat{0}) { - $dateFormat = '!' . $dateFormat; - } - $object = Carbon::createFromFormat($dateFormat, $date); - $result = $object->format('Y-m-d H:i:s'); - Log::debug(sprintf('createFromFormat: Turning "%s" into "%s" using "%s"', $date, $result, $this->config['date-format'] ?? 'Ymd')); - } catch (InvalidDateException|InvalidArgumentException $e) { - Log::error($e->getMessage()); - Log::error($e->getTraceAsString()); - } - } - - return $result; - } - - /** - * @param ImportTransaction $importable - * - * @throws FireflyException - * @return array - */ - private function convertSingle(ImportTransaction $importable): array - { - Log::debug(sprintf('Description is: "%s"', $importable->description)); - $foreignAmount = $importable->calculateForeignAmount(); - $amount = $importable->calculateAmount(); - - Log::debug('All meta data: ', $importable->meta); - - if ('' === $amount) { - $amount = $foreignAmount; - } - if ('' === $amount) { - throw new FireflyException('No transaction amount information.'); - } - - $source = $this->assetMapper->map($importable->accountId, $importable->getAccountData()); - $destination = $this->opposingMapper->map($importable->opposingId, $amount, $importable->getOpposingAccountData()); - $currency = $this->currencyMapper->map($importable->currencyId, $importable->getCurrencyData()); - $foreignCurrency = $this->currencyMapper->map($importable->foreignCurrencyId, $importable->getForeignCurrencyData()); - - Log::debug(sprintf('"%s" (#%d) is source and "%s" (#%d) is destination.', $source->name, $source->id, $destination->name, $destination->id)); - - - // amount is positive? Then switch: - if (1 === bccomp($amount, '0')) { - - [$destination, $source] = [$source, $destination]; - Log::debug( - sprintf( - '%s is positive, so "%s" (#%d) is now source and "%s" (#%d) is now destination.', - $amount, $source->name, $source->id, $destination->name, $destination->id - ) - ); - } - - if ($destination->id === $source->id) { - throw new FireflyException( - sprintf( - 'Source ("%s", #%d) and destination ("%s", #%d) are the same account.', $source->name, $source->id, $destination->name, $destination->id - ) - ); - } - - $transactionType = $this->getTransactionType($source->accountType->type, $destination->accountType->type); - $currency = $currency ?? $this->getCurrency($source, $destination); - - if ('unknown' === $transactionType) { - $message = sprintf( - 'Cannot determine transaction type. Source account is a %s, destination is a %s', $source->accountType->type, $destination->accountType->type - ); - Log::error($message, ['source' => $source->toArray(), 'dest' => $destination->toArray()]); - throw new FireflyException($message); - } - - return [ - 'type' => $transactionType, - 'date' => $this->convertDateValue($importable->date) ?? Carbon::now()->format('Y-m-d H:i:s'), - 'tags' => $importable->tags, - 'user' => $this->importJob->user_id, - 'notes' => $importable->note, - - // all custom fields: - 'internal_reference' => $importable->meta['internal-reference'] ?? null, - 'sepa-cc' => $importable->meta['sepa-cc'] ?? null, - 'sepa-ct-op' => $importable->meta['sepa-ct-op'] ?? null, - 'sepa-ct-id' => $importable->meta['sepa-ct-id'] ?? null, - 'sepa-db' => $importable->meta['sepa-db'] ?? null, - 'sepa-country' => $importable->meta['sepa-country'] ?? null, - 'sepa-ep' => $importable->meta['sepa-ep'] ?? null, - 'sepa-ci' => $importable->meta['sepa-ci'] ?? null, - 'sepa-batch-id' => $importable->meta['sepa-batch-id'] ?? null, - 'interest_date' => $this->convertDateValue($importable->meta['date-interest'] ?? null), - 'book_date' => $this->convertDateValue($importable->meta['date-book'] ?? null), - 'process_date' => $this->convertDateValue($importable->meta['date-process'] ?? null), - 'due_date' => $this->convertDateValue($importable->meta['date-due'] ?? null), - 'payment_date' => $this->convertDateValue($importable->meta['date-payment'] ?? null), - 'invoice_date' => $this->convertDateValue($importable->meta['date-invoice'] ?? null), - 'external_id' => $importable->externalId, - 'original-source' => $importable->meta['original-source'] ?? null, - // journal data: - 'description' => $importable->description, - 'piggy_bank_id' => null, - 'piggy_bank_name' => null, - 'bill_id' => $importable->billId, - 'bill_name' => $importable->billName, - - // transaction data: - 'transactions' => [ - [ - 'currency_id' => $currency->id, - 'currency_code' => null, - 'description' => null, - 'amount' => $amount, - 'budget_id' => $importable->budgetId, - 'budget_name' => $importable->budgetName, - 'category_id' => $importable->categoryId, - 'category_name' => $importable->categoryName, - 'source_id' => $source->id, - 'source_name' => null, - 'destination_id' => $destination->id, - 'destination_name' => null, - 'foreign_currency_id' => $importable->foreignCurrencyId, - 'foreign_currency_code' => null === $foreignCurrency ? null : $foreignCurrency->code, - 'foreign_amount' => $foreignAmount, - 'reconciled' => false, - 'identifier' => 0, - ], - ], - ]; - } - - /** - * @param Account $source - * @param Account $destination - * - * @return TransactionCurrency - */ - private function getCurrency(Account $source, Account $destination): TransactionCurrency - { - $currency = null; - if ($destination->accountType->type === AccountType::ASSET) { - // destination is asset, might have currency preference: - $destinationCurrencyId = (int)$this->accountRepository->getMetaValue($destination, 'currency_id'); - $currency = 0 === $destinationCurrencyId ? $this->defaultCurrency : $this->currencyMapper->map($destinationCurrencyId, []); - Log::debug(sprintf('Destination is an asset account, and has currency preference %s', $currency->code)); - } - - if ($source->accountType->type === AccountType::ASSET) { - // source is asset, might have currency preference: - $sourceCurrencyId = (int)$this->accountRepository->getMetaValue($source, 'currency_id'); - $currency = 0 === $sourceCurrencyId ? $this->defaultCurrency : $this->currencyMapper->map($sourceCurrencyId, []); - Log::debug(sprintf('Source is an asset account, and has currency preference %s', $currency->code)); - } - if (null === $currency) { - Log::debug(sprintf('Could not map currency, use default (%s)', $this->defaultCurrency->code)); - $currency = $this->defaultCurrency; - } - - return $currency; - } - - /** - * @param string $source - * @param string $destination - * - * @return string - */ - private function getTransactionType(string $source, string $destination): string - { - $type = 'unknown'; - - if ($source === AccountType::ASSET && $destination === AccountType::ASSET) { - Log::debug('Source and destination are asset accounts. This is a transfer.'); - $type = 'transfer'; - } - if ($source === AccountType::REVENUE) { - Log::debug('Source is a revenue account. This is a deposit.'); - $type = 'deposit'; - } - if ($destination === AccountType::EXPENSE) { - Log::debug('Destination is an expense account. This is a withdrawal.'); - $type = 'withdrawal'; - } - - return $type; - } } diff --git a/app/Support/Import/Routine/File/LineReader.php b/app/Support/Import/Routine/File/LineReader.php index 0953ec1ad8..ac0185248f 100644 --- a/app/Support/Import/Routine/File/LineReader.php +++ b/app/Support/Import/Routine/File/LineReader.php @@ -93,7 +93,7 @@ class LineReader $names = array_keys($specifics); $toApply = []; foreach ($names as $name) { - if (!\in_array($name, $validSpecifics, true)) { + if (!in_array($name, $validSpecifics, true)) { continue; } $class = config(sprintf('csv.import_specifics.%s', $name)); @@ -134,9 +134,22 @@ class LineReader $results = $stmt->process($reader); $lines = []; foreach ($results as $line) { - $lines[] = array_values($line); + + $lineValues = array_values($line); + + // do a first sanity check on whatever comes out of the CSV file. + array_walk( + $lineValues, static function ($element) { + $element = str_replace(' ', ' ', (string)$element); + + return $element; + } + ); + + $lines[] = $lineValues; } + return $lines; } diff --git a/app/Support/Import/Routine/File/MappedValuesValidator.php b/app/Support/Import/Routine/File/MappedValuesValidator.php index af3d35249d..c0bc90c704 100644 --- a/app/Support/Import/Routine/File/MappedValuesValidator.php +++ b/app/Support/Import/Routine/File/MappedValuesValidator.php @@ -90,7 +90,7 @@ class MappedValuesValidator foreach ($mappings as $role => $values) { Log::debug(sprintf('Now at role "%s"', $role)); $values = array_unique($values); - if (\count($values) > 0) { + if (count($values) > 0) { switch ($role) { default: throw new FireflyException(sprintf('Cannot validate mapped values for role "%s"', $role)); // @codeCoverageIgnore diff --git a/app/Support/Import/Routine/File/MappingConverger.php b/app/Support/Import/Routine/File/MappingConverger.php index c16afd9e2d..41bb5eae54 100644 --- a/app/Support/Import/Routine/File/MappingConverger.php +++ b/app/Support/Import/Routine/File/MappingConverger.php @@ -69,7 +69,7 @@ class MappingConverger { Log::debug('Start converging process.'); $collection = []; - $total = \count($lines); + $total = count($lines); /** @var array $line */ foreach ($lines as $lineIndex => $line) { Log::debug(sprintf('Now converging line %d out of %d.', $lineIndex + 1, $total)); diff --git a/app/Support/Import/Routine/File/OpposingAccountMapper.php b/app/Support/Import/Routine/File/OpposingAccountMapper.php index a6541785f3..36fe9e5dd6 100644 --- a/app/Support/Import/Routine/File/OpposingAccountMapper.php +++ b/app/Support/Import/Routine/File/OpposingAccountMapper.php @@ -60,15 +60,19 @@ class OpposingAccountMapper Log::debug(sprintf('Because amount is %s, will instead search for accounts of type %s', $amount, $expectedType)); } + // append expected types with liability types: + $expectedTypes = [$expectedType, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE]; + + if ((int)$accountId > 0) { // find any account with this ID: $result = $this->repository->findNull($accountId); - if (null !== $result && ($result->accountType->type === $expectedType || $result->accountType->type === AccountType::ASSET)) { + if (null !== $result && (in_array($result->accountType->type, $expectedTypes, true) || $result->accountType->type === AccountType::ASSET)) { Log::debug(sprintf('Found account "%s" (%s) based on given ID %d. Return it!', $result->name, $result->accountType->type, $accountId)); return $result; } - if (null !== $result && $result->accountType->type !== $expectedType) { + if (null !== $result && !in_array($result->accountType->type, $expectedTypes, true)) { Log::warning( sprintf( 'Found account "%s" (%s) based on given ID %d, but need a %s. Return nothing.', $result->name, $result->accountType->type, $accountId, @@ -90,7 +94,7 @@ class OpposingAccountMapper } // first search for $expectedType, then find asset: - $searchTypes = [$expectedType, AccountType::ASSET]; + $searchTypes = [$expectedType, AccountType::ASSET, AccountType::DEBT, AccountType::MORTGAGE, AccountType::LOAN]; foreach ($searchTypes as $type) { // find by (respectively): // IBAN, accountNumber, name, @@ -114,9 +118,9 @@ class OpposingAccountMapper $creation = [ 'name' => $data['name'] ?? '(no name)', 'iban' => $data['iban'] ?? null, - 'accountNumber' => $data['number'] ?? null, + 'account_number' => $data['number'] ?? null, 'account_type_id' => null, - 'accountType' => $expectedType, + 'account_type' => $expectedType, 'active' => true, 'BIC' => $data['bic'] ?? null, ]; diff --git a/app/Support/Import/Routine/Spectre/StageAuthenticatedHandler.php b/app/Support/Import/Routine/Spectre/StageAuthenticatedHandler.php index c4b08e7af1..3e5af0dbee 100644 --- a/app/Support/Import/Routine/Spectre/StageAuthenticatedHandler.php +++ b/app/Support/Import/Routine/Spectre/StageAuthenticatedHandler.php @@ -56,8 +56,8 @@ class StageAuthenticatedHandler // grab a list of logins. $config = $this->importJob->configuration; $logins = $config['all-logins'] ?? []; - Log::debug(sprintf('%d logins in config', \count($logins))); - if (0 === \count($logins)) { + Log::debug(sprintf('%d logins in config', count($logins))); + if (0 === count($logins)) { // get logins from Spectre. $logins = $this->getLogins(); $config['all-logins'] = $logins; @@ -115,7 +115,7 @@ class StageAuthenticatedHandler $request->setLogin($login); $request->call(); $accounts = $request->getAccounts(); - Log::debug(sprintf('Found %d accounts using login', \count($accounts))); + Log::debug(sprintf('Found %d accounts using login', count($accounts))); return $accounts; } @@ -137,7 +137,7 @@ class StageAuthenticatedHandler $logins = $request->getLogins(); $return = []; - Log::debug(sprintf('Found %d logins in users Spectre account.', \count($logins))); + Log::debug(sprintf('Found %d logins in users Spectre account.', count($logins))); /** @var Login $login */ foreach ($logins as $login) { diff --git a/app/Support/Import/Routine/Spectre/StageImportDataHandler.php b/app/Support/Import/Routine/Spectre/StageImportDataHandler.php index f92da203ed..e0849fff7d 100644 --- a/app/Support/Import/Routine/Spectre/StageImportDataHandler.php +++ b/app/Support/Import/Routine/Spectre/StageImportDataHandler.php @@ -59,8 +59,8 @@ class StageImportDataHandler Log::debug('Now in StageImportDataHandler::run()'); $config = $this->importJob->configuration; $accounts = $config['accounts'] ?? []; - Log::debug(sprintf('Count of accounts in array is %d', \count($accounts))); - if (0 === \count($accounts)) { + Log::debug(sprintf('Count of accounts in array is %d', count($accounts))); + if (0 === count($accounts)) { throw new FireflyException('There are no accounts in this import job. Cannot continue.'); // @codeCoverageIgnore } $toImport = $config['account_mapping'] ?? []; @@ -73,14 +73,15 @@ class StageImportDataHandler $merge = $this->getTransactions($spectreAccount, $localAccount); $totalSet[] = $merge; Log::debug( - sprintf('Found %d transactions in account "%s" (%s)', \count($merge), $spectreAccount->getName(), $spectreAccount->getCurrencyCode()) + sprintf('Found %d transactions in account "%s" (%s)', count($merge), $spectreAccount->getName(), $spectreAccount->getCurrencyCode()) ); continue; } Log::debug(sprintf('Local account is = zero, will not import from Spectr account with ID #%d', $spectreId)); } $totalSet = array_merge(...$totalSet); - Log::debug(sprintf('Found %d transactions in total.', \count($totalSet))); + Log::debug(sprintf('Found %d transactions in total.', count($totalSet))); + $this->repository->setTransactions($this->importJob, $totalSet); } @@ -112,8 +113,8 @@ class StageImportDataHandler private function convertToArray(array $transactions, SpectreAccount $spectreAccount, LocalAccount $originalSource): array { $array = []; - $total = \count($transactions); - Log::debug(sprintf('Now in StageImportDataHandler::convertToArray() with count %d', \count($transactions))); + $total = count($transactions); + Log::debug(sprintf('Now in StageImportDataHandler::convertToArray() with count %d', count($transactions))); /** @var SpectreTransaction $transaction */ foreach ($transactions as $index => $transaction) { Log::debug(sprintf('Now creating array for transaction %d of %d', $index + 1, $total)); @@ -174,35 +175,33 @@ class StageImportDataHandler } $entry = [ - 'type' => $type, - 'date' => $transaction->getMadeOn()->format('Y-m-d'), - 'tags' => $tags, - 'user' => $this->importJob->user_id, - 'notes' => $notes, - - // all custom fields: - 'external_id' => (string)$transaction->getId(), - - // journal data: - 'description' => $transaction->getDescription(), - 'piggy_bank_id' => null, - 'piggy_bank_name' => null, - 'bill_id' => null, - 'bill_name' => null, - 'original-source' => sprintf('spectre-v%s', config('firefly.version')), - // transaction data: 'transactions' => [ [ - 'currency_id' => null, - 'currency_code' => $currencyCode, - 'description' => null, - 'amount' => $amount, - 'budget_id' => null, - 'budget_name' => null, - 'category_id' => null, - 'category_name' => $transaction->getCategory(), - 'source_id' => $source->id, + 'date' => $transaction->getMadeOn()->format('Y-m-d'), + 'tags' => $tags, + 'user' => $this->importJob->user_id, + 'notes' => $notes, + + // all custom fields: + 'external_id' => (string)$transaction->getId(), + + // journal data: + 'description' => $transaction->getDescription(), + 'piggy_bank_id' => null, + 'piggy_bank_name' => null, + 'bill_id' => null, + 'bill_name' => null, + 'original-source' => sprintf('spectre-v%s', config('firefly.version')), + 'type' => $type, + 'currency_id' => null, + 'currency_code' => $currencyCode, + 'amount' => $amount, + 'budget_id' => null, + 'budget_name' => null, + 'category_id' => null, + 'category_name' => $transaction->getCategory(), + 'source_id' => $source->id, 'source_name' => null, 'destination_id' => $destination->id, 'destination_name' => null, @@ -216,7 +215,7 @@ class StageImportDataHandler ]; $array[] = $entry; } - Log::debug(sprintf('Return %d entries', \count($array))); + Log::debug(sprintf('Return %d entries', count($array))); return $array; } @@ -233,7 +232,7 @@ class StageImportDataHandler if (null === $account) { throw new FireflyException(sprintf('Cannot find Firefly III asset account with ID #%d. Job must stop now.', $accountId)); // @codeCoverageIgnore } - if (!\in_array($account->accountType->type, [AccountType::ASSET, AccountType::LOAN, AccountType::MORTGAGE, AccountType::DEBT], true)) { + if (!in_array($account->accountType->type, [AccountType::ASSET, AccountType::LOAN, AccountType::MORTGAGE, AccountType::DEBT], true)) { throw new FireflyException( sprintf('Account with ID #%d is not an asset/loan/mortgage/debt account. Job must stop now.', $accountId) ); // @codeCoverageIgnore diff --git a/app/Support/Import/Routine/Spectre/StageNewHandler.php b/app/Support/Import/Routine/Spectre/StageNewHandler.php index 74a789dc0f..7cb0d4795f 100644 --- a/app/Support/Import/Routine/Spectre/StageNewHandler.php +++ b/app/Support/Import/Routine/Spectre/StageNewHandler.php @@ -80,7 +80,7 @@ class StageNewHandler $list = $request->getLogins(); // count is zero? - $this->countLogins = \count($list); + $this->countLogins = count($list); Log::debug(sprintf('Number of logins is %d', $this->countLogins)); if ($this->countLogins > 0) { $store = []; diff --git a/app/Support/Import/Routine/Ynab/GetAccountsHandler.php b/app/Support/Import/Routine/Ynab/GetAccountsHandler.php index 75632769d5..998f6354ce 100644 --- a/app/Support/Import/Routine/Ynab/GetAccountsHandler.php +++ b/app/Support/Import/Routine/Ynab/GetAccountsHandler.php @@ -63,7 +63,7 @@ class GetAccountsHandler $request->call(); $config['accounts'] = $request->accounts; $this->repository->setConfiguration($this->importJob, $config); - if (0 === \count($config['accounts'])) { + if (0 === count($config['accounts'])) { throw new FireflyException('This budget contains zero accounts.'); } } diff --git a/app/Support/Import/Routine/Ynab/ImportDataHandler.php b/app/Support/Import/Routine/Ynab/ImportDataHandler.php index 1468b0e356..50fc1d5116 100644 --- a/app/Support/Import/Routine/Ynab/ImportDataHandler.php +++ b/app/Support/Import/Routine/Ynab/ImportDataHandler.php @@ -78,7 +78,7 @@ class ImportDataHandler } $totalSet = array_merge(...$total); - Log::debug(sprintf('Found %d transactions in total.', \count($totalSet))); + Log::debug(sprintf('Found %d transactions in total.', count($totalSet))); $this->repository->setTransactions($this->importJob, $totalSet); // assuming this works, store today's date as a preference @@ -114,9 +114,9 @@ class ImportDataHandler { $config = $this->repository->getConfiguration($this->importJob); $array = []; - $total = \count($transactions); + $total = count($transactions); $budget = $this->getSelectedBudget(); - Log::debug(sprintf('Now in StageImportDataHandler::convertToArray() with count %d', \count($transactions))); + Log::debug(sprintf('Now in StageImportDataHandler::convertToArray() with count %d', count($transactions))); /** @var array $transaction */ foreach ($transactions as $index => $transaction) { $description = $transaction['memo'] ?? '(empty)'; diff --git a/app/Support/Import/Routine/Ynab/StageGetBudgetsHandler.php b/app/Support/Import/Routine/Ynab/StageGetBudgetsHandler.php index bd0d6e2d6b..70142c5083 100644 --- a/app/Support/Import/Routine/Ynab/StageGetBudgetsHandler.php +++ b/app/Support/Import/Routine/Ynab/StageGetBudgetsHandler.php @@ -57,8 +57,8 @@ class StageGetBudgetsHandler // store budgets in users preferences. $configuration['budgets'] = $request->budgets; $this->repository->setConfiguration($this->importJob, $configuration); - Log::debug(sprintf('Found %d budgets', \count($request->budgets))); - if (0 === \count($request->budgets)) { + Log::debug(sprintf('Found %d budgets', count($request->budgets))); + if (0 === count($request->budgets)) { throw new FireflyException('It seems this user has zero budgets or an error prevented Firefly III from reading them.'); } } diff --git a/app/Support/Logging/AuditLogger.php b/app/Support/Logging/AuditLogger.php index 1c17d6c37c..9895f1017d 100644 --- a/app/Support/Logging/AuditLogger.php +++ b/app/Support/Logging/AuditLogger.php @@ -26,6 +26,7 @@ namespace FireflyIII\Support\Logging; /** * Class AuditLogger + * @codeCoverageIgnore */ class AuditLogger { diff --git a/app/Support/Logging/AuditProcessor.php b/app/Support/Logging/AuditProcessor.php index 823f5e57ff..831fd6099b 100644 --- a/app/Support/Logging/AuditProcessor.php +++ b/app/Support/Logging/AuditProcessor.php @@ -26,6 +26,7 @@ namespace FireflyIII\Support\Logging; /** * Class AuditProcessor + * @codeCoverageIgnore */ class AuditProcessor { diff --git a/app/Support/Navigation.php b/app/Support/Navigation.php index 850739d3de..1850087b94 100644 --- a/app/Support/Navigation.php +++ b/app/Support/Navigation.php @@ -24,8 +24,7 @@ namespace FireflyIII\Support; use Carbon\Carbon; use FireflyIII\Exceptions\FireflyException; -use FireflyIII\Helpers\FiscalHelperInterface; -use Log; +use FireflyIII\Helpers\Fiscal\FiscalHelperInterface; /** * Class Navigation. @@ -90,7 +89,7 @@ class Navigation $months = ['1M', 'month', 'monthly']; $difference = $date->month - $theDate->month; - if (2 === $difference && $date->day > 0 && \in_array($repeatFreq, $months, true)) { + if (2 === $difference && $date->day > 0 && in_array($repeatFreq, $months, true)) { $date->subDays($date->day); } @@ -227,7 +226,7 @@ class Navigation if (isset($modifierMap[$repeatFreq])) { $currentEnd->$function($modifierMap[$repeatFreq]); - if (\in_array($repeatFreq, $subDay, true)) { + if (in_array($repeatFreq, $subDay, true)) { $currentEnd->subDay(); } $currentEnd->endOfDay(); @@ -236,7 +235,7 @@ class Navigation } $currentEnd->$function(); $currentEnd->endOfDay(); - if (\in_array($repeatFreq, $subDay, true)) { + if (in_array($repeatFreq, $subDay, true)) { $currentEnd->subDay(); } @@ -307,16 +306,15 @@ class Navigation $displayFormat = (string)trans('config.year'); } + $begin = clone $start; $entries = []; while ($begin < $end) { $formatted = $begin->format($format); $displayed = $begin->formatLocalized($displayFormat); $entries[$formatted] = $displayed; - $begin->$increment(); } - return $entries; } @@ -540,7 +538,7 @@ class Navigation $subtract = $subtract ?? 1; $date = clone $theDate; // 1D 1W 1M 3M 6M 1Y - Log::debug(sprintf('subtractPeriod: date is %s, repeat frequency is %s and subtract is %d', $date->format('Y-m-d'), $repeatFreq, $subtract)); + //Log::debug(sprintf('subtractPeriod: date is %s, repeat frequency is %s and subtract is %d', $date->format('Y-m-d'), $repeatFreq, $subtract)); $functionMap = [ '1D' => 'subDays', 'daily' => 'subDays', @@ -564,16 +562,16 @@ class Navigation if (isset($functionMap[$repeatFreq])) { $function = $functionMap[$repeatFreq]; $date->$function($subtract); - Log::debug(sprintf('%s is in function map, execute %s with argument %d', $repeatFreq, $function, $subtract)); - Log::debug(sprintf('subtractPeriod: resulting date is %s', $date->format('Y-m-d'))); + //Log::debug(sprintf('%s is in function map, execute %s with argument %d', $repeatFreq, $function, $subtract)); + //Log::debug(sprintf('subtractPeriod: resulting date is %s', $date->format('Y-m-d'))); return $date; } if (isset($modifierMap[$repeatFreq])) { $subtract *= $modifierMap[$repeatFreq]; $date->subMonths($subtract); - Log::debug(sprintf('%s is in modifier map with value %d, execute subMonths with argument %d', $repeatFreq, $modifierMap[$repeatFreq], $subtract)); - Log::debug(sprintf('subtractPeriod: resulting date is %s', $date->format('Y-m-d'))); + //Log::debug(sprintf('%s is in modifier map with value %d, execute subMonths with argument %d', $repeatFreq, $modifierMap[$repeatFreq], $subtract)); + //Log::debug(sprintf('subtractPeriod: resulting date is %s', $date->format('Y-m-d'))); return $date; } @@ -586,10 +584,10 @@ class Navigation /** @var Carbon $tEnd */ $tEnd = session('end', Carbon::now()->endOfMonth()); $diffInDays = $tStart->diffInDays($tEnd); - Log::debug(sprintf('repeatFreq is %s, start is %s and end is %s (session data).', $repeatFreq, $tStart->format('Y-m-d'), $tEnd->format('Y-m-d'))); - Log::debug(sprintf('Diff in days is %d', $diffInDays)); + //Log::debug(sprintf('repeatFreq is %s, start is %s and end is %s (session data).', $repeatFreq, $tStart->format('Y-m-d'), $tEnd->format('Y-m-d'))); + //Log::debug(sprintf('Diff in days is %d', $diffInDays)); $date->subDays($diffInDays * $subtract); - Log::debug(sprintf('subtractPeriod: resulting date is %s', $date->format('Y-m-d'))); + //Log::debug(sprintf('subtractPeriod: resulting date is %s', $date->format('Y-m-d'))); return $date; } diff --git a/app/Support/NullArrayObject.php b/app/Support/NullArrayObject.php new file mode 100644 index 0000000000..a7d9fe98b1 --- /dev/null +++ b/app/Support/NullArrayObject.php @@ -0,0 +1,62 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Support; + + +use ArrayObject; + +/** + * Class NullArrayObject + * @codeCoverageIgnore + */ +class NullArrayObject extends ArrayObject +{ + public $default = null; + + /** + * NullArrayObject constructor. + * + * @param array $array + * @param null $default + */ + public function __construct(array $array, $default = null) + { + parent::__construct($array); + $this->default = $default; + } + + /** + * @param mixed $key + * + * @return mixed|null + */ + public function offsetGet($key) + { + if ($this->offsetExists($key)) { + return parent::offsetGet($key); + } + + return null; + } +} \ No newline at end of file diff --git a/app/Support/Preferences.php b/app/Support/Preferences.php index 09106f4c9a..72bd3a2f03 100644 --- a/app/Support/Preferences.php +++ b/app/Support/Preferences.php @@ -32,17 +32,21 @@ use Session; /** * Class Preferences. + * @codeCoverageIgnore */ class Preferences { /** - * @param User $user + * @param User $user * @param string $search * * @return Collection */ public function beginsWith(User $user, string $search): Collection { + if ('testing' === config('app.env')) { + Log::warning(sprintf('%s should NOT be called in the TEST environment!', __METHOD__)); + } $set = Preference::where('user_id', $user->id)->where('name', 'LIKE', $search . '%')->get(); return $set; @@ -55,6 +59,9 @@ class Preferences */ public function delete(string $name): bool { + if ('testing' === config('app.env')) { + Log::warning(sprintf('%s should NOT be called in the TEST environment!', __METHOD__)); + } $fullName = sprintf('preference%s%s', auth()->user()->id, $name); if (Cache::has($fullName)) { Cache::forget($fullName); @@ -76,21 +83,31 @@ class Preferences */ public function findByName(string $name): Collection { + if ('testing' === config('app.env')) { + Log::warning(sprintf('%s should NOT be called in the TEST environment!', __METHOD__)); + } + return Preference::where('name', $name)->get(); } /** * @param string $name - * @param mixed $default + * @param mixed $default * * @return \FireflyIII\Models\Preference|null */ public function get(string $name, $default = null): ?Preference { + if ('testing' === config('app.env')) { + Log::warning(sprintf('%s should NOT be called in the TEST environment!', __METHOD__)); + } /** @var User $user */ $user = auth()->user(); if (null === $user) { - return $default; + $preference = new Preference; + $preference->data = $default; + + return $preference; } return $this->getForUser($user, $name, $default); @@ -98,12 +115,15 @@ class Preferences /** * @param \FireflyIII\User $user - * @param array $list + * @param array $list * * @return array */ public function getArrayForUser(User $user, array $list): array { + if ('testing' === config('app.env')) { + Log::warning(sprintf('%s should NOT be called in the TEST environment!', __METHOD__)); + } $result = []; $preferences = Preference::where('user_id', $user->id)->whereIn('name', $list)->get(['id', 'name', 'data']); /** @var Preference $preference */ @@ -121,13 +141,16 @@ class Preferences /** * @param \FireflyIII\User $user - * @param string $name - * @param null|string $default + * @param string $name + * @param null|string $default * * @return \FireflyIII\Models\Preference|null */ public function getForUser(User $user, string $name, $default = null): ?Preference { + if ('testing' === config('app.env')) { + Log::warning(sprintf('%s should NOT be called in the TEST environment!', __METHOD__)); + } $fullName = sprintf('preference%s%s', $user->id, $name); if (Cache::has($fullName)) { return Cache::get($fullName); @@ -162,13 +185,16 @@ class Preferences */ public function lastActivity(): string { + if ('testing' === config('app.env')) { + Log::warning(sprintf('%s should NOT be called in the TEST environment!', __METHOD__)); + } $lastActivity = microtime(); $preference = $this->get('lastActivity', microtime()); if (null !== $preference && null !== $preference->data) { $lastActivity = $preference->data; } - if (\is_array($lastActivity)) { + if (is_array($lastActivity)) { $lastActivity = implode(',', $lastActivity); } @@ -186,7 +212,7 @@ class Preferences /** * @param string $name - * @param mixed $value + * @param mixed $value * * @return \FireflyIII\Models\Preference */ @@ -207,8 +233,8 @@ class Preferences /** * @param \FireflyIII\User $user - * @param string $name - * @param mixed $value + * @param string $name + * @param mixed $value * * @return Preference */ diff --git a/app/Support/Repositories/Recurring/FiltersWeekends.php b/app/Support/Repositories/Recurring/FiltersWeekends.php index b46daf1105..3c91f1947f 100644 --- a/app/Support/Repositories/Recurring/FiltersWeekends.php +++ b/app/Support/Repositories/Recurring/FiltersWeekends.php @@ -87,12 +87,12 @@ trait FiltersWeekends } // filter unique dates - Log::debug(sprintf('Count before filtering: %d', \count($dates))); + Log::debug(sprintf('Count before filtering: %d', count($dates))); $collection = new Collection($return); $filtered = $collection->unique(); $return = $filtered->toArray(); - Log::debug(sprintf('Count after filtering: %d', \count($return))); + Log::debug(sprintf('Count after filtering: %d', count($return))); return $return; } diff --git a/app/Support/Search/Modifier.php b/app/Support/Search/Modifier.php deleted file mode 100644 index 076d19a9e0..0000000000 --- a/app/Support/Search/Modifier.php +++ /dev/null @@ -1,203 +0,0 @@ -. - */ -declare(strict_types=1); - -namespace FireflyIII\Support\Search; - -use Carbon\Carbon; -use Exception; -use FireflyIII\Models\Transaction; -use Log; - -/** - * Class Modifier - */ -class Modifier -{ - /** - * @param Transaction $transaction - * @param string $amount - * @param int $expected - * - * @return bool - */ - public static function amountCompare(Transaction $transaction, string $amount, int $expected): bool - { - $amount = app('steam')->positive($amount); - $transactionAmount = app('steam')->positive($transaction->transaction_amount); - - $compare = bccomp($amount, $transactionAmount); - Log::debug(sprintf('%s vs %s is %d', $amount, $transactionAmount, $compare)); - - return $compare === $expected; - } - - /** - * @param array $modifier - * @param Transaction $transaction - * - * @return bool - * - * @SuppressWarnings(PHPMD.CyclomaticComplexity) - */ - public static function apply(array $modifier, Transaction $transaction): bool - { - $res = true; - switch ($modifier['type']) { - case 'source': - $name = $transaction->account_name; - $res = self::stringCompare($name, $modifier['value']); - Log::debug(sprintf('Source is %s? %s', $modifier['value'], var_export($res, true))); - break; - case 'destination': - $name = $transaction->opposing_account_name; - $res = self::stringCompare($name, $modifier['value']); - Log::debug(sprintf('Destination is %s? %s', $modifier['value'], var_export($res, true))); - break; - case 'category': - $res = self::category($transaction, $modifier['value']); - Log::debug(sprintf('Category is %s? %s', $modifier['value'], var_export($res, true))); - break; - case 'budget': - $res = self::budget($transaction, $modifier['value']); - Log::debug(sprintf('Budget is %s? %s', $modifier['value'], var_export($res, true))); - break; - case 'bill': - $name = $transaction->bill_name; - $res = self::stringCompare($name, $modifier['value']); - Log::debug(sprintf('Bill is %s? %s', $modifier['value'], var_export($res, true))); - break; - } - - return $res; - } - - /** - * @param Carbon $date - * @param string $compare - * - * @return bool - */ - public static function dateAfter(Carbon $date, string $compare): bool - { - try { - $compareDate = new Carbon($compare); - } catch (Exception $e) { - Log::debug(sprintf('Not interesting in Modifier:dateAfter(): %s', $e->getMessage())); - - return false; - } - - return $date->greaterThanOrEqualTo($compareDate); - } - - /** - * @param Carbon $date - * @param string $compare - * - * @return bool - */ - public static function dateBefore(Carbon $date, string $compare): bool - { - try { - $compareDate = new Carbon($compare); - } catch (Exception $e) { - Log::debug(sprintf('Not interesting in modifier:dateBefore(): %s', $e->getMessage())); - - return false; - } - - return $date->lessThanOrEqualTo($compareDate); - } - - /** - * @param Carbon $date - * @param string $compare - * - * @return bool - */ - public static function sameDate(Carbon $date, string $compare): bool - { - try { - $compareDate = new Carbon($compare); - } catch (Exception $e) { - Log::debug(sprintf('Not interesting in Modifier:sameDate(): %s', $e->getMessage())); - - return false; - } - - return $compareDate->isSameDay($date); - } - - /** - * @param string $haystack - * @param string $needle - * - * @return bool - */ - public static function stringCompare(string $haystack, string $needle): bool - { - $res = !(false === stripos($haystack, $needle)); - Log::debug(sprintf('"%s" is in "%s"? %s', $needle, $haystack, var_export($res, true))); - - return $res; - } - - /** - * @param Transaction $transaction - * @param string $search - * - * @return bool - */ - private static function budget(Transaction $transaction, string $search): bool - { - $journalBudget = ''; - if (null !== $transaction->transaction_journal_budget_name) { - $journalBudget = $transaction->transaction_journal_budget_name; - } - $transactionBudget = ''; - if (null !== $transaction->transaction_budget_name) { - $journalBudget = $transaction->transaction_budget_name; - } - - return self::stringCompare($journalBudget, $search) || self::stringCompare($transactionBudget, $search); - } - - /** - * @param Transaction $transaction - * @param string $search - * - * @return bool - */ - private static function category(Transaction $transaction, string $search): bool - { - $journalCategory = ''; - if (null !== $transaction->transaction_journal_category_name) { - $journalCategory = $transaction->transaction_journal_category_name; - } - $transactionCategory = ''; - if (null !== $transaction->transaction_category_name) { - $journalCategory = $transaction->transaction_category_name; - } - - return self::stringCompare($journalCategory, $search) || self::stringCompare($transactionCategory, $search); - } -} diff --git a/app/Support/Search/Search.php b/app/Support/Search/Search.php index b0c8ed1909..7d62c2cff7 100644 --- a/app/Support/Search/Search.php +++ b/app/Support/Search/Search.php @@ -23,9 +23,7 @@ declare(strict_types=1); namespace FireflyIII\Support\Search; use Carbon\Carbon; -use FireflyIII\Helpers\Collector\TransactionCollectorInterface; -use FireflyIII\Helpers\Filter\DoubleTransactionFilter; -use FireflyIII\Helpers\Filter\InternalTransferFilter; +use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Models\AccountType; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\Bill\BillRepositoryInterface; @@ -78,7 +76,7 @@ class Search implements SearchInterface $this->billRepository = app(BillRepositoryInterface::class); if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } @@ -132,6 +130,22 @@ class Search implements SearchInterface } } + /** + * @param string $string + */ + private function extractModifier(string $string): void + { + $parts = explode(':', $string); + if (2 === count($parts) && '' !== trim((string)$parts[1]) && '' !== trim((string)$parts[0])) { + $type = trim((string)$parts[0]); + $value = trim((string)$parts[1]); + if (in_array($type, $this->validModifiers, true)) { + // filter for valid type + $this->modifiers->push(['type' => $type, 'value' => $value]); + } + } + } + /** * @return float */ @@ -149,75 +163,54 @@ class Search implements SearchInterface $pageSize = 50; $page = 1; - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setAllAssetAccounts()->setLimit($pageSize)->setPage($page)->withOpposingAccount(); - if ($this->hasModifiers()) { - $collector->withOpposingAccount()->withCategoryInformation()->withBudgetInformation(); - } + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + $collector->setLimit($pageSize)->setPage($page)->withAccountInformation(); + $collector->withCategoryInformation()->withBudgetInformation(); $collector->setSearchWords($this->words); - $collector->removeFilter(InternalTransferFilter::class); - $collector->addFilter(DoubleTransactionFilter::class); // Most modifiers can be applied to the collector directly. $collector = $this->applyModifiers($collector); - return $collector->getPaginatedTransactions(); + return $collector->getPaginatedGroups(); } /** - * @param int $limit - */ - public function setLimit(int $limit): void - { - $this->limit = $limit; - } - - /** - * @param User $user - */ - public function setUser(User $user): void - { - $this->user = $user; - $this->accountRepository->setUser($user); - $this->billRepository->setUser($user); - $this->categoryRepository->setUser($user); - $this->budgetRepository->setUser($user); - } - - /** - * @param TransactionCollectorInterface $collector + * @param GroupCollectorInterface $collector * - * @return TransactionCollectorInterface + * @return GroupCollectorInterface * @SuppressWarnings(PHPMD.CyclomaticComplexity) */ - private function applyModifiers(TransactionCollectorInterface $collector): TransactionCollectorInterface + private function applyModifiers(GroupCollectorInterface $collector): GroupCollectorInterface { /* * TODO: * 'bill', */ + $totalAccounts = new Collection; foreach ($this->modifiers as $modifier) { switch ($modifier['type']) { default: die(sprintf('unsupported modifier: "%s"', $modifier['type'])); + case 'from': case 'source': // source can only be asset, liability or revenue account: $searchTypes = [AccountType::ASSET, AccountType::MORTGAGE, AccountType::LOAN, AccountType::DEBT, AccountType::REVENUE]; $accounts = $this->accountRepository->searchAccount($modifier['value'], $searchTypes); if ($accounts->count() > 0) { - $collector->setAccounts($accounts); + $totalAccounts = $accounts->merge($totalAccounts); } break; + case 'to': case 'destination': // source can only be asset, liability or expense account: $searchTypes = [AccountType::ASSET, AccountType::MORTGAGE, AccountType::LOAN, AccountType::DEBT, AccountType::EXPENSE]; $accounts = $this->accountRepository->searchAccount($modifier['value'], $searchTypes); if ($accounts->count() > 0) { - $collector->setOpposingAccounts($accounts); + $totalAccounts = $accounts->merge($totalAccounts); } break; case 'category': @@ -280,23 +273,28 @@ class Search implements SearchInterface break; } } + $collector->setAccounts($totalAccounts); return $collector; } /** - * @param string $string + * @param int $limit */ - private function extractModifier(string $string): void + public function setLimit(int $limit): void { - $parts = explode(':', $string); - if (2 === \count($parts) && '' !== trim((string)$parts[1]) && '' !== trim((string)$parts[0])) { - $type = trim((string)$parts[0]); - $value = trim((string)$parts[1]); - if (\in_array($type, $this->validModifiers, true)) { - // filter for valid type - $this->modifiers->push(['type' => $type, 'value' => $value]); - } - } + $this->limit = $limit; + } + + /** + * @param User $user + */ + public function setUser(User $user): void + { + $this->user = $user; + $this->accountRepository->setUser($user); + $this->billRepository->setUser($user); + $this->categoryRepository->setUser($user); + $this->budgetRepository->setUser($user); } } diff --git a/app/Support/Steam.php b/app/Support/Steam.php index 5de283f50d..1333c22c71 100644 --- a/app/Support/Steam.php +++ b/app/Support/Steam.php @@ -28,10 +28,12 @@ use FireflyIII\Models\Account; use FireflyIII\Models\Transaction; use FireflyIII\Repositories\Account\AccountRepositoryInterface; use Illuminate\Support\Collection; +use Log; use stdClass; /** * Class Steam. + * @codeCoverageIgnore */ class Steam { @@ -44,13 +46,16 @@ class Steam */ public function balance(Account $account, Carbon $date): string { + if ('testing' === config('app.env')) { + Log::warning(sprintf('%s should NOT be called in the TEST environment!', __METHOD__)); + } // abuse chart properties: $cache = new CacheProperties; $cache->addProperty($account->id); $cache->addProperty('balance'); $cache->addProperty($date); if ($cache->has()) { - return $cache->get(); // @codeCoverageIgnore + //return $cache->get(); // @codeCoverageIgnore } // /** @var AccountRepositoryInterface $repository */ @@ -93,6 +98,9 @@ class Steam */ public function balanceIgnoreVirtual(Account $account, Carbon $date): string { + if ('testing' === config('app.env')) { + Log::warning(sprintf('%s should NOT be called in the TEST environment!', __METHOD__)); + } // abuse chart properties: $cache = new CacheProperties; $cache->addProperty($account->id); @@ -139,6 +147,9 @@ class Steam */ public function balanceInRange(Account $account, Carbon $start, Carbon $end): array { + if ('testing' === config('app.env')) { + Log::warning(sprintf('%s should NOT be called in the TEST environment!', __METHOD__)); + } // abuse chart properties: $cache = new CacheProperties; $cache->addProperty($account->id); @@ -225,7 +236,9 @@ class Steam */ public function balancePerCurrency(Account $account, Carbon $date): array { - + if ('testing' === config('app.env')) { + Log::warning(sprintf('%s should NOT be called in the TEST environment!', __METHOD__)); + } // abuse chart properties: $cache = new CacheProperties; $cache->addProperty($account->id); @@ -259,6 +272,9 @@ class Steam */ public function balancesByAccounts(Collection $accounts, Carbon $date): array { + if ('testing' === config('app.env')) { + Log::warning(sprintf('%s should NOT be called in the TEST environment!', __METHOD__)); + } $ids = $accounts->pluck('id')->toArray(); // cache this property. $cache = new CacheProperties; @@ -291,6 +307,9 @@ class Steam */ public function balancesPerCurrencyByAccounts(Collection $accounts, Carbon $date): array { + if ('testing' === config('app.env')) { + Log::warning(sprintf('%s should NOT be called in the TEST environment!', __METHOD__)); + } $ids = $accounts->pluck('id')->toArray(); // cache this property. $cache = new CacheProperties; @@ -371,6 +390,7 @@ class Steam ]; $replace = "\x20"; // plain old normal space $string = str_replace($search, $replace, $string); + $string = str_replace(["\n", "\t", "\r"], "\x20", $string); return trim($string); } @@ -382,6 +402,9 @@ class Steam */ public function getLastActivities(array $accounts): array { + if ('testing' === config('app.env')) { + Log::warning(sprintf('%s should NOT be called in the TEST environment!', __METHOD__)); + } $list = []; $set = auth()->user()->transactions() diff --git a/app/Support/Twig/AmountFormat.php b/app/Support/Twig/AmountFormat.php index 9992740d8e..ad712e781f 100644 --- a/app/Support/Twig/AmountFormat.php +++ b/app/Support/Twig/AmountFormat.php @@ -25,7 +25,7 @@ namespace FireflyIII\Support\Twig; use FireflyIII\Models\Account as AccountModel; use FireflyIII\Models\TransactionCurrency; use FireflyIII\Repositories\Account\AccountRepositoryInterface; -use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface; +use Log; use Twig_Extension; use Twig_SimpleFilter; use Twig_SimpleFunction; @@ -83,21 +83,11 @@ class AmountFormat extends Twig_Extension { return new Twig_SimpleFunction( 'formatAmountByAccount', - function (AccountModel $account, string $amount, bool $coloured = null): string { + static function (AccountModel $account, string $amount, bool $coloured = null): string { $coloured = $coloured ?? true; /** @var AccountRepositoryInterface $accountRepos */ $accountRepos = app(AccountRepositoryInterface::class); - /** @var CurrencyRepositoryInterface $currencyRepos */ - $currencyRepos = app(CurrencyRepositoryInterface::class); - $currency = app('amount')->getDefaultCurrency(); - $currencyId = (int)$accountRepos->getMetaValue($account, 'currency_id'); - $accountCurrency = null; - if (0 !== $currencyId) { - $accountCurrency = $currencyRepos->findNull($currencyId); - } - if (null !== $accountCurrency) { - $currency = $accountCurrency; - } + $currency = $accountRepos->getAccountCurrency($account) ?? app('amount')->getDefaultCurrency(); return app('amount')->formatAnything($currency, $amount, $coloured); }, diff --git a/app/Support/Twig/Extension/Account.php b/app/Support/Twig/Extension/Account.php deleted file mode 100644 index c3e61b32ba..0000000000 --- a/app/Support/Twig/Extension/Account.php +++ /dev/null @@ -1,51 +0,0 @@ -. - */ -declare(strict_types=1); - -namespace FireflyIII\Support\Twig\Extension; - -use FireflyIII\Models\Account as AccountModel; -use FireflyIII\Repositories\Account\AccountRepositoryInterface; -use Twig_Extension; - -/** - * Class Account. - */ -class Account extends Twig_Extension -{ - /** - * @param AccountModel $account - * @param string $field - * - * @return string - */ - public function getMetaField(AccountModel $account, string $field): string - { - /** @var AccountRepositoryInterface $repository */ - $repository = app(AccountRepositoryInterface::class); - $result = $repository->getMetaValue($account, $field); - if (null === $result) { - return ''; - } - - return $result; - } -} diff --git a/app/Support/Twig/Extension/Transaction.php b/app/Support/Twig/Extension/Transaction.php deleted file mode 100644 index 8894eff09c..0000000000 --- a/app/Support/Twig/Extension/Transaction.php +++ /dev/null @@ -1,426 +0,0 @@ -. - */ -declare(strict_types=1); - -namespace FireflyIII\Support\Twig\Extension; - -use FireflyIII\Models\AccountType; -use FireflyIII\Models\Attachment; -use FireflyIII\Models\Transaction as TransactionModel; -use FireflyIII\Models\TransactionCurrency; -use FireflyIII\Models\TransactionJournal; -use FireflyIII\Models\TransactionType; -use Lang; -use Log; -use Twig_Extension; - -/** - * Class Transaction. - */ -class Transaction extends Twig_Extension -{ - /** - * Can show the amount of a transaction, if that transaction has been collected by the journal collector. - * - * @param TransactionModel $transaction - * - * @return string - */ - public function amount(TransactionModel $transaction): string - { - // at this point amount is always negative. - $amount = bcmul(app('steam')->positive((string)$transaction->transaction_amount), '-1'); - $format = '%s'; - $coloured = true; - - if (TransactionType::RECONCILIATION === $transaction->transaction_type_type && 1 === bccomp((string)$transaction->transaction_amount, '0')) { - $amount = bcmul($amount, '-1'); - } - - if (TransactionType::DEPOSIT === $transaction->transaction_type_type) { - $amount = bcmul($amount, '-1'); - } - - if (TransactionType::TRANSFER === $transaction->transaction_type_type) { - $amount = app('steam')->positive($amount); - $coloured = false; - $format = '%s'; - } - if (TransactionType::OPENING_BALANCE === $transaction->transaction_type_type) { - $amount = (string)$transaction->transaction_amount; - } - - $currency = new TransactionCurrency; - $currency->symbol = $transaction->transaction_currency_symbol; - $currency->decimal_places = $transaction->transaction_currency_dp; - $str = sprintf($format, app('amount')->formatAnything($currency, $amount, $coloured)); - - if (null !== $transaction->transaction_foreign_amount) { - $amount = bcmul(app('steam')->positive((string)$transaction->transaction_foreign_amount), '-1'); - if (TransactionType::DEPOSIT === $transaction->transaction_type_type) { - $amount = bcmul($amount, '-1'); - } - - if (TransactionType::TRANSFER === $transaction->transaction_type_type) { - $amount = app('steam')->positive($amount); - $coloured = false; - $format = '%s'; - } - - $currency = new TransactionCurrency; - $currency->symbol = $transaction->foreign_currency_symbol; - $currency->decimal_places = $transaction->foreign_currency_dp; - $str .= ' (' . sprintf($format, app('amount')->formatAnything($currency, $amount, $coloured)) . ')'; - } - - return $str; - } - - /** - * @param array $transaction - * - * @return string - */ - public function amountArray(array $transaction): string - { - // first display amount: - $amount = (string)$transaction['amount']; - $fakeCurrency = new TransactionCurrency; - $fakeCurrency->decimal_places = $transaction['currency_decimal_places']; - $fakeCurrency->symbol = $transaction['currency_symbol']; - $string = app('amount')->formatAnything($fakeCurrency, $amount, true); - - // then display (if present) the foreign amount: - if (null !== $transaction['foreign_amount']) { - $amount = (string)$transaction['foreign_amount']; - $fakeCurrency = new TransactionCurrency; - $fakeCurrency->decimal_places = $transaction['foreign_currency_decimal_places']; - $fakeCurrency->symbol = $transaction['foreign_currency_symbol']; - $string .= ' (' . app('amount')->formatAnything($fakeCurrency, $amount, true) . ')'; - } - - return $string; - } - - /** - * - * @param TransactionModel $transaction - * - * @return string - */ - public function budgets(TransactionModel $transaction): string - { - $txt = ''; - // journal has a budget: - if (null !== $transaction->transaction_journal_budget_id) { - $name = $transaction->transaction_journal_budget_name; - $txt = sprintf('%s', route('budgets.show', [$transaction->transaction_journal_budget_id]), e($name), e($name)); - } - - // transaction has a budget - if (null !== $transaction->transaction_budget_id && '' === $txt) { - $name = $transaction->transaction_budget_name; - $txt = sprintf('%s', route('budgets.show', [$transaction->transaction_budget_id]), e($name), e($name)); - } - - if ('' === $txt) { - // see if the transaction has a budget: - $budgets = $transaction->budgets()->get(); - if (0 === $budgets->count()) { - $budgets = $transaction->transactionJournal()->first()->budgets()->get(); - } - if ($budgets->count() > 0) { - $str = []; - foreach ($budgets as $budget) { - $str[] = sprintf('%s', route('budgets.show', [$budget->id]), e($budget->name), e($budget->name)); - } - $txt = implode(', ', $str); - } - } - - return $txt; - } - - /** - * @param TransactionModel $transaction - * - * @return string - */ - public function categories(TransactionModel $transaction): string - { - $txt = ''; - // journal has a category: - if (null !== $transaction->transaction_journal_category_id) { - $name = $transaction->transaction_journal_category_name; - $txt = sprintf('%s', route('categories.show', [$transaction->transaction_journal_category_id]), e($name), e($name)); - } - - // transaction has a category: - if (null !== $transaction->transaction_category_id && '' === $txt) { - $name = $transaction->transaction_category_name; - $txt = sprintf('%s', route('categories.show', [$transaction->transaction_category_id]), e($name), e($name)); - } - - if ('' === $txt) { - // see if the transaction has a category: - $categories = $transaction->categories()->get(); - if (0 === $categories->count()) { - $categories = $transaction->transactionJournal()->first()->categories()->get(); - } - if ($categories->count() > 0) { - $str = []; - foreach ($categories as $category) { - $str[] = sprintf('%s', route('categories.show', [$category->id]), e($category->name), e($category->name)); - } - - $txt = implode(', ', $str); - } - } - - return $txt; - } - - /** - * @param TransactionModel $transaction - * - * @return string - */ - public function description(TransactionModel $transaction): string - { - $description = $transaction->description; - if ('' !== (string)$transaction->transaction_description) { - $description = $transaction->transaction_description . ' (' . $transaction->description . ')'; - } - - return $description; - } - - /** - * @param TransactionModel $transaction - * - * @return string - */ - public function destinationAccount(TransactionModel $transaction): string - { - if (TransactionType::RECONCILIATION === $transaction->transaction_type_type) { - return '—'; - } - - $name = $transaction->account_name; - $iban = $transaction->account_iban; - $transactionId = (int)$transaction->account_id; - $type = $transaction->account_type; - - // name is present in object, use that one: - if (null !== $transaction->opposing_account_id && bccomp($transaction->transaction_amount, '0') === -1) { - $name = $transaction->opposing_account_name; - $transactionId = (int)$transaction->opposing_account_id; - $type = $transaction->opposing_account_type; - $iban = $transaction->opposing_account_iban; - } - - // Find the opposing account and use that one: - if (null === $transaction->opposing_account_id && bccomp($transaction->transaction_amount, '0') === -1) { - // if the amount is negative, find the opposing account and use that one: - $journalId = $transaction->journal_id; - /** @var TransactionModel $other */ - $other = TransactionModel - ::where('transaction_journal_id', $journalId) - ->where('transactions.id', '!=', $transaction->id) - ->where('amount', '=', bcmul($transaction->transaction_amount, '-1')) - ->where('identifier', $transaction->identifier) - ->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id') - ->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id') - ->first(['transactions.account_id', 'accounts.encrypted', 'accounts.name', 'account_types.type']); - if (null === $other) { - Log::error(sprintf('Cannot find other transaction for journal #%d', $journalId)); - - return ''; - } - $name = $other->name; - $transactionId = $other->account_id; - $type = $other->type; - } - - if (AccountType::CASH === $type) { - $txt = '(' . trans('firefly.cash') . ')'; - - return $txt; - } - - $txt = sprintf('%1$s', e($name), route('accounts.show', [$transactionId]), e($iban)); - - return $txt; - } - - /** - * @param TransactionModel $transaction - * - * @return string - */ - public function hasAttachments(TransactionModel $transaction): string - { - $res = ''; - if (\is_int($transaction->attachmentCount) && $transaction->attachmentCount > 0) { - $res = sprintf( - '', Lang::choice( - 'firefly.nr_of_attachments', - $transaction->attachmentCount, ['count' => $transaction->attachmentCount] - ) - ); - } - if (null === $transaction->attachmentCount) { - $journalId = (int)$transaction->journal_id; - $count = Attachment::whereNull('deleted_at') - ->where('attachable_type', TransactionJournal::class) - ->where('attachable_id', $journalId) - ->count(); - if ($count > 0) { - $res = sprintf('', Lang::choice('firefly.nr_of_attachments', $count, ['count' => $count])); - } - } - - return $res; - } - - /** - * @param TransactionModel $transaction - * - * @return string - */ - public function icon(TransactionModel $transaction): string - { - switch ($transaction->transaction_type_type) { - case TransactionType::WITHDRAWAL: - $txt = sprintf('', (string)trans('firefly.withdrawal')); - break; - case TransactionType::DEPOSIT: - $txt = sprintf('', (string)trans('firefly.deposit')); - break; - case TransactionType::TRANSFER: - $txt = sprintf('', (string)trans('firefly.transfer')); - break; - case TransactionType::OPENING_BALANCE: - $txt = sprintf('', (string)trans('firefly.opening_balance')); - break; - case TransactionType::RECONCILIATION: - $txt = sprintf('', (string)trans('firefly.reconciliation_transaction')); - break; - default: - $txt = ''; - break; - } - - return $txt; - } - - /** - * @param TransactionModel $transaction - * - * @return string - */ - public function isReconciled(TransactionModel $transaction): string - { - $icon = ''; - if (1 === (int)$transaction->reconciled) { - $icon = ''; - } - - return $icon; - } - - /** - * Returns an icon when the transaction is a split transaction. - * - * @param TransactionModel $transaction - * - * @return string - */ - public function isSplit(TransactionModel $transaction): string - { - $res = ''; - if (true === $transaction->is_split) { - $res = ''; - } - - if (null === $transaction->is_split) { - $journalId = (int)$transaction->journal_id; - $count = TransactionModel::where('transaction_journal_id', $journalId)->whereNull('deleted_at')->count(); - if ($count > 2) { - $res = ''; - } - } - - return $res; - } - - /** - * @param TransactionModel $transaction - * - * @return string - */ - public function sourceAccount(TransactionModel $transaction): string - { - if (TransactionType::RECONCILIATION === $transaction->transaction_type_type) { - return '—'; - } - - // if the amount is negative, assume that the current account (the one in $transaction) is indeed the source account. - $name = $transaction->account_name; - $transactionId = (int)$transaction->account_id; - $type = $transaction->account_type; - $iban = $transaction->account_iban; - - // name is present in object, use that one: - if (null !== $transaction->opposing_account_id && 1 === bccomp($transaction->transaction_amount, '0')) { - $name = $transaction->opposing_account_name; - $transactionId = (int)$transaction->opposing_account_id; - $type = $transaction->opposing_account_type; - $iban = $transaction->opposing_account_iban; - } - // Find the opposing account and use that one: - if (null === $transaction->opposing_account_id && 1 === bccomp($transaction->transaction_amount, '0')) { - $journalId = $transaction->journal_id; - /** @var TransactionModel $other */ - $other = TransactionModel::where('transaction_journal_id', $journalId)->where('transactions.id', '!=', $transaction->id) - ->where('amount', '=', bcmul($transaction->transaction_amount, '-1'))->where( - 'identifier', - $transaction->identifier - ) - ->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id') - ->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id') - ->first(['transactions.account_id', 'accounts.encrypted', 'accounts.name', 'account_types.type']); - $name = $other->name; - $transactionId = $other->account_id; - $type = $other->type; - } - - if (AccountType::CASH === $type) { - $txt = '(' . trans('firefly.cash') . ')'; - - return $txt; - } - - $txt = sprintf('%1$s', e($name), route('accounts.show', [$transactionId]), e($iban)); - - return $txt; - } -} diff --git a/app/Support/Twig/Extension/TransactionJournal.php b/app/Support/Twig/Extension/TransactionJournal.php deleted file mode 100644 index 4e1ee4be73..0000000000 --- a/app/Support/Twig/Extension/TransactionJournal.php +++ /dev/null @@ -1,178 +0,0 @@ -. - */ -declare(strict_types=1); - -namespace FireflyIII\Support\Twig\Extension; - -use Carbon\Carbon; -use FireflyIII\Models\Transaction as TransactionModel; -use FireflyIII\Models\TransactionJournal as JournalModel; -use FireflyIII\Models\TransactionType; -use FireflyIII\Repositories\Journal\JournalRepositoryInterface; -use Twig_Extension; - -/** - * Class TransactionJournal - */ -class TransactionJournal extends Twig_Extension -{ - /** - * @param JournalModel $journal - * @param string $field - * - * @return null|Carbon - */ - public function getMetaDate(JournalModel $journal, string $field): ?Carbon - { - /** @var JournalRepositoryInterface $repository */ - $repository = app(JournalRepositoryInterface::class); - - return $repository->getMetaDate($journal, $field); - } - - /** - * @param JournalModel $journal - * @param string $field - * - * @return string - */ - public function getMetaField(JournalModel $journal, string $field): string - { - /** @var JournalRepositoryInterface $repository */ - $repository = app(JournalRepositoryInterface::class); - $result = $repository->getMetaField($journal, $field); - if (null === $result) { - return ''; - } - - return $result; - } - - /** - * Return if journal HAS field. - * - * @param JournalModel $journal - * @param string $field - * - * @return bool - */ - public function hasMetaField(JournalModel $journal, string $field): bool - { - // HIER BEN JE - /** @var JournalRepositoryInterface $repository */ - $repository = app(JournalRepositoryInterface::class); - $result = $repository->getMetaField($journal, $field); - if (null === $result) { - return false; - } - if ('' === (string)$result) { - return false; - } - - return true; - } - - /** - * @param JournalModel $journal - * - * @return string - */ - public function totalAmount(JournalModel $journal): string - { - $type = $journal->transactionType->type; - $totals = $this->getTotalAmount($journal); - $array = []; - foreach ($totals as $total) { - if (TransactionType::WITHDRAWAL === $type) { - $total['amount'] = bcmul($total['amount'], '-1'); - } - if (null !== $total['currency']) { - $array[] = app('amount')->formatAnything($total['currency'], $total['amount']); - } - } - - return implode(' / ', $array); - } - - /** - * @param JournalModel $journal - * - * @return string - */ - public function totalAmountPlain(JournalModel $journal): string - { - $type = $journal->transactionType->type; - $totals = $this->getTotalAmount($journal); - $array = []; - - foreach ($totals as $total) { - if (TransactionType::WITHDRAWAL === $type) { - $total['amount'] = bcmul($total['amount'], '-1'); - } - $array[] = app('amount')->formatAnything($total['currency'], $total['amount'], false); - } - - return implode(' / ', $array); - } - - /** - * @param JournalModel $journal - * - * @return array - */ - private function getTotalAmount(JournalModel $journal): array - { - $transactions = $journal->transactions()->where('amount', '>', 0)->get(); - $totals = []; - - /** @var TransactionModel $transaction */ - foreach ($transactions as $transaction) { - $currencyId = $transaction->transaction_currency_id; - $currency = $transaction->transactionCurrency; - - if (!isset($totals[$currencyId])) { - $totals[$currencyId] = [ - 'amount' => '0', - 'currency' => $currency, - ]; - } - $totals[$currencyId]['amount'] = bcadd($transaction->amount, $totals[$currencyId]['amount']); - - if (null !== $transaction->foreign_currency_id) { - $foreignAmount = $transaction->foreign_amount ?? '0'; - $foreignId = $transaction->foreign_currency_id; - $foreign = $transaction->foreignCurrency; - if (!isset($totals[$foreignId])) { - $totals[$foreignId] = [ - 'amount' => '0', - 'currency' => $foreign, - ]; - } - $totals[$foreignId]['amount'] = bcadd( - $foreignAmount, - $totals[$foreignId]['amount'] - ); - } - } - - return $totals; - } -} diff --git a/app/Support/Twig/General.php b/app/Support/Twig/General.php index 265db54739..bbab73358f 100644 --- a/app/Support/Twig/General.php +++ b/app/Support/Twig/General.php @@ -24,9 +24,10 @@ namespace FireflyIII\Support\Twig; use Carbon\Carbon; use FireflyIII\Models\Account; +use FireflyIII\Repositories\Account\AccountRepositoryInterface; use FireflyIII\Repositories\User\UserRepositoryInterface; -use FireflyIII\Support\Twig\Extension\Account as AccountExtension; use League\CommonMark\CommonMarkConverter; +use Log; use Route; use Twig_Extension; use Twig_SimpleFilter; @@ -56,14 +57,12 @@ class General extends Twig_Extension public function getFunctions(): array { return [ - $this->getCurrencyCode(), - $this->getCurrencySymbol(), $this->phpdate(), $this->activeRouteStrict(), $this->activeRoutePartial(), - $this->activeRoutePartialWhat(), + $this->activeRoutePartialObjectType(), $this->formatDate(), - new Twig_SimpleFunction('accountGetMetaField', [AccountExtension::class, 'getMetaField']), + $this->getMetaField(), $this->hasRole(), ]; } @@ -79,7 +78,7 @@ class General extends Twig_Extension return new Twig_SimpleFunction( 'activeRoutePartial', function (): string { - $args = \func_get_args(); + $args = func_get_args(); $route = $args[0]; // name of the route. $name = Route::getCurrentRoute()->getName() ?? ''; if (!(false === strpos($name, $route))) { @@ -97,15 +96,15 @@ class General extends Twig_Extension * * @return Twig_SimpleFunction */ - protected function activeRoutePartialWhat(): Twig_SimpleFunction + protected function activeRoutePartialObjectType(): Twig_SimpleFunction { return new Twig_SimpleFunction( - 'activeRoutePartialWhat', - function ($context): string { - [, $route, $what] = \func_get_args(); - $activeWhat = $context['what'] ?? false; + 'activeRoutePartialObjectType', + static function ($context): string { + [, $route, $objectType] = func_get_args(); + $activeObjectType = $context['objectType'] ?? false; - if ($what === $activeWhat && !(false === stripos(Route::getCurrentRoute()->getName(), $route))) { + if ($objectType === $activeObjectType && !(false === stripos(Route::getCurrentRoute()->getName(), $route))) { return 'active'; } @@ -126,7 +125,7 @@ class General extends Twig_Extension return new Twig_SimpleFunction( 'activeRouteStrict', function (): string { - $args = \func_get_args(); + $args = func_get_args(); $route = $args[0]; // name of the route. if (Route::getCurrentRoute()->getName() === $route) { @@ -139,13 +138,15 @@ class General extends Twig_Extension } /** + * Show account balance. Only used on the front page of Firefly III. + * * @return Twig_SimpleFilter */ protected function balance(): Twig_SimpleFilter { return new Twig_SimpleFilter( 'balance', - function (?Account $account): string { + static function (?Account $account): string { if (null === $account) { return 'NULL'; } @@ -158,6 +159,8 @@ class General extends Twig_Extension } /** + * Formats a string as a thing by converting it to a Carbon first. + * * @return Twig_SimpleFunction */ protected function formatDate(): Twig_SimpleFunction @@ -173,6 +176,8 @@ class General extends Twig_Extension } /** + * Used to convert 1024 to 1kb etc. + * * @return Twig_SimpleFilter */ protected function formatFilesize(): Twig_SimpleFilter @@ -198,25 +203,23 @@ class General extends Twig_Extension /** * @return Twig_SimpleFunction */ - protected function getCurrencyCode(): Twig_SimpleFunction + protected function getMetaField(): Twig_SimpleFunction { return new Twig_SimpleFunction( - 'getCurrencyCode', - function (): string { - return app('amount')->getCurrencyCode(); - } - ); - } + 'accountGetMetaField', + static function (Account $account, string $field): string { + if ('testing' === config('app.env')) { + Log::warning('Twig General::getMetaField should NOT be called in the TEST environment!'); + } - /** - * @return Twig_SimpleFunction - */ - protected function getCurrencySymbol(): Twig_SimpleFunction - { - return new Twig_SimpleFunction( - 'getCurrencySymbol', - function (): string { - return app('amount')->getCurrencySymbol(); + /** @var AccountRepositoryInterface $repository */ + $repository = app(AccountRepositoryInterface::class); + $result = $repository->getMetaValue($account, $field); + if (null === $result) { + return ''; + } + + return $result; } ); } @@ -230,7 +233,7 @@ class General extends Twig_Extension { return new Twig_SimpleFunction( 'hasRole', - function (string $role): bool { + static function (string $role): bool { $repository = app(UserRepositoryInterface::class); if ($repository->hasRole(auth()->user(), $role)) { return true; @@ -248,7 +251,7 @@ class General extends Twig_Extension { return new Twig_SimpleFilter( 'markdown', - function (string $text): string { + static function (string $text): string { $converter = new CommonMarkConverter; return $converter->convertToHtml($text); @@ -257,13 +260,15 @@ class General extends Twig_Extension } /** + * Show icon with attachment. + * * @return Twig_SimpleFilter */ protected function mimeIcon(): Twig_SimpleFilter { return new Twig_SimpleFilter( 'mimeIcon', - function (string $string): string { + static function (string $string): string { switch ($string) { default: return 'fa-file-o'; @@ -334,16 +339,17 @@ class General extends Twig_Extension } /** + * Basic example thing for some views. + * * @return Twig_SimpleFunction */ protected function phpdate(): Twig_SimpleFunction { return new Twig_SimpleFunction( 'phpdate', - function (string $str): string { + static function (string $str): string { return date($str); } ); } - } diff --git a/app/Support/Twig/Journal.php b/app/Support/Twig/Journal.php deleted file mode 100644 index 632c80a2b3..0000000000 --- a/app/Support/Twig/Journal.php +++ /dev/null @@ -1,223 +0,0 @@ -. - */ -declare(strict_types=1); - -namespace FireflyIII\Support\Twig; - -use FireflyIII\Models\Account; -use FireflyIII\Models\AccountType; -use FireflyIII\Models\Category; -use FireflyIII\Models\TransactionJournal; -use FireflyIII\Repositories\Journal\JournalRepositoryInterface; -use FireflyIII\Support\CacheProperties; -use FireflyIII\Support\Twig\Extension\TransactionJournal as TransactionJournalExtension; -use Twig_Extension; -use Twig_SimpleFilter; -use Twig_SimpleFunction; - -/** - * Class Journal. - */ -class Journal extends Twig_Extension -{ - /** - * @return Twig_SimpleFunction - */ - public function getDestinationAccount(): Twig_SimpleFunction - { - return new Twig_SimpleFunction( - 'destinationAccount', - function (TransactionJournal $journal) { - $cache = new CacheProperties; - $cache->addProperty($journal->id); - $cache->addProperty('transaction-journal'); - $cache->addProperty('destination-account-string'); - if ($cache->has()) { - return $cache->get(); // @codeCoverageIgnore - } - - /** @var JournalRepositoryInterface $repository */ - $repository = app(JournalRepositoryInterface::class); - $list = $repository->getJournalDestinationAccounts($journal); - $array = []; - /** @var Account $entry */ - foreach ($list as $entry) { - if (AccountType::CASH === $entry->accountType->type) { - $array[] = '(cash)'; - continue; - } - $array[] = sprintf('%1$s', e($entry->name), route('accounts.show', $entry->id)); - } - $array = array_unique($array); - $result = implode(', ', $array); - $cache->store($result); - - return $result; - } - ); - } - - /** - * @return array - */ - public function getFilters(): array - { - $filters = [ - new Twig_SimpleFilter('journalTotalAmount', [TransactionJournalExtension::class, 'totalAmount'], ['is_safe' => ['html']]), - new Twig_SimpleFilter('journalTotalAmountPlain', [TransactionJournalExtension::class, 'totalAmountPlain'], ['is_safe' => ['html']]), - ]; - - return $filters; - } - - /** - * @return array - */ - public function getFunctions(): array - { - $functions = [ - $this->getSourceAccount(), - $this->getDestinationAccount(), - $this->journalBudgets(), - $this->journalCategories(), - new Twig_SimpleFunction('journalGetMetaField', [TransactionJournalExtension::class, 'getMetaField']), - new Twig_SimpleFunction('journalHasMeta', [TransactionJournalExtension::class, 'hasMetaField']), - new Twig_SimpleFunction('journalGetMetaDate', [TransactionJournalExtension::class, 'getMetaDate']), - ]; - - return $functions; - } - - /** - * @return Twig_SimpleFunction - */ - public function getSourceAccount(): Twig_SimpleFunction - { - return new Twig_SimpleFunction( - 'sourceAccount', - function (TransactionJournal $journal): string { - $cache = new CacheProperties; - $cache->addProperty($journal->id); - $cache->addProperty('transaction-journal'); - $cache->addProperty('source-account-string'); - if ($cache->has()) { - return $cache->get(); // @codeCoverageIgnore - } - - /** @var JournalRepositoryInterface $repository */ - $repository = app(JournalRepositoryInterface::class); - - $list = $repository->getJournalSourceAccounts($journal); - $array = []; - /** @var Account $entry */ - foreach ($list as $entry) { - if (AccountType::CASH === $entry->accountType->type) { - $array[] = '(cash)'; - continue; - } - $array[] = sprintf('%1$s', e($entry->name), route('accounts.show', $entry->id)); - } - $array = array_unique($array); - $result = implode(', ', $array); - $cache->store($result); - - return $result; - } - ); - } - - /** - * @return Twig_SimpleFunction - */ - public function journalBudgets(): Twig_SimpleFunction - { - return new Twig_SimpleFunction( - 'journalBudgets', - function (TransactionJournal $journal): string { - $cache = new CacheProperties; - $cache->addProperty($journal->id); - $cache->addProperty('transaction-journal'); - $cache->addProperty('budget-string'); - if ($cache->has()) { - return $cache->get(); // @codeCoverageIgnore - } - - $budgets = []; - // get all budgets: - foreach ($journal->budgets as $budget) { - $budgets[] = sprintf('%1$s', e($budget->name), route('budgets.show', $budget->id)); - } - // and more! - foreach ($journal->transactions as $transaction) { - foreach ($transaction->budgets as $budget) { - $budgets[] = sprintf('%1$s', e($budget->name), route('budgets.show', $budget->id)); - } - } - $string = implode(', ', array_unique($budgets)); - $cache->store($string); - - return $string; - } - ); - } - - /** - * @return Twig_SimpleFunction - */ - public function journalCategories(): Twig_SimpleFunction - { - return new Twig_SimpleFunction( - 'journalCategories', - function (TransactionJournal $journal): string { - $cache = new CacheProperties; - $cache->addProperty($journal->id); - $cache->addProperty('transaction-journal'); - $cache->addProperty('category-string'); - if ($cache->has()) { - return $cache->get(); // @codeCoverageIgnore - } - $categories = []; - // get all categories for the journal itself (easy): - foreach ($journal->categories as $category) { - $categories[] = sprintf('%1$s', e($category->name), route('categories.show', $category->id)); - } - if (0 === \count($categories)) { - $set = Category::distinct()->leftJoin('category_transaction', 'categories.id', '=', 'category_transaction.category_id') - ->leftJoin('transactions', 'category_transaction.transaction_id', '=', 'transactions.id') - ->leftJoin('transaction_journals', 'transactions.transaction_journal_id', '=', 'transaction_journals.id') - ->where('categories.user_id', $journal->user_id) - ->where('transaction_journals.id', $journal->id) - ->whereNull('transactions.deleted_at') - ->get(['categories.*']); - /** @var Category $category */ - foreach ($set as $category) { - $categories[] = sprintf('%1$s', e($category->name), route('categories.show', $category->id)); - } - } - - $string = implode(', ', array_unique($categories)); - $cache->store($string); - - return $string; - } - ); - } -} diff --git a/app/Support/Twig/Loader/AccountLoader.php b/app/Support/Twig/Loader/AccountLoader.php deleted file mode 100644 index be92e6586d..0000000000 --- a/app/Support/Twig/Loader/AccountLoader.php +++ /dev/null @@ -1,52 +0,0 @@ -. - */ -declare(strict_types=1); - -namespace FireflyIII\Support\Twig\Loader; - -use FireflyIII\Support\Twig\Extension\Account; -use Twig_RuntimeLoaderInterface; - -/** - * Class AccountLoader. - */ -class AccountLoader implements Twig_RuntimeLoaderInterface -{ - /** - * Creates the runtime implementation of a Twig element (filter/function/test). - * - * @param string $class A runtime class - * - * @return object|null The runtime instance or null if the loader does not know how to create the runtime for this class - */ - public function load($class) - { - // implement the logic to create an instance of $class - // and inject its dependencies - // most of the time, it means using your dependency injection container - - if (Account::class === $class) { - return app(Account::class); - } - - return null; - } -} diff --git a/app/Support/Twig/Loader/TransactionJournalLoader.php b/app/Support/Twig/Loader/TransactionJournalLoader.php deleted file mode 100644 index 5268fa9dd8..0000000000 --- a/app/Support/Twig/Loader/TransactionJournalLoader.php +++ /dev/null @@ -1,52 +0,0 @@ -. - */ -declare(strict_types=1); - -namespace FireflyIII\Support\Twig\Loader; - -use FireflyIII\Support\Twig\Extension\TransactionJournal; -use Twig_RuntimeLoaderInterface; - -/** - * Class TransactionJournalLoader. - */ -class TransactionJournalLoader implements Twig_RuntimeLoaderInterface -{ - /** - * Creates the runtime implementation of a Twig element (filter/function/test). - * - * @param string $class A runtime class - * - * @return object|null The runtime instance or null if the loader does not know how to create the runtime for this class - */ - public function load($class) - { - // implement the logic to create an instance of $class - // and inject its dependencies - // most of the time, it means using your dependency injection container - - if (TransactionJournal::class === $class) { - return app(TransactionJournal::class); - } - - return null; - } -} diff --git a/app/Support/Twig/Loader/TransactionLoader.php b/app/Support/Twig/Loader/TransactionLoader.php deleted file mode 100644 index 43ed1b2774..0000000000 --- a/app/Support/Twig/Loader/TransactionLoader.php +++ /dev/null @@ -1,52 +0,0 @@ -. - */ -declare(strict_types=1); - -namespace FireflyIII\Support\Twig\Loader; - -use FireflyIII\Support\Twig\Extension\Transaction; -use Twig_RuntimeLoaderInterface; - -/** - * Class TransactionLoader. - */ -class TransactionLoader implements Twig_RuntimeLoaderInterface -{ - /** - * Creates the runtime implementation of a Twig element (filter/function/test). - * - * @param string $class A runtime class - * - * @return object|null The runtime instance or null if the loader does not know how to create the runtime for this class - */ - public function load($class) - { - // implement the logic to create an instance of $class - // and inject its dependencies - // most of the time, it means using your dependency injection container - - if (Transaction::class === $class) { - return app(Transaction::class); - } - - return null; - } -} diff --git a/app/Support/Twig/Rule.php b/app/Support/Twig/Rule.php index 83a403bc0f..c40c8097bd 100644 --- a/app/Support/Twig/Rule.php +++ b/app/Support/Twig/Rule.php @@ -38,7 +38,7 @@ class Rule extends Twig_Extension { return new Twig_SimpleFunction( 'allRuleActions', - function () { + static function () { // array of valid values for actions $ruleActions = array_keys(Config::get('firefly.rule-actions')); $possibleActions = []; @@ -60,7 +60,7 @@ class Rule extends Twig_Extension { return new Twig_SimpleFunction( 'allJournalTriggers', - function () { + static function () { return [ 'store-journal' => (string)trans('firefly.rule_trigger_store_journal'), 'update-journal' => (string)trans('firefly.rule_trigger_update_journal'), @@ -76,7 +76,7 @@ class Rule extends Twig_Extension { return new Twig_SimpleFunction( 'allRuleTriggers', - function () { + static function () { $ruleTriggers = array_keys(Config::get('firefly.rule-triggers')); $possibleTriggers = []; foreach ($ruleTriggers as $key) { diff --git a/app/Support/Twig/Transaction.php b/app/Support/Twig/Transaction.php deleted file mode 100644 index ffe6b046fd..0000000000 --- a/app/Support/Twig/Transaction.php +++ /dev/null @@ -1,55 +0,0 @@ -. - */ -declare(strict_types=1); - -namespace FireflyIII\Support\Twig; - -use FireflyIII\Support\Twig\Extension\Transaction as TransactionExtension; -use Twig_Extension; -use Twig_SimpleFilter; - -/** - * Class Transaction. - */ -class Transaction extends Twig_Extension -{ - /** - * @return array - */ - public function getFilters(): array - { - $filters = [ - new Twig_SimpleFilter('transactionIcon', [TransactionExtension::class, 'icon'], ['is_safe' => ['html']]), - new Twig_SimpleFilter('transactionDescription', [TransactionExtension::class, 'description']), - new Twig_SimpleFilter('transactionIsSplit', [TransactionExtension::class, 'isSplit'], ['is_safe' => ['html']]), - new Twig_SimpleFilter('transactionReconciled', [TransactionExtension::class, 'isReconciled'], ['is_safe' => ['html']]), - new Twig_SimpleFilter('transactionHasAtt', [TransactionExtension::class, 'hasAttachments'], ['is_safe' => ['html']]), - new Twig_SimpleFilter('transactionAmount', [TransactionExtension::class, 'amount'], ['is_safe' => ['html']]), - new Twig_SimpleFilter('transactionArrayAmount', [TransactionExtension::class, 'amountArray'], ['is_safe' => ['html']]), - new Twig_SimpleFilter('transactionBudgets', [TransactionExtension::class, 'budgets'], ['is_safe' => ['html']]), - new Twig_SimpleFilter('transactionCategories', [TransactionExtension::class, 'categories'], ['is_safe' => ['html']]), - new Twig_SimpleFilter('transactionSourceAccount', [TransactionExtension::class, 'sourceAccount'], ['is_safe' => ['html']]), - new Twig_SimpleFilter('transactionDestinationAccount', [TransactionExtension::class, 'destinationAccount'], ['is_safe' => ['html']]), - ]; - - return $filters; - } -} diff --git a/app/Support/Twig/TransactionGroupTwig.php b/app/Support/Twig/TransactionGroupTwig.php new file mode 100644 index 0000000000..65b459e83d --- /dev/null +++ b/app/Support/Twig/TransactionGroupTwig.php @@ -0,0 +1,333 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Support\Twig\Extension; + +use Carbon\Carbon; +use DB; +use FireflyIII\Models\Transaction; +use FireflyIII\Models\TransactionJournal; +use FireflyIII\Models\TransactionType; +use Log; +use Twig_Extension; +use Twig_SimpleFunction; + +/** + * Class TransactionGroupTwig + */ +class TransactionGroupTwig extends Twig_Extension +{ + /** @noinspection PhpMissingParentCallCommonInspection */ + /** + * @return array + * + */ + public function getFunctions(): array + { + return [ + $this->journalArrayAmount(), + $this->journalObjectAmount(), + $this->groupAmount(), + $this->journalHasMeta(), + $this->journalGetMetaDate(), + $this->journalGetMetaField(), + ]; + } + + /** + * @return Twig_SimpleFunction + */ + public function groupAmount(): Twig_SimpleFunction + { + return new Twig_SimpleFunction( + 'groupAmount', + static function (array $array): string { + $sums = $array['sums']; + $return = []; + $first = reset($array['transactions']); + $type = $first['transaction_type_type'] ?? TransactionType::WITHDRAWAL; + $colored = true; + if ($type === TransactionType::TRANSFER) { + $colored = false; + } + + + /** @var array $sum */ + foreach ($sums as $sum) { + $amount = $sum['amount']; + + // do multiplication thing. + if ($type !== TransactionType::WITHDRAWAL) { + $amount = bcmul($amount, '-1'); + } + + $return[] = app('amount')->formatFlat($sum['currency_symbol'], (int)$sum['currency_decimal_places'], $amount, $colored); + } + + return implode(', ', $return); + }, + ['is_safe' => ['html']] + ); + } + + /** + * @return Twig_SimpleFunction + */ + public function journalGetMetaDate(): Twig_SimpleFunction + { + return new Twig_SimpleFunction( + 'journalGetMetaDate', + static function (int $journalId, string $metaField) { + if ('testing' === config('app.env')) { + Log::warning('Twig TransactionGroup::journalGetMetaDate should NOT be called in the TEST environment!'); + } + $entry = DB::table('journal_meta') + ->where('name', $metaField) + ->where('transaction_journal_id', $journalId) + ->whereNull('deleted_at') + ->first(); + if (null === $entry) { + return new Carbon; + } + + return new Carbon(json_decode($entry->data, false)); + } + ); + } + + /** + * @return Twig_SimpleFunction + */ + public function journalGetMetaField(): Twig_SimpleFunction + { + return new Twig_SimpleFunction( + 'journalGetMetaField', + static function (int $journalId, string $metaField) { + if ('testing' === config('app.env')) { + Log::warning('Twig TransactionGroup::journalGetMetaField should NOT be called in the TEST environment!'); + } + $entry = DB::table('journal_meta') + ->where('name', $metaField) + ->where('transaction_journal_id', $journalId) + ->whereNull('deleted_at') + ->first(); + if (null === $entry) { + return ''; + } + + return json_decode($entry->data, true); + } + ); + } + + /** + * @return Twig_SimpleFunction + */ + public function journalHasMeta(): Twig_SimpleFunction + { + return new Twig_SimpleFunction( + 'journalHasMeta', + static function (int $journalId, string $metaField) { + if ('testing' === config('app.env')) { + Log::warning('Twig TransactionGroup::journalHasMeta should NOT be called in the TEST environment!'); + } + $count = DB::table('journal_meta') + ->where('name', $metaField) + ->where('transaction_journal_id', $journalId) + ->whereNull('deleted_at') + ->count(); + + return 1 === $count; + } + ); + } + + /** + * Shows the amount for a single journal array. + * + * @return Twig_SimpleFunction + */ + public function journalArrayAmount(): Twig_SimpleFunction + { + return new Twig_SimpleFunction( + 'journalArrayAmount', + function (array $array): string { + // if is not a withdrawal, amount positive. + $result = $this->normalJournalArrayAmount($array); + // now append foreign amount, if any. + if (null !== $array['foreign_amount']) { + $foreign = $this->foreignJournalArrayAmount($array); + $result = sprintf('%s (%s)', $result, $foreign); + } + + return $result; + }, + ['is_safe' => ['html']] + ); + } + + /** + * Shows the amount for a single journal object. + * + * @return Twig_SimpleFunction + */ + public function journalObjectAmount(): Twig_SimpleFunction + { + return new Twig_SimpleFunction( + 'journalObjectAmount', + function (TransactionJournal $journal): string { + // if is not a withdrawal, amount positive. + $result = $this->normalJournalObjectAmount($journal); + // now append foreign amount, if any. + if ($this->journalObjectHasForeign($journal)) { + $foreign = $this->foreignJournalObjectAmount($journal); + $result = sprintf('%s (%s)', $result, $foreign); + } + + return $result; + }, + ['is_safe' => ['html']] + ); + } + + /** + * Generate foreign amount for transaction from a transaction group. + * + * @param array $array + * + * @return string + */ + private function foreignJournalArrayAmount(array $array): string + { + $type = $array['transaction_type_type'] ?? TransactionType::WITHDRAWAL; + $amount = $array['foreign_amount'] ?? '0'; + $colored = true; + if ($type !== TransactionType::WITHDRAWAL) { + $amount = bcmul($amount, '-1'); + } + if ($type === TransactionType::TRANSFER) { + $colored = false; + } + $result = app('amount')->formatFlat($array['foreign_currency_symbol'], (int)$array['foreign_currency_decimal_places'], $amount, $colored); + if ($type === TransactionType::TRANSFER) { + $result = sprintf('%s', $result); + } + + return $result; + } + + /** + * Generate foreign amount for journal from a transaction group. + * + * @param TransactionJournal $journal + * + * @return string + */ + private function foreignJournalObjectAmount(TransactionJournal $journal): string + { + $type = $journal->transactionType->type; + /** @var Transaction $first */ + $first = $journal->transactions()->where('amount', '<', 0)->first(); + $currency = $first->foreignCurrency; + $amount = $first->foreign_amount ?? '0'; + $colored = true; + if ($type !== TransactionType::WITHDRAWAL) { + $amount = bcmul($amount, '-1'); + } + if ($type === TransactionType::TRANSFER) { + $colored = false; + } + $result = app('amount')->formatFlat($currency->symbol, (int)$currency->decimal_places, $amount, $colored); + if ($type === TransactionType::TRANSFER) { + $result = sprintf('%s', $result); + } + + return $result; + } + + /** + * Generate normal amount for transaction from a transaction group. + * + * @param array $array + * + * @return string + */ + private function normalJournalArrayAmount(array $array): string + { + $type = $array['transaction_type_type'] ?? TransactionType::WITHDRAWAL; + $amount = $array['amount'] ?? '0'; + $colored = true; + if ($type !== TransactionType::WITHDRAWAL) { + $amount = bcmul($amount, '-1'); + } + if ($type === TransactionType::TRANSFER) { + $colored = false; + } + $result = app('amount')->formatFlat($array['currency_symbol'], (int)$array['currency_decimal_places'], $amount, $colored); + if ($type === TransactionType::TRANSFER) { + $result = sprintf('%s', $result); + } + + return $result; + } + + /** + * Generate normal amount for transaction from a transaction group. + * + * @param TransactionJournal $journal + * + * @return string + */ + private function normalJournalObjectAmount(TransactionJournal $journal): string + { + $type = $journal->transactionType->type; + $first = $journal->transactions()->where('amount', '<', 0)->first(); + $currency = $journal->transactionCurrency; + $amount = $first->amount ?? '0'; + $colored = true; + if ($type !== TransactionType::WITHDRAWAL) { + $amount = bcmul($amount, '-1'); + } + if ($type === TransactionType::TRANSFER) { + $colored = false; + } + $result = app('amount')->formatFlat($currency->symbol, (int)$currency->decimal_places, $amount, $colored); + if ($type === TransactionType::TRANSFER) { + $result = sprintf('%s', $result); + } + + return $result; + } + + /** + * @param TransactionJournal $journal + * @return bool + */ + private function journalObjectHasForeign(TransactionJournal $journal): bool + { + /** @var Transaction $first */ + $first = $journal->transactions()->where('amount', '<', 0)->first(); + + return null !== $first->foreign_amount; + } +} \ No newline at end of file diff --git a/app/Support/Twig/Translation.php b/app/Support/Twig/Translation.php index 6aa55ceec3..a1c0c8eead 100644 --- a/app/Support/Twig/Translation.php +++ b/app/Support/Twig/Translation.php @@ -59,7 +59,6 @@ class Translation extends Twig_Extension ]; } - /** * @return Twig_SimpleFunction */ @@ -70,7 +69,6 @@ class Translation extends Twig_Extension function (string $direction, string $original) { $key = sprintf('firefly.%s_%s', $original, $direction); $translation = trans($key); - if ($key === $translation) { return $original; } diff --git a/app/TransactionRules/Actions/AddTag.php b/app/TransactionRules/Actions/AddTag.php index 4da72dcf74..252afd1f8e 100644 --- a/app/TransactionRules/Actions/AddTag.php +++ b/app/TransactionRules/Actions/AddTag.php @@ -73,7 +73,6 @@ class AddTag implements ActionInterface return true; } - Log::debug(sprintf('RuleAction AddTag fired but tag %d ("%s") was already added to journal %d.', $tag->id, $tag->tag, $journal->id)); return false; diff --git a/app/TransactionRules/Actions/ClearBudget.php b/app/TransactionRules/Actions/ClearBudget.php index 0047f0903f..4d0ae7f241 100644 --- a/app/TransactionRules/Actions/ClearBudget.php +++ b/app/TransactionRules/Actions/ClearBudget.php @@ -53,7 +53,7 @@ class ClearBudget implements ActionInterface $journal->budgets()->detach(); $journal->touch(); - // also remove categories from transactions: + // also remove budgets from transactions (although no longer necessary) /** @var Transaction $transaction */ foreach ($journal->transactions as $transaction) { $transaction->budgets()->detach(); diff --git a/app/TransactionRules/Actions/LinkToBill.php b/app/TransactionRules/Actions/LinkToBill.php index d0bd43240c..e51fb72f34 100644 --- a/app/TransactionRules/Actions/LinkToBill.php +++ b/app/TransactionRules/Actions/LinkToBill.php @@ -39,6 +39,8 @@ class LinkToBill implements ActionInterface /** * TriggerInterface constructor. * + * @codeCoverageIgnore + * * @param RuleAction $action */ public function __construct(RuleAction $action) @@ -65,13 +67,13 @@ class LinkToBill implements ActionInterface $journal->bill()->associate($bill); $journal->save(); Log::debug(sprintf('RuleAction LinkToBill set the bill of journal #%d to bill #%d ("%s").', $journal->id, $bill->id, $bill->name)); + + return true; } - if (null === $bill) { - Log::error(sprintf('RuleAction LinkToBill could not set the bill of journal #%d to bill "%s": no such bill found!', $journal->id, $billName)); - } + Log::error(sprintf('RuleAction LinkToBill could not set the bill of journal #%d to bill "%s": no such bill found!', $journal->id, $billName)); - return true; + return false; } } diff --git a/app/TransactionRules/Actions/RemoveTag.php b/app/TransactionRules/Actions/RemoveTag.php index dd81dedfd5..bb20c0453f 100644 --- a/app/TransactionRules/Actions/RemoveTag.php +++ b/app/TransactionRules/Actions/RemoveTag.php @@ -47,7 +47,7 @@ class RemoveTag implements ActionInterface /** * Remove tag X - * + * TODO the filter is no longer necessary. * @param TransactionJournal $journal * * @return bool diff --git a/app/TransactionRules/Actions/SetBudget.php b/app/TransactionRules/Actions/SetBudget.php index 693ec72308..37816cd281 100644 --- a/app/TransactionRules/Actions/SetBudget.php +++ b/app/TransactionRules/Actions/SetBudget.php @@ -49,7 +49,8 @@ class SetBudget implements ActionInterface } /** - * Set budget X + * Set budget. + * TODO the filter is no longer necessary. * * @param TransactionJournal $journal * @@ -62,15 +63,18 @@ class SetBudget implements ActionInterface $repository->setUser($journal->user); $search = $this->action->action_value; $budgets = $repository->getActiveBudgets(); + + // TODO no longer need to loop like this + $budget = $budgets->filter( - function (Budget $current) use ($search) { + static function (Budget $current) use ($search) { return $current->name === $search; } )->first(); if (null === $budget) { Log::debug(sprintf('RuleAction SetBudget could not set budget of journal #%d to "%s" because no such budget exists.', $journal->id, $search)); - return true; + return false; } if (TransactionType::WITHDRAWAL !== $journal->transactionType->type) { @@ -88,12 +92,7 @@ class SetBudget implements ActionInterface Log::debug(sprintf('RuleAction SetBudget set the budget of journal #%d to budget #%d ("%s").', $journal->id, $budget->id, $budget->name)); - $journal->budgets()->detach(); - // set budget on transactions: - /** @var Transaction $transaction */ - foreach ($journal->transactions as $transaction) { - $transaction->budgets()->sync([$budget->id]); - } + $journal->budgets()->sync([$budget->id]); $journal->touch(); return true; diff --git a/app/TransactionRules/Actions/SetCategory.php b/app/TransactionRules/Actions/SetCategory.php index 500d7bd559..04b1c77d23 100644 --- a/app/TransactionRules/Actions/SetCategory.php +++ b/app/TransactionRules/Actions/SetCategory.php @@ -64,18 +64,11 @@ class SetCategory implements ActionInterface if (null === $category) { Log::error(sprintf('Action SetCategory did not fire because "%s" did not result in a valid category.', $name)); - return true; + return false; } - $journal->categories()->detach(); - // set category on transactions: - /** @var Transaction $transaction */ - foreach ($journal->transactions as $transaction) { - $transaction->categories()->sync([$category->id]); - } - $journal->touch(); + $journal->categories()->sync([$category->id]); - $journal->touch(); Log::debug(sprintf('RuleAction SetCategory set the category of journal #%d to category #%d ("%s").', $journal->id, $category->id, $category->name)); return true; diff --git a/app/TransactionRules/Actions/SetDestinationAccount.php b/app/TransactionRules/Actions/SetDestinationAccount.php index 956ae71670..885fc6e2ff 100644 --- a/app/TransactionRules/Actions/SetDestinationAccount.php +++ b/app/TransactionRules/Actions/SetDestinationAccount.php @@ -69,13 +69,6 @@ class SetDestinationAccount implements ActionInterface $this->journal = $journal; $this->repository = app(AccountRepositoryInterface::class); $this->repository->setUser($journal->user); - $count = $journal->transactions()->count(); - if ($count > 2) { - Log::error(sprintf('Cannot change destination account of journal #%d because it is a split journal.', $journal->id)); - - return false; - } - // journal type: $type = $journal->transactionType->type; @@ -103,7 +96,7 @@ class SetDestinationAccount implements ActionInterface // get destination transaction: $transaction = $journal->transactions()->where('amount', '>', 0)->first(); if (null === $transaction) { - return true; + return true; // @codeCoverageIgnore } $transaction->account_id = $this->newDestinationAccount->id; $transaction->save(); @@ -140,9 +133,9 @@ class SetDestinationAccount implements ActionInterface if (null === $account) { $data = [ 'name' => $this->action->action_value, - 'accountType' => 'expense', + 'account_type' => 'expense', 'account_type_id' => null, - 'virtualBalance' => 0, + 'virtual_balance' => 0, 'active' => true, 'iban' => null, ]; diff --git a/app/TransactionRules/Actions/SetSourceAccount.php b/app/TransactionRules/Actions/SetSourceAccount.php index ff2f0e870f..9622f4ea4e 100644 --- a/app/TransactionRules/Actions/SetSourceAccount.php +++ b/app/TransactionRules/Actions/SetSourceAccount.php @@ -69,13 +69,6 @@ class SetSourceAccount implements ActionInterface $this->journal = $journal; $this->repository = app(AccountRepositoryInterface::class); $this->repository->setUser($journal->user); - $count = $journal->transactions()->count(); - if ($count > 2) { - Log::error(sprintf('Cannot change source account of journal #%d because it is a split journal.', $journal->id)); - - return false; - } - // journal type: $type = $journal->transactionType->type; // if this is a transfer or a withdrawal, the new source account must be an asset account or a default account, and it MUST exist: @@ -102,9 +95,11 @@ class SetSourceAccount implements ActionInterface // get source transaction: $transaction = $journal->transactions()->where('amount', '<', 0)->first(); if (null === $transaction) { + // @codeCoverageIgnoreStart Log::error(sprintf('Cannot change source account of journal #%d because no source transaction exists.', $journal->id)); return false; + // @codeCoverageIgnoreEnd } $transaction->account_id = $this->newSourceAccount->id; $transaction->save(); @@ -142,9 +137,9 @@ class SetSourceAccount implements ActionInterface // create new revenue account with this name: $data = [ 'name' => $this->action->action_value, - 'accountType' => 'revenue', + 'account_type' => 'revenue', 'account_type_id' => null, - 'virtualBalance' => 0, + 'virtual_balance' => 0, 'active' => true, 'iban' => null, ]; diff --git a/app/TransactionRules/Engine/RuleEngine.php b/app/TransactionRules/Engine/RuleEngine.php new file mode 100644 index 0000000000..aa58ff754d --- /dev/null +++ b/app/TransactionRules/Engine/RuleEngine.php @@ -0,0 +1,233 @@ +. + */ + +namespace FireflyIII\TransactionRules\Engine; + +use FireflyIII\Exceptions\FireflyException; +use FireflyIII\Models\Rule; +use FireflyIII\Models\RuleGroup; +use FireflyIII\Models\RuleTrigger; +use FireflyIII\Models\TransactionJournal; +use FireflyIII\Repositories\RuleGroup\RuleGroupRepository; +use FireflyIII\TransactionRules\Processor; +use FireflyIII\User; +use Illuminate\Support\Collection; +use Log; + +/** + * Class RuleEngine + * + * Set the user, then apply an array to setRulesToApply(array) or call addRuleIdToApply(int) or addRuleToApply(Rule). + * Then call process() to make the magic happen. + * + */ +class RuleEngine +{ + /** @var int */ + public const TRIGGER_STORE = 1; + /** @var int */ + public const TRIGGER_UPDATE = 2; + + /** @var Collection */ + private $ruleGroups; + /** @var array */ + private $rulesToApply; + /** @var bool */ + private $allRules; + /** @var User */ + private $user; + /** @var RuleGroupRepository */ + private $ruleGroupRepository; + /** @var int */ + private $triggerMode; + + /** + * RuleEngine constructor. + */ + public function __construct() + { + Log::debug('Created RuleEngine'); + $this->ruleGroups = new Collection; + $this->rulesToApply = []; + $this->allRules = false; + $this->ruleGroupRepository = app(RuleGroupRepository::class); + $this->triggerMode = self::TRIGGER_STORE; + } + + /** + * @param int $triggerMode + */ + public function setTriggerMode(int $triggerMode): void + { + $this->triggerMode = $triggerMode; + } + + /** + * @param bool $allRules + */ + public function setAllRules(bool $allRules): void + { + Log::debug('RuleEngine will apply ALL rules.'); + $this->allRules = $allRules; + } + + + /** + * @param array $rulesToApply + */ + public function setRulesToApply(array $rulesToApply): void + { + Log::debug('RuleEngine will try rules', $rulesToApply); + $this->rulesToApply = $rulesToApply; + } + + /** + * @param User $user + */ + public function setUser(User $user): void + { + $this->user = $user; + $this->ruleGroupRepository->setUser($user); + $this->ruleGroups = $this->ruleGroupRepository->getActiveGroups(); + } + + /** + * @param TransactionJournal $transactionJournal + */ + public function processTransactionJournal(TransactionJournal $transactionJournal): void + { + Log::debug(sprintf('Will process transaction journal #%d ("%s")', $transactionJournal->id, $transactionJournal->description)); + /** @var RuleGroup $group */ + foreach ($this->ruleGroups as $group) { + Log::debug(sprintf('Now at rule group #%d', $group->id)); + $groupTriggered = false; + /** @var Rule $rule */ + foreach ($group->rules as $rule) { + Log::debug(sprintf('Now at rule #%d from rule group #%d', $rule->id, $group->id)); + $ruleTriggered = false; + // if in rule selection, or group in selection or all rules, it's included. + if ($this->includeRule($rule)) { + Log::debug(sprintf('Rule #%d is included.', $rule->id)); + /** @var Processor $processor */ + $processor = app(Processor::class); + $ruleTriggered = false; + try { + $processor->make($rule, true); + $ruleTriggered = $processor->handleTransactionJournal($transactionJournal); + } catch (FireflyException $e) { + Log::error($e->getMessage()); + } + + if ($ruleTriggered) { + Log::debug('The rule was triggered, so the group is as well!'); + $groupTriggered = true; + } + } + if (!$this->includeRule($rule)) { + Log::debug(sprintf('Rule #%d is not included.', $rule->id)); + } + + // if the rule is triggered and stop processing is true, cancel the entire group. + if ($ruleTriggered && $rule->stop_processing) { + Log::info(sprintf('Break out group #%d because rule #%d was triggered.', $group->id, $rule->id)); + break; + } + } + // if group is triggered and stop processing is true, cancel the whole thing. + if ($groupTriggered && $group->stop_processing) { + Log::info(sprintf('Break out ALL because group #%d was triggered.', $group->id)); + break; + } + } + Log::debug('Done processing this transaction journal.'); + } + + /** + * @param array $journal + */ + public function processJournalArray(array $journal): void + { + $journalId = $journal['id'] ?? $journal['transaction_journal_id']; + Log::debug(sprintf('Will process transaction journal #%d ("%s")', $journalId, $journal['description'])); + /** @var RuleGroup $group */ + foreach ($this->ruleGroups as $group) { + Log::debug(sprintf('Now at rule group #%d', $group->id)); + $groupTriggered = false; + /** @var Rule $rule */ + foreach ($group->rules as $rule) { + Log::debug(sprintf('Now at rule #%d from rule group #%d', $rule->id, $group->id)); + $ruleTriggered = false; + // if in rule selection, or group in selection or all rules, it's included. + if ($this->includeRule($rule)) { + Log::debug(sprintf('Rule #%d is included.', $rule->id)); + /** @var Processor $processor */ + $processor = app(Processor::class); + $ruleTriggered = false; + try { + $processor->make($rule, true); + $ruleTriggered = $processor->handleJournalArray($journal); + } catch (FireflyException $e) { + Log::error($e->getMessage()); + } + + if ($ruleTriggered) { + Log::debug('The rule was triggered, so the group is as well!'); + $groupTriggered = true; + } + } + if (!$this->includeRule($rule)) { + Log::debug(sprintf('Rule #%d is not included.', $rule->id)); + } + + // if the rule is triggered and stop processing is true, cancel the entire group. + if ($ruleTriggered && $rule->stop_processing) { + Log::info(sprintf('Break out group #%d because rule #%d was triggered.', $group->id, $rule->id)); + break; + } + } + // if group is triggered and stop processing is true, cancel the whole thing. + if ($groupTriggered && $group->stop_processing) { + Log::info(sprintf('Break out ALL because group #%d was triggered.', $group->id)); + break; + } + } + Log::debug('Done processing this transaction journal.'); + } + + /** + * @param Rule $rule + * @return bool + */ + private function includeRule(Rule $rule): bool + { + /** @var RuleTrigger $trigger */ + $trigger = $rule->ruleTriggers()->where('trigger_type', 'user_action')->first(); + if (null === $trigger) { + return false; + } + + $validTrigger = ('store-journal' === $trigger->trigger_value && self::TRIGGER_STORE === $this->triggerMode) || + ('update-journal' === $trigger->trigger_value && self::TRIGGER_UPDATE === $this->triggerMode); + + return $validTrigger && ($this->allRules || in_array($rule->id, $this->rulesToApply, true)); + } + +} \ No newline at end of file diff --git a/app/TransactionRules/Factory/ActionFactory.php b/app/TransactionRules/Factory/ActionFactory.php index 7d3cf1001e..f9864c0e5d 100644 --- a/app/TransactionRules/Factory/ActionFactory.php +++ b/app/TransactionRules/Factory/ActionFactory.php @@ -92,7 +92,7 @@ class ActionFactory */ protected static function getActionTypes(): array { - if (0 === \count(self::$actionTypes)) { + if (0 === count(self::$actionTypes)) { self::$actionTypes = Domain::getRuleActions(); } diff --git a/app/TransactionRules/Factory/TriggerFactory.php b/app/TransactionRules/Factory/TriggerFactory.php index 3bced52e35..4d5c10e69e 100644 --- a/app/TransactionRules/Factory/TriggerFactory.php +++ b/app/TransactionRules/Factory/TriggerFactory.php @@ -62,7 +62,7 @@ class TriggerFactory $obj->stopProcessing = $trigger->stop_processing; Log::debug(sprintf('self::getTriggerClass("%s") = "%s"', $triggerType, $class)); - Log::debug(sprintf('%s::makeFromTriggerValue(%s) = object of class "%s"', $class, $trigger->trigger_value, \get_class($obj))); + Log::debug(sprintf('%s::makeFromTriggerValue(%s) = object of class "%s"', $class, $trigger->trigger_value, get_class($obj))); return $obj; } @@ -101,7 +101,7 @@ class TriggerFactory */ protected static function getTriggerTypes(): array { - if (0 === \count(self::$triggerTypes)) { + if (0 === count(self::$triggerTypes)) { self::$triggerTypes = Domain::getRuleTriggers(); } diff --git a/app/TransactionRules/Processor.php b/app/TransactionRules/Processor.php index ec0ded8198..e57a655801 100644 --- a/app/TransactionRules/Processor.php +++ b/app/TransactionRules/Processor.php @@ -62,26 +62,6 @@ class Processor $this->actions = new Collection; } - /** - * Return found triggers - * - * @return int - */ - public function getFoundTriggers(): int - { - return $this->foundTriggers; - } - - /** - * Set found triggers - * - * @param int $foundTriggers - */ - public function setFoundTriggers(int $foundTriggers): void - { - $this->foundTriggers = $foundTriggers; - } - /** * Returns the rule * @@ -127,6 +107,126 @@ class Processor return false; } + /** + * Method to check whether the current transaction would be triggered + * by the given list of triggers. + * + * @return bool + */ + private function triggered(): bool + { + Log::debug('start of Processor::triggered()'); + $foundTriggers = $this->getFoundTriggers(); + $hitTriggers = 0; + Log::debug(sprintf('Found triggers starts at %d', $foundTriggers)); + /** @var AbstractTrigger $trigger */ + foreach ($this->triggers as $trigger) { + ++$foundTriggers; + Log::debug(sprintf('Now checking trigger %s with value %s', get_class($trigger), $trigger->getTriggerValue())); + /** @var AbstractTrigger $trigger */ + if ($trigger->triggered($this->journal)) { + Log::debug('Is a match!'); + ++$hitTriggers; + // is non-strict? then return true! + if (!$this->strict && UserAction::class !== get_class($trigger)) { + Log::debug('Rule is set as non-strict, return true!'); + + return true; + } + if (!$this->strict && UserAction::class === get_class($trigger)) { + Log::debug('Rule is set as non-strict, but action was "user-action". Will not return true.'); + } + } + if ($trigger->stopProcessing) { + Log::debug('Stop processing this trigger and break.'); + break; + } + } + $result = ($hitTriggers === $foundTriggers && $foundTriggers > 0); + Log::debug('Result of triggered()', ['hitTriggers' => $hitTriggers, 'foundTriggers' => $foundTriggers, 'result' => $result]); + + return $result; + } + + /** + * Return found triggers + * + * @return int + */ + public function getFoundTriggers(): int + { + return $this->foundTriggers; + } + + /** + * Set found triggers + * + * @param int $foundTriggers + */ + public function setFoundTriggers(int $foundTriggers): void + { + $this->foundTriggers = $foundTriggers; + } + + /** + * Run the actions + * + * @return void + * @throws \FireflyIII\Exceptions\FireflyException + */ + private function actions(): void + { + /** + * @var int + * @var RuleAction $action + */ + foreach ($this->actions as $action) { + /** @var ActionInterface $actionClass */ + $actionClass = ActionFactory::getAction($action); + Log::debug(sprintf('Fire action %s on journal #%d', get_class($actionClass), $this->journal->id)); + $actionClass->act($this->journal); + if ($action->stop_processing) { + Log::debug('Stop processing now and break.'); + break; + } + } + } + + /** + * This method will scan the given transaction journal and check if it matches the triggers found in the Processor + * If so, it will also attempt to run the given actions on the journal. It returns a bool indicating if the transaction journal + * matches all of the triggers (regardless of whether the Processor could act on it). + * + * @param array $journal + * + * @return bool + * @throws \FireflyIII\Exceptions\FireflyException + */ + public function handleJournalArray(array $journal): bool + { + + Log::debug(sprintf('handleJournalArray for journal #%d (group #%d)', $journal['transaction_journal_id'], $journal['transaction_group_id'])); + + // grab the actual journal. + $this->journal = TransactionJournal::find($journal['transaction_journal_id']); + // get all triggers: + $triggered = $this->triggered(); + if ($triggered) { + Log::debug('Rule is triggered, go to actions.'); + if ($this->actions->count() > 0) { + Log::debug('Has more than zero actions.'); + $this->actions(); + } + if (0 === $this->actions->count()) { + Log::info('Rule has no actions!'); + } + + return true; + } + + return false; + } + /** * This method will scan the given transaction journal and check if it matches the triggers found in the Processor * If so, it will also attempt to run the given actions on the journal. It returns a bool indicating if the transaction journal @@ -236,69 +336,4 @@ class Processor } } - - /** - * Run the actions - * - * @return void - * @throws \FireflyIII\Exceptions\FireflyException - */ - private function actions(): void - { - /** - * @var int - * @var RuleAction $action - */ - foreach ($this->actions as $action) { - /** @var ActionInterface $actionClass */ - $actionClass = ActionFactory::getAction($action); - Log::debug(sprintf('Fire action %s on journal #%d', \get_class($actionClass), $this->journal->id)); - $actionClass->act($this->journal); - if ($action->stop_processing) { - Log::debug('Stop processing now and break.'); - break; - } - } - } - - /** - * Method to check whether the current transaction would be triggered - * by the given list of triggers. - * - * @return bool - */ - private function triggered(): bool - { - Log::debug('start of Processor::triggered()'); - $foundTriggers = $this->getFoundTriggers(); - $hitTriggers = 0; - Log::debug(sprintf('Found triggers starts at %d', $foundTriggers)); - /** @var AbstractTrigger $trigger */ - foreach ($this->triggers as $trigger) { - ++$foundTriggers; - Log::debug(sprintf('Now checking trigger %s with value %s', \get_class($trigger), $trigger->getTriggerValue())); - /** @var AbstractTrigger $trigger */ - if ($trigger->triggered($this->journal)) { - Log::debug('Is a match!'); - ++$hitTriggers; - // is non-strict? then return true! - if (!$this->strict && UserAction::class !== \get_class($trigger)) { - Log::debug('Rule is set as non-strict, return true!'); - - return true; - } - if (!$this->strict && UserAction::class === \get_class($trigger)) { - Log::debug('Rule is set as non-strict, but action was "user-action". Will not return true.'); - } - } - if ($trigger->stopProcessing) { - Log::debug('Stop processing this trigger and break.'); - break; - } - } - $result = ($hitTriggers === $foundTriggers && $foundTriggers > 0); - Log::debug('Result of triggered()', ['hitTriggers' => $hitTriggers, 'foundTriggers' => $foundTriggers, 'result' => $result]); - - return $result; - } } diff --git a/app/TransactionRules/TransactionMatcher.php b/app/TransactionRules/TransactionMatcher.php index b4b568879b..f54cd3dc23 100644 --- a/app/TransactionRules/TransactionMatcher.php +++ b/app/TransactionRules/TransactionMatcher.php @@ -23,12 +23,11 @@ declare(strict_types=1); namespace FireflyIII\TransactionRules; use Carbon\Carbon; -use FireflyIII\Helpers\Collector\TransactionCollectorInterface; -use FireflyIII\Helpers\Filter\InternalTransferFilter; +use FireflyIII\Helpers\Collector\GroupCollectorInterface; use FireflyIII\Models\Rule; use FireflyIII\Models\RuleTrigger; -use FireflyIII\Models\Transaction; use FireflyIII\Models\TransactionType; +use FireflyIII\User; use Illuminate\Support\Collection; use Log; @@ -78,16 +77,16 @@ class TransactionMatcher * transaction journals matching the given rule. This is accomplished by trying to fire these * triggers onto each transaction journal until enough matches are found ($limit). * - * @return Collection + * @return array * @throws \FireflyIII\Exceptions\FireflyException */ - public function findTransactionsByRule(): Collection + public function findTransactionsByRule(): array { Log::debug('Now in findTransactionsByRule()'); - if (0 === \count($this->rule->ruleTriggers)) { + if (0 === count($this->rule->ruleTriggers)) { Log::error('Rule has no triggers!'); - return new Collection; + return []; } // Variables used within the loop. @@ -98,7 +97,7 @@ class TransactionMatcher // If the list of matchingTransactions is larger than the maximum number of results // (e.g. if a large percentage of the transactions match), truncate the list - $result = $result->slice(0, $this->searchLimit); + $result = array_slice($result, 0, $this->searchLimit); return $result; } @@ -113,7 +112,7 @@ class TransactionMatcher */ public function findTransactionsByTriggers(): Collection { - if (0 === \count($this->triggers)) { + if (0 === count($this->triggers)) { return new Collection; } @@ -254,7 +253,7 @@ class TransactionMatcher * * @return Collection */ - private function runProcessor(Processor $processor): Collection + private function runProcessor(Processor $processor): array { Log::debug('Now in runprocessor()'); // since we have a rule in $this->rule, we can add some of the triggers @@ -266,25 +265,26 @@ class TransactionMatcher // - all transactions have been fetched from the database // - the maximum number of transactions to return has been found // - the maximum number of transactions to search in have been searched - $pageSize = min($this->searchLimit, min($this->triggeredLimit, 50)); - $processed = 0; - $page = 1; - $result = new Collection; + $pageSize = min($this->searchLimit, min($this->triggeredLimit, 50)); + $processed = 0; + $page = 1; + $totalResult = []; Log::debug(sprintf('Search limit is %d, triggered limit is %d, so page size is %d', $this->searchLimit, $this->triggeredLimit, $pageSize)); do { Log::debug('Start of do-loop'); // Fetch a batch of transactions from the database - /** @var TransactionCollectorInterface $collector */ - $collector = app(TransactionCollectorInterface::class); - $collector->setUser(auth()->user()); - $collector->withOpposingAccount(); + + /** @var GroupCollectorInterface $collector */ + $collector = app(GroupCollectorInterface::class); + + /** @var User $user */ + $user = auth()->user(); + + $collector->setUser($user); // limit asset accounts: - if (0 === $this->accounts->count()) { - $collector->setAllAssetAccounts(); - } if ($this->accounts->count() > 0) { $collector->setAccounts($this->accounts); } @@ -305,36 +305,35 @@ class TransactionMatcher Log::debug(sprintf('Amount must be exactly %s', $this->exactAmount)); $collector->amountIs($this->exactAmount); } - $collector->removeFilter(InternalTransferFilter::class); - $set = $collector->getPaginatedTransactions(); - Log::debug(sprintf('Found %d journals to check. ', $set->count())); + $journals = $collector->getExtractedJournals(); + Log::debug(sprintf('Found %d transaction journals to check. ', count($journals))); // Filter transactions that match the given triggers. - $filtered = $set->filter( - function (Transaction $transaction) use ($processor) { - Log::debug(sprintf('Test the triggers on journal #%d (transaction #%d)', $transaction->transaction_journal_id, $transaction->id)); - - return $processor->handleTransaction($transaction); + $filtered = []; + /** @var array $journal */ + foreach ($journals as $journal) { + $result = $processor->handleJournalArray($journal); + if ($result) { + $filtered[] = $journal; } - ); + } - Log::debug(sprintf('Found %d journals that match.', $filtered->count())); + Log::debug(sprintf('Found %d journals that match.', count($filtered))); // merge: - /** @var Collection $result */ - $result = $result->merge($filtered); - Log::debug(sprintf('Total count is now %d', $result->count())); + $totalResult = $totalResult + $filtered; + Log::debug(sprintf('Total count is now %d', count($totalResult))); // Update counters ++$page; - $processed += \count($set); + $processed += count($journals); Log::debug(sprintf('Page is now %d, processed is %d', $page, $processed)); // Check for conditions to finish the loop - $reachedEndOfList = $set->count() < 1; - $foundEnough = $result->count() >= $this->triggeredLimit; + $reachedEndOfList = count($journals) < 1; + $foundEnough = count($totalResult) >= $this->triggeredLimit; $searchedEnough = ($processed >= $this->searchLimit); Log::debug(sprintf('reachedEndOfList: %s', var_export($reachedEndOfList, true))); @@ -343,6 +342,6 @@ class TransactionMatcher } while (!$reachedEndOfList && !$foundEnough && !$searchedEnough); Log::debug('End of do-loop'); - return $result; + return $totalResult; } } diff --git a/app/TransactionRules/Triggers/CurrencyIs.php b/app/TransactionRules/Triggers/CurrencyIs.php index d936a47a5e..03fdc9b5b6 100644 --- a/app/TransactionRules/Triggers/CurrencyIs.php +++ b/app/TransactionRules/Triggers/CurrencyIs.php @@ -70,8 +70,15 @@ final class CurrencyIs extends AbstractTrigger implements TriggerInterface { /** @var CurrencyRepositoryInterface $repository */ $repository = app(CurrencyRepositoryInterface::class); - $currency = $repository->findByNameNull($this->triggerValue); - $hit = true; + + // if currency name contains " (" + if (0 === strpos($this->triggerValue, ' (')) { + $parts = explode(' (', $this->triggerValue); + $this->triggerValue = $parts[0]; + } + + $currency = $repository->findByNameNull($this->triggerValue); + $hit = true; if (null !== $currency) { /** @var Transaction $transaction */ foreach ($journal->transactions as $transaction) { diff --git a/app/TransactionRules/Triggers/DescriptionEnds.php b/app/TransactionRules/Triggers/DescriptionEnds.php index 71bb7e5f2f..d9215798ce 100644 --- a/app/TransactionRules/Triggers/DescriptionEnds.php +++ b/app/TransactionRules/Triggers/DescriptionEnds.php @@ -72,9 +72,9 @@ final class DescriptionEnds extends AbstractTrigger implements TriggerInterface public function triggered(TransactionJournal $journal): bool { $description = strtolower($journal->description ?? ''); - $descriptionLength = \strlen($description); + $descriptionLength = strlen($description); $search = strtolower($this->triggerValue); - $searchLength = \strlen($search); + $searchLength = strlen($search); // if the string to search for is longer than the description, // return false. diff --git a/app/TransactionRules/Triggers/DescriptionStarts.php b/app/TransactionRules/Triggers/DescriptionStarts.php index 250f0ae3e3..77c665c711 100644 --- a/app/TransactionRules/Triggers/DescriptionStarts.php +++ b/app/TransactionRules/Triggers/DescriptionStarts.php @@ -74,7 +74,7 @@ final class DescriptionStarts extends AbstractTrigger implements TriggerInterfac $description = strtolower($journal->description ?? ''); $search = strtolower($this->triggerValue); - $part = substr($description, 0, \strlen($search)); + $part = substr($description, 0, strlen($search)); if ($part === $search) { Log::debug(sprintf('RuleTrigger DescriptionStarts for journal #%d: "%s" starts with "%s", return true.', $journal->id, $description, $search)); diff --git a/app/TransactionRules/Triggers/FromAccountEnds.php b/app/TransactionRules/Triggers/FromAccountEnds.php index 8aa5a253d6..2dc567e935 100644 --- a/app/TransactionRules/Triggers/FromAccountEnds.php +++ b/app/TransactionRules/Triggers/FromAccountEnds.php @@ -82,9 +82,9 @@ final class FromAccountEnds extends AbstractTrigger implements TriggerInterface $name .= strtolower($account->name); } - $nameLength = \strlen($name); + $nameLength = strlen($name); $search = strtolower($this->triggerValue); - $searchLength = \strlen($search); + $searchLength = strlen($search); // if the string to search for is longer than the account name, // it will never be in the account name. diff --git a/app/TransactionRules/Triggers/FromAccountStarts.php b/app/TransactionRules/Triggers/FromAccountStarts.php index 25b916b439..7cdc74e752 100644 --- a/app/TransactionRules/Triggers/FromAccountStarts.php +++ b/app/TransactionRules/Triggers/FromAccountStarts.php @@ -83,7 +83,7 @@ final class FromAccountStarts extends AbstractTrigger implements TriggerInterfac } $search = strtolower($this->triggerValue); - $part = substr($name, 0, \strlen($search)); + $part = substr($name, 0, strlen($search)); if ($part === $search) { Log::debug(sprintf('RuleTrigger FromAccountStarts for journal #%d: "%s" starts with "%s", return true.', $journal->id, $name, $search)); diff --git a/app/TransactionRules/Triggers/NotesEnd.php b/app/TransactionRules/Triggers/NotesEnd.php index 3eb265ab5c..ebead68e0f 100644 --- a/app/TransactionRules/Triggers/NotesEnd.php +++ b/app/TransactionRules/Triggers/NotesEnd.php @@ -78,9 +78,9 @@ final class NotesEnd extends AbstractTrigger implements TriggerInterface if (null !== $note) { $text = strtolower($note->text); } - $notesLength = \strlen($text); + $notesLength = strlen($text); $search = strtolower($this->triggerValue); - $searchLength = \strlen($search); + $searchLength = strlen($search); // if the string to search for is longer than the description, // return false diff --git a/app/TransactionRules/Triggers/NotesStart.php b/app/TransactionRules/Triggers/NotesStart.php index 4bef3f85e2..2ba1d5df11 100644 --- a/app/TransactionRules/Triggers/NotesStart.php +++ b/app/TransactionRules/Triggers/NotesStart.php @@ -80,7 +80,7 @@ final class NotesStart extends AbstractTrigger implements TriggerInterface } $search = strtolower($this->triggerValue); - $part = substr($text, 0, \strlen($search)); + $part = substr($text, 0, strlen($search)); if ($part === $search) { Log::debug(sprintf('RuleTrigger NotesStart for journal #%d: "%s" starts with "%s", return true.', $journal->id, $text, $search)); diff --git a/app/TransactionRules/Triggers/ToAccountEnds.php b/app/TransactionRules/Triggers/ToAccountEnds.php index a9474e32c0..3026ea6afa 100644 --- a/app/TransactionRules/Triggers/ToAccountEnds.php +++ b/app/TransactionRules/Triggers/ToAccountEnds.php @@ -82,9 +82,9 @@ final class ToAccountEnds extends AbstractTrigger implements TriggerInterface $toAccountName .= strtolower($account->name); } - $toAccountNameLength = \strlen($toAccountName); + $toAccountNameLength = strlen($toAccountName); $search = strtolower($this->triggerValue); - $searchLength = \strlen($search); + $searchLength = strlen($search); // if the string to search for is longer than the account name, // return false diff --git a/app/TransactionRules/Triggers/ToAccountStarts.php b/app/TransactionRules/Triggers/ToAccountStarts.php index 8c813b499f..861e36d983 100644 --- a/app/TransactionRules/Triggers/ToAccountStarts.php +++ b/app/TransactionRules/Triggers/ToAccountStarts.php @@ -83,7 +83,7 @@ final class ToAccountStarts extends AbstractTrigger implements TriggerInterface } $search = strtolower($this->triggerValue); - $part = substr($toAccountName, 0, \strlen($search)); + $part = substr($toAccountName, 0, strlen($search)); if ($part === $search) { Log::debug(sprintf('RuleTrigger ToAccountStarts for journal #%d: "%s" starts with "%s", return true.', $journal->id, $toAccountName, $search)); diff --git a/app/Transformers/AccountTransformer.php b/app/Transformers/AccountTransformer.php index 53f45f5344..c360ee651a 100644 --- a/app/Transformers/AccountTransformer.php +++ b/app/Transformers/AccountTransformer.php @@ -46,7 +46,7 @@ class AccountTransformer extends AbstractTransformer public function __construct() { if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } $this->repository = app(AccountRepositoryInterface::class); @@ -105,7 +105,7 @@ class AccountTransformer extends AbstractTransformer 'notes' => $this->repository->getNoteText($account), 'monthly_payment_date' => $monthlyPaymentDate, 'credit_card_type' => $creditCardType, - 'account_number' => $this->repository->getMetaValue($account, 'accountNumber'), + 'account_number' => $this->repository->getMetaValue($account, 'account_number'), 'iban' => '' === $account->iban ? null : $account->iban, 'bic' => $this->repository->getMetaValue($account, 'BIC'), 'virtual_balance' => round($account->virtual_balance, $decimalPlaces), @@ -137,7 +137,7 @@ class AccountTransformer extends AbstractTransformer */ private function getAccountRole(Account $account, string $accountType): ?string { - $accountRole = $this->repository->getMetaValue($account, 'accountRole'); + $accountRole = $this->repository->getMetaValue($account, 'account_role'); if ('asset' !== $accountType || '' === (string)$accountRole) { $accountRole = null; } @@ -157,8 +157,8 @@ class AccountTransformer extends AbstractTransformer $monthlyPaymentDate = null; $creditCardType = null; if ('ccAsset' === $accountRole && 'asset' === $accountType) { - $creditCardType = $this->repository->getMetaValue($account, 'ccType'); - $monthlyPaymentDate = $this->repository->getMetaValue($account, 'ccMonthlyPaymentDate'); + $creditCardType = $this->repository->getMetaValue($account, 'cc_type'); + $monthlyPaymentDate = $this->repository->getMetaValue($account, 'cc_monthly_payment_date'); } return [$creditCardType, $monthlyPaymentDate]; @@ -229,7 +229,7 @@ class AccountTransformer extends AbstractTransformer { $openingBalance = null; $openingBalanceDate = null; - if (\in_array($accountType, ['asset', 'liabilities'], true)) { + if (in_array($accountType, ['asset', 'liabilities'], true)) { $amount = $this->repository->getOpeningBalanceAmount($account); $openingBalance = null === $amount ? null : round($amount, $decimalPlaces); $openingBalanceDate = $this->repository->getOpeningBalanceDate($account); diff --git a/app/Transformers/AttachmentTransformer.php b/app/Transformers/AttachmentTransformer.php index aedb1598ea..f7c16b80e2 100644 --- a/app/Transformers/AttachmentTransformer.php +++ b/app/Transformers/AttachmentTransformer.php @@ -45,7 +45,7 @@ class AttachmentTransformer extends AbstractTransformer { $this->repository = app(AttachmentRepositoryInterface::class); if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } diff --git a/app/Transformers/AvailableBudgetTransformer.php b/app/Transformers/AvailableBudgetTransformer.php index 8043a07513..0f02659580 100644 --- a/app/Transformers/AvailableBudgetTransformer.php +++ b/app/Transformers/AvailableBudgetTransformer.php @@ -46,7 +46,7 @@ class AvailableBudgetTransformer extends AbstractTransformer { $this->repository = app(BudgetRepositoryInterface::class); if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } diff --git a/app/Transformers/BillTransformer.php b/app/Transformers/BillTransformer.php index b9428efed8..84a4a8fe67 100644 --- a/app/Transformers/BillTransformer.php +++ b/app/Transformers/BillTransformer.php @@ -46,7 +46,7 @@ class BillTransformer extends AbstractTransformer { $this->repository = app(BillRepositoryInterface::class); if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } diff --git a/app/Transformers/BudgetLimitTransformer.php b/app/Transformers/BudgetLimitTransformer.php index 414f201a48..65b8772b10 100644 --- a/app/Transformers/BudgetLimitTransformer.php +++ b/app/Transformers/BudgetLimitTransformer.php @@ -39,7 +39,7 @@ class BudgetLimitTransformer extends AbstractTransformer public function __construct() { if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } diff --git a/app/Transformers/BudgetTransformer.php b/app/Transformers/BudgetTransformer.php index c6273ef5ca..7243ac9e7a 100644 --- a/app/Transformers/BudgetTransformer.php +++ b/app/Transformers/BudgetTransformer.php @@ -45,7 +45,7 @@ class BudgetTransformer extends AbstractTransformer { $this->repository = app(BudgetRepositoryInterface::class); if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } diff --git a/app/Transformers/CategoryTransformer.php b/app/Transformers/CategoryTransformer.php index 1b33b44414..fec2cb6141 100644 --- a/app/Transformers/CategoryTransformer.php +++ b/app/Transformers/CategoryTransformer.php @@ -49,7 +49,7 @@ class CategoryTransformer extends AbstractTransformer { $this->repository = app(CategoryRepositoryInterface::class); if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } diff --git a/app/Transformers/CurrencyExchangeRateTransformer.php b/app/Transformers/CurrencyExchangeRateTransformer.php index 20f52caaa2..45e6585dd1 100644 --- a/app/Transformers/CurrencyExchangeRateTransformer.php +++ b/app/Transformers/CurrencyExchangeRateTransformer.php @@ -41,7 +41,7 @@ class CurrencyExchangeRateTransformer extends AbstractTransformer public function __construct() { if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } diff --git a/app/Transformers/CurrencyTransformer.php b/app/Transformers/CurrencyTransformer.php index 0525106d0f..fb7a26c151 100644 --- a/app/Transformers/CurrencyTransformer.php +++ b/app/Transformers/CurrencyTransformer.php @@ -39,7 +39,7 @@ class CurrencyTransformer extends AbstractTransformer public function __construct() { if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } diff --git a/app/Transformers/ImportJobTransformer.php b/app/Transformers/ImportJobTransformer.php index a9356c7e38..addfd0b180 100644 --- a/app/Transformers/ImportJobTransformer.php +++ b/app/Transformers/ImportJobTransformer.php @@ -40,7 +40,7 @@ class ImportJobTransformer extends AbstractTransformer public function __construct() { if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } diff --git a/app/Transformers/LinkTypeTransformer.php b/app/Transformers/LinkTypeTransformer.php index 6471d7b07f..7bb00626d0 100644 --- a/app/Transformers/LinkTypeTransformer.php +++ b/app/Transformers/LinkTypeTransformer.php @@ -41,7 +41,7 @@ class LinkTypeTransformer extends AbstractTransformer public function __construct() { if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } diff --git a/app/Transformers/PiggyBankEventTransformer.php b/app/Transformers/PiggyBankEventTransformer.php index 264bf68770..dd9f1d40ab 100644 --- a/app/Transformers/PiggyBankEventTransformer.php +++ b/app/Transformers/PiggyBankEventTransformer.php @@ -53,7 +53,7 @@ class PiggyBankEventTransformer extends AbstractTransformer $this->currencyRepos = app(CurrencyRepositoryInterface::class); $this->piggyRepos = app(PiggyBankRepositoryInterface::class); if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } @@ -82,8 +82,7 @@ class PiggyBankEventTransformer extends AbstractTransformer } // get associated journal and transaction, if any: - $journalId = $event->transaction_journal_id; - $transactionId = $this->piggyRepos->getTransactionWithEvent($event); + $journalId = (int)$event->transaction_journal_id; $data = [ 'id' => (int)$event->id, @@ -94,8 +93,7 @@ class PiggyBankEventTransformer extends AbstractTransformer 'currency_code' => $currency->code, 'currency_symbol' => $currency->symbol, 'currency_decimal_places' => $currency->decimal_places, - 'journal_id' => $journalId, - 'transaction_id' => $transactionId, + 'transaction_journal_id' => $journalId, 'links' => [ [ 'rel' => 'self', diff --git a/app/Transformers/PiggyBankTransformer.php b/app/Transformers/PiggyBankTransformer.php index f922c3ff03..268994ba22 100644 --- a/app/Transformers/PiggyBankTransformer.php +++ b/app/Transformers/PiggyBankTransformer.php @@ -54,7 +54,7 @@ class PiggyBankTransformer extends AbstractTransformer $this->currencyRepos = app(CurrencyRepositoryInterface::class); $this->piggyRepos = app(PiggyBankRepositoryInterface::class); if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } diff --git a/app/Transformers/PreferenceTransformer.php b/app/Transformers/PreferenceTransformer.php index 3087487da6..d817891987 100644 --- a/app/Transformers/PreferenceTransformer.php +++ b/app/Transformers/PreferenceTransformer.php @@ -41,7 +41,7 @@ class PreferenceTransformer extends AbstractTransformer public function __construct() { if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } diff --git a/app/Transformers/RecurrenceTransformer.php b/app/Transformers/RecurrenceTransformer.php index ee11a0a81f..a1085c0f41 100644 --- a/app/Transformers/RecurrenceTransformer.php +++ b/app/Transformers/RecurrenceTransformer.php @@ -69,7 +69,7 @@ class RecurrenceTransformer extends AbstractTransformer $this->budgetRepos = app(BudgetRepositoryInterface::class); if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } @@ -267,6 +267,30 @@ class RecurrenceTransformer extends AbstractTransformer $foreignCurrencySymbol = $transaction->foreignCurrency->symbol; $foreignCurrencyDp = $transaction->foreignCurrency->decimal_places; } + + // source info: + $sourceName = ''; + $sourceId = null; + $sourceType = null; + $sourceIban = null; + if (null !== $sourceAccount) { + $sourceName = $sourceAccount->name; + $sourceId = $sourceAccount->id; + $sourceType = $sourceAccount->accountType->type; + $sourceIban = $sourceAccount->iban; + } + $destinationName = ''; + $destinationId = null; + $destinationType = null; + $destinationIban = null; + if (null !== $destinationAccount) { + $destinationName = $destinationAccount->name; + $destinationId = $destinationAccount->id; + $destinationType = $destinationAccount->accountType->type; + $destinationIban = $destinationAccount->iban; + } + + $amount = round($transaction->amount, $transaction->transactionCurrency->decimal_places); $foreignAmount = null; if (null !== $transaction->foreign_currency_id && null !== $transaction->foreign_amount) { @@ -281,10 +305,14 @@ class RecurrenceTransformer extends AbstractTransformer 'foreign_currency_code' => $foreignCurrencyCode, 'foreign_currency_symbol' => $foreignCurrencySymbol, 'foreign_currency_decimal_places' => $foreignCurrencyDp, - 'source_id' => $transaction->source_id, - 'source_name' => null === $sourceAccount ? '' : $sourceAccount->name, - 'destination_id' => $transaction->destination_id, - 'destination_name' => null === $destinationAccount ? '' : $destinationAccount->name, + 'source_id' => $sourceId, + 'source_name' => $sourceName, + 'source_iban' => $sourceIban, + 'source_type' => $sourceType, + 'destination_id' => $destinationId, + 'destination_name' => $destinationName, + 'destination_iban' => $destinationIban, + 'destination_type' => $destinationType, 'amount' => $amount, 'foreign_amount' => $foreignAmount, 'description' => $transaction->description, diff --git a/app/Transformers/RuleGroupTransformer.php b/app/Transformers/RuleGroupTransformer.php index c8a0a87f5f..0aea33dcfb 100644 --- a/app/Transformers/RuleGroupTransformer.php +++ b/app/Transformers/RuleGroupTransformer.php @@ -39,7 +39,7 @@ class RuleGroupTransformer extends AbstractTransformer public function __construct() { if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } diff --git a/app/Transformers/RuleTransformer.php b/app/Transformers/RuleTransformer.php index f8f825ae31..7eeeeede04 100644 --- a/app/Transformers/RuleTransformer.php +++ b/app/Transformers/RuleTransformer.php @@ -47,7 +47,7 @@ class RuleTransformer extends AbstractTransformer { $this->ruleRepository = app(RuleRepositoryInterface::class); if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } diff --git a/app/Transformers/TagTransformer.php b/app/Transformers/TagTransformer.php index 54c9063f03..964f7c3b7d 100644 --- a/app/Transformers/TagTransformer.php +++ b/app/Transformers/TagTransformer.php @@ -40,7 +40,7 @@ class TagTransformer extends AbstractTransformer public function __construct() { if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } diff --git a/app/Transformers/TransactionGroupTransformer.php b/app/Transformers/TransactionGroupTransformer.php new file mode 100644 index 0000000000..1248bb81f4 --- /dev/null +++ b/app/Transformers/TransactionGroupTransformer.php @@ -0,0 +1,363 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Transformers; + +use FireflyIII\Models\Budget; +use FireflyIII\Models\Category; +use FireflyIII\Models\Transaction; +use FireflyIII\Models\TransactionGroup; +use FireflyIII\Models\TransactionJournal; +use FireflyIII\Models\TransactionType; +use FireflyIII\Repositories\TransactionGroup\TransactionGroupRepositoryInterface; +use FireflyIII\Support\NullArrayObject; +use Illuminate\Support\Collection; + +/** + * Class TransactionGroupTransformer + */ +class TransactionGroupTransformer extends AbstractTransformer +{ + /** @var TransactionGroupRepositoryInterface */ + private $groupRepos; + /** @var array Array with meta date fields. */ + private $metaDateFields; + /** @var array Array with meta fields. */ + private $metaFields; + + /** + * Constructor. + * + * @codeCoverageIgnore + */ + public function __construct() + { + $this->groupRepos = app(TransactionGroupRepositoryInterface::class); + $this->metaFields = [ + 'sepa_cc', 'sepa_ct_op', 'sepa_ct_id', 'sepa_db', 'sepa_country', 'sepa_ep', + 'sepa_ci', 'sepa_batch_id', 'internal_reference', 'bunq_payment_id', 'import_hash_v2', + 'recurrence_id', 'external_id', 'original_source', + ]; + $this->metaDateFields = ['interest_date', 'book_date', 'process_date', 'due_date', 'payment_date', 'invoice_date']; + + if ('testing' === config('app.env')) { + app('log')->warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); + } + } + + /** + * @param array $group + * + * @return array + */ + public function transform(array $group): array + { + $data = new NullArrayObject($group); + $first = new NullArrayObject(reset($group['transactions'])); + $result = [ + 'id' => (int)$first['transaction_group_id'], + 'created_at' => $first['created_at']->toAtomString(), + 'updated_at' => $first['updated_at']->toAtomString(), + 'user' => (int)$data['user_id'], + 'group_title' => $data['title'], + 'transactions' => $this->transformTransactions($data), + 'links' => [ + [ + 'rel' => 'self', + 'uri' => '/transactions/' . $first['transaction_group_id'], + ], + ], + ]; + + // do something else. + + return $result; + } + + /** + * @param TransactionGroup $group + * + * @return array + */ + public function transformObject(TransactionGroup $group): array + { + $result = [ + 'id' => (int)$group->id, + 'created_at' => $group->created_at->toAtomString(), + 'updated_at' => $group->updated_at->toAtomString(), + 'user' => (int)$group->user_id, + 'group_title' => $group->title, + 'transactions' => $this->transformJournals($group->transactionJournals), + 'links' => [ + [ + 'rel' => 'self', + 'uri' => '/transactions/' . $group->id, + ], + ], + ]; + + // do something else. + + return $result; + } + + /** + * @param TransactionJournal $journal + * + * @return Transaction + */ + private function getDestinationTransaction(TransactionJournal $journal): Transaction + { + + return $journal->transactions->first( + static function (Transaction $transaction) { + return (float)$transaction->amount > 0; + } + ); + } + + /** + * @param TransactionJournal $journal + * + * @return Transaction + */ + private function getSourceTransaction(TransactionJournal $journal): Transaction + { + + return $journal->transactions->first( + static function (Transaction $transaction) { + return (float)$transaction->amount < 0; + } + ); + } + + /** + * @param Collection $transactionJournals + * + * @return array + */ + private function transformJournals(Collection $transactionJournals): array + { + $result = []; + /** @var TransactionJournal $journal */ + foreach ($transactionJournals as $journal) { + $source = $this->getSourceTransaction($journal); + $destination = $this->getDestinationTransaction($journal); + $type = $journal->transactionType->type; + + // get amount + $amount = app('steam')->positive($source->amount); + if (TransactionType::WITHDRAWAL !== $type) { + $amount = app('steam')->negative($source->amount); + } + + // get foreign amount: + $foreignAmount = null; + // @codeCoverageIgnoreStart + if (null !== $source->foreign_amount) { + $foreignAmount = TransactionType::WITHDRAWAL !== $type + ? app('steam')->negative($source->foreign_amount) + : app('steam')->positive($source->foreign_amount); + } + // @codeCoverageIgnoreEnd + + $metaFieldData = $this->groupRepos->getMetaFields($journal->id, $this->metaFields); + $metaDateData = $this->groupRepos->getMetaDateFields($journal->id, $this->metaDateFields); + /** @var Budget $budget */ + $budget = $journal->budgets->first(); + /** @var Category $category */ + $category = $journal->categories->first(); + $currency = $source->transactionCurrency; + $result[] = [ + 'user' => (int)$journal->user_id, + 'transaction_journal_id' => $journal->id, + 'type' => strtolower($type), + 'date' => $journal->date->toAtomString(), + 'order' => $journal->order, + + 'currency_id' => $currency->id, + 'currency_code' => $currency->code, + 'currency_symbol' => $currency->symbol, + 'currency_decimal_places' => $currency->decimal_places, + + 'foreign_currency_id' => $source->foreignCurrency ? $source->foreignCurrency->id : null, + 'foreign_currency_code' => $source->foreignCurrency ? $source->foreignCurrency->code : null, + 'foreign_currency_symbol' => $source->foreignCurrency ? $source->foreignCurrency->symbol : null, + 'foreign_currency_decimal_places' => $source->foreignCurrency ? $source->foreignCurrency->decimal_places : null, + + 'amount' => $amount, + 'foreign_amount' => $foreignAmount, + + 'description' => $journal->description, + + 'source_id' => $source->account_id, + 'source_name' => $source->account->name, + 'source_iban' => $source->account->iban, + 'source_type' => $source->account->accountType->type, + + 'destination_id' => $destination->account_id, + 'destination_name' => $destination->account->name, + 'destination_iban' => $destination->account->iban, + 'destination_type' => $destination->account->accountType->type, + + 'budget_id' => $budget ? $budget->id : null, + 'budget_name' => $budget ? $budget->name : null, + + 'category_id' => $category ? $category->id : null, + 'category_name' => $category ? $category->name : null, + + 'bill_id' => $journal->bill_id ?: null, + 'bill_name' => $journal->bill_id ? $journal->bill->name : null, + + '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'], + + 'sepa_cc' => $metaFieldData['sepa_cc'], + 'sepa_ct_op' => $metaFieldData['sepa_ct_op'], + 'sepa_ct_id' => $metaFieldData['sepa_ct_id'], + 'sepa_db' => $metaFieldData['sepa_ddb'], + 'sepa_country' => $metaFieldData['sepa_country'], + 'sepa_ep' => $metaFieldData['sepa_ep'], + 'sepa_ci' => $metaFieldData['sepa_ci'], + 'sepa_batch_id' => $metaFieldData['sepa_batch_id'], + + 'interest_date' => $metaDateData['interest_date'] ? $metaDateData['interest_date']->toAtomString() : null, + 'book_date' => $metaDateData['book_date'] ? $metaDateData['book_date']->toAtomString() : null, + 'process_date' => $metaDateData['process_date'] ? $metaDateData['process_date']->toAtomString() : null, + 'due_date' => $metaDateData['due_date'] ? $metaDateData['due_date']->toAtomString() : null, + 'payment_date' => $metaDateData['payment_date'] ? $metaDateData['payment_date']->toAtomString() : null, + 'invoice_date' => $metaDateData['invoice_date'] ? $metaDateData['invoice_date']->toAtomString() : null, + ]; + } + + return $result; + } + + /** + * @param NullArrayObject $data + * + * @return array + */ + private function transformTransactions(NullArrayObject $data): array + { + $result = []; + $transactions = $data['transactions'] ?? []; + foreach ($transactions as $transaction) { + $row = new NullArrayObject($transaction); + + // amount: + $type = $row['transaction_type_type'] ?? TransactionType::WITHDRAWAL; + $amount = $row['amount'] ?? '0'; + if (TransactionType::WITHDRAWAL !== $type) { + $amount = bcmul($amount, '-1'); + } + $foreignAmount = null; + if (null !== $row['foreign_amount']) { + $foreignAmount = TransactionType::WITHDRAWAL !== $type ? bcmul($row['foreign_amount'], '-1') : $row['foreign_amount']; // @codeCoverageIgnore + } + + $metaFieldData = $this->groupRepos->getMetaFields((int)$row['transaction_journal_id'], $this->metaFields); + $metaDateData = $this->groupRepos->getMetaDateFields((int)$row['transaction_journal_id'], $this->metaDateFields); + + $result[] = [ + 'user' => (int)$row['user_id'], + 'transaction_journal_id' => $row['transaction_journal_id'], + 'type' => strtolower($type), + 'date' => $row['date']->toAtomString(), + 'order' => $row['order'], + + 'currency_id' => $row['currency_id'], + 'currency_code' => $row['currency_code'], + 'currency_name' => $row['currency_name'], + 'currency_symbol' => $row['currency_symbol'], + 'currency_decimal_places' => $row['currency_decimal_places'], + + 'foreign_currency_id' => $row['foreign_currency_id'], + 'foreign_currency_code' => $row['foreign_currency_code'], + 'foreign_currency_symbol' => $row['foreign_currency_symbol'], + 'foreign_currency_decimal_places' => $row['foreign_currency_decimal_places'], + + 'amount' => $amount, + 'foreign_amount' => $foreignAmount, + + 'description' => $row['description'], + + 'source_id' => $row['source_account_id'], + 'source_name' => $row['source_account_name'], + 'source_iban' => $row['source_account_iban'], + 'source_type' => $row['source_account_type'], + + 'destination_id' => $row['destination_account_id'], + 'destination_name' => $row['destination_account_name'], + 'destination_iban' => $row['destination_account_iban'], + 'destination_type' => $row['destination_account_type'], + + 'budget_id' => $row['budget_id'], + 'budget_name' => $row['budget_name'], + + 'category_id' => $row['category_id'], + 'category_name' => $row['category_name'], + + 'bill_id' => $row['bill_id'], + 'bill_name' => $row['bill_name'], + + 'reconciled' => $row['reconciled'], + 'notes' => $this->groupRepos->getNoteText((int)$row['transaction_journal_id']), + 'tags' => $this->groupRepos->getTags((int)$row['transaction_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'], + + 'sepa_cc' => $metaFieldData['sepa_cc'], + 'sepa_ct_op' => $metaFieldData['sepa_ct_op'], + 'sepa_ct_id' => $metaFieldData['sepa_ct_id'], + 'sepa_db' => $metaFieldData['sepa_ddb'], + 'sepa_country' => $metaFieldData['sepa_country'], + 'sepa_ep' => $metaFieldData['sepa_ep'], + 'sepa_ci' => $metaFieldData['sepa_ci'], + 'sepa_batch_id' => $metaFieldData['sepa_batch_id'], + + 'interest_date' => $metaDateData['interest_date'] ? $metaDateData['interest_date']->toAtomString() : null, + 'book_date' => $metaDateData['book_date'] ? $metaDateData['book_date']->toAtomString() : null, + 'process_date' => $metaDateData['process_date'] ? $metaDateData['process_date']->toAtomString() : null, + 'due_date' => $metaDateData['due_date'] ? $metaDateData['due_date']->toAtomString() : null, + 'payment_date' => $metaDateData['payment_date'] ? $metaDateData['payment_date']->toAtomString() : null, + 'invoice_date' => $metaDateData['invoice_date'] ? $metaDateData['invoice_date']->toAtomString() : null, + ]; + } + + return $result; + } +} \ No newline at end of file diff --git a/app/Transformers/TransactionLinkTransformer.php b/app/Transformers/TransactionLinkTransformer.php index 656083ea76..9b104f894e 100644 --- a/app/Transformers/TransactionLinkTransformer.php +++ b/app/Transformers/TransactionLinkTransformer.php @@ -47,7 +47,7 @@ class TransactionLinkTransformer extends AbstractTransformer $this->repository = app(JournalRepositoryInterface::class); if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } diff --git a/app/Transformers/TransactionTransformer.php b/app/Transformers/TransactionTransformer.php deleted file mode 100644 index 353b9fffa6..0000000000 --- a/app/Transformers/TransactionTransformer.php +++ /dev/null @@ -1,213 +0,0 @@ -. - */ - -declare(strict_types=1); - -namespace FireflyIII\Transformers; - - -use FireflyIII\Exceptions\FireflyException; -use FireflyIII\Models\Transaction; -use FireflyIII\Models\TransactionType; -use FireflyIII\Repositories\Journal\JournalRepositoryInterface; -use Log; - -/** - * Class TransactionTransformer - */ -class TransactionTransformer extends AbstractTransformer -{ - /** @var JournalRepositoryInterface */ - protected $repository; - - /** - * TransactionTransformer constructor. - * - * @codeCoverageIgnore - */ - public function __construct() - { - $this->repository = app(JournalRepositoryInterface::class); - if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); - } - } - - /** - * Transform the journal. - * - * @param Transaction $transaction - * - * @return array - * @throws FireflyException - */ - public function transform(Transaction $transaction): array - { - $journal = $transaction->transactionJournal; - $category = $this->getCategory($transaction); - $budget = $this->getBudget($transaction); - - $this->repository->setUser($journal->user); - - $notes = $this->repository->getNoteText($journal); - $tags = implode(',', $this->repository->getTags($journal)); - - $data = [ - 'id' => (int)$transaction->id, - 'created_at' => $transaction->created_at->toAtomString(), - 'updated_at' => $transaction->updated_at->toAtomString(), - 'description' => $transaction->description, - 'journal_description' => $transaction->description, - 'transaction_description' => $transaction->transaction_description, - 'date' => $transaction->date->toAtomString(), - 'type' => $transaction->transaction_type_type, - 'identifier' => $transaction->identifier, - 'journal_id' => (int)$transaction->journal_id, - 'reconciled' => (bool)$transaction->reconciled, - 'amount' => round($transaction->transaction_amount, (int)$transaction->transaction_currency_dp), - 'currency_id' => $transaction->transaction_currency_id, - 'currency_code' => $transaction->transaction_currency_code, - 'currency_symbol' => $transaction->transaction_currency_symbol, - 'currency_decimal_places' => $transaction->transaction_currency_dp, - 'foreign_amount' => null, - 'foreign_currency_id' => $transaction->foreign_currency_id, - 'foreign_currency_code' => $transaction->foreign_currency_code, - 'foreign_currency_symbol' => $transaction->foreign_currency_symbol, - 'foreign_currency_decimal_places' => $transaction->foreign_currency_dp, - 'bill_id' => $transaction->bill_id, - 'bill_name' => $transaction->bill_name, - 'category_id' => $category['category_id'], - 'category_name' => $category['category_name'], - 'budget_id' => $budget['budget_id'], - 'budget_name' => $budget['budget_name'], - 'notes' => $notes, - 'sepa_cc' => $this->repository->getMetaField($journal, 'sepa-cc'), - 'sepa_ct_op' => $this->repository->getMetaField($journal, 'sepa-ct-op'), - 'sepa_ct_id' => $this->repository->getMetaField($journal, 'sepa-ct-ud'), - 'sepa_db' => $this->repository->getMetaField($journal, 'sepa-db'), - 'sepa_country' => $this->repository->getMetaField($journal, 'sepa-country'), - 'sepa_ep' => $this->repository->getMetaField($journal, 'sepa-ep'), - 'sepa_ci' => $this->repository->getMetaField($journal, 'sepa-ci'), - 'sepa_batch_id' => $this->repository->getMetaField($journal, 'sepa-batch-id'), - 'interest_date' => $this->repository->getMetaDateString($journal, 'interest_date'), - 'book_date' => $this->repository->getMetaDateString($journal, 'book_date'), - 'process_date' => $this->repository->getMetaDateString($journal, 'process_date'), - 'due_date' => $this->repository->getMetaDateString($journal, 'due_date'), - 'payment_date' => $this->repository->getMetaDateString($journal, 'payment_date'), - 'invoice_date' => $this->repository->getMetaDateString($journal, 'invoice_date'), - 'internal_reference' => $this->repository->getMetaField($journal, 'internal_reference'), - 'bunq_payment_id' => $this->repository->getMetaField($journal, 'bunq_payment_id'), - 'importHashV2' => $this->repository->getMetaField($journal, 'importHashV2'), - 'recurrence_id' => (int)$this->repository->getMetaField($journal, 'recurrence_id'), - 'external_id' => $this->repository->getMetaField($journal, 'external_id'), - 'original_source' => $this->repository->getMetaField($journal, 'original-source'), - 'tags' => '' === $tags ? null : $tags, - 'links' => [ - [ - 'rel' => 'self', - 'uri' => '/transactions/' . $transaction->id, - ], - ], - ]; - - // expand foreign amount: - if (null !== $transaction->transaction_foreign_amount) { - $data['foreign_amount'] = round($transaction->transaction_foreign_amount, (int)$transaction->foreign_currency_dp); - } - - // switch on type for consistency - switch ($transaction->transaction_type_type) { - case TransactionType::WITHDRAWAL: - Log::debug(sprintf('%d is a withdrawal', $transaction->journal_id)); - $data['source_id'] = $transaction->account_id; - $data['source_name'] = $transaction->account_name; - $data['source_iban'] = $transaction->account_iban; - $data['source_type'] = $transaction->account_type; - $data['destination_id'] = $transaction->opposing_account_id; - $data['destination_name'] = $transaction->opposing_account_name; - $data['destination_iban'] = $transaction->opposing_account_iban; - $data['destination_type'] = $transaction->opposing_account_type; - Log::debug(sprintf('source_id / account_id is %d', $transaction->account_id)); - Log::debug(sprintf('source_name / account_name is "%s"', $transaction->account_name)); - break; - case TransactionType::DEPOSIT: - case TransactionType::TRANSFER: - case TransactionType::OPENING_BALANCE: - case TransactionType::RECONCILIATION: - $data['source_id'] = $transaction->opposing_account_id; - $data['source_name'] = $transaction->opposing_account_name; - $data['source_iban'] = $transaction->opposing_account_iban; - $data['source_type'] = $transaction->opposing_account_type; - $data['destination_id'] = $transaction->account_id; - $data['destination_name'] = $transaction->account_name; - $data['destination_iban'] = $transaction->account_iban; - $data['destination_type'] = $transaction->account_type; - break; - default: - // @codeCoverageIgnoreStart - throw new FireflyException( - sprintf('Transaction transformer cannot handle transactions of type "%s"!', $transaction->transaction_type_type) - ); - // @codeCoverageIgnoreEnd - - } - - // expand description. - if ('' !== (string)$transaction->transaction_description) { - $data['description'] = $transaction->transaction_description . ' (' . $transaction->description . ')'; - } - - return $data; - } - - /** - * @param Transaction $transaction - * - * @return array - */ - private function getBudget(Transaction $transaction): array - { - if ($transaction->transaction_type_type !== TransactionType::WITHDRAWAL) { - return [ - 'budget_id' => null, - 'budget_name' => null, - ]; - } - - return [ - 'budget_id' => $transaction->transaction_budget_id ?? $transaction->transaction_journal_budget_id, - 'budget_name' => $transaction->transaction_budget_name ?? $transaction->transaction_journal_budget_name, - ]; - } - - /** - * @param Transaction $transaction - * - * @return array - */ - private function getCategory(Transaction $transaction): array - { - return [ - 'category_id' => $transaction->transaction_category_id ?? $transaction->transaction_journal_category_id, - 'category_name' => $transaction->transaction_category_name ?? $transaction->transaction_journal_category_name, - ]; - } -} diff --git a/app/Transformers/UserTransformer.php b/app/Transformers/UserTransformer.php index 78487e2265..bd6b797ef6 100644 --- a/app/Transformers/UserTransformer.php +++ b/app/Transformers/UserTransformer.php @@ -45,7 +45,7 @@ class UserTransformer extends AbstractTransformer { $this->repository = app(UserRepositoryInterface::class); if ('testing' === config('app.env')) { - Log::warning(sprintf('%s should not be instantiated in the TEST environment!', \get_class($this))); + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); } } diff --git a/app/User.php b/app/User.php index 4d04b51020..330dc20a7f 100644 --- a/app/User.php +++ b/app/User.php @@ -32,7 +32,6 @@ use FireflyIII\Models\Bill; use FireflyIII\Models\Budget; use FireflyIII\Models\Category; use FireflyIII\Models\CurrencyExchangeRate; -use FireflyIII\Models\ExportJob; use FireflyIII\Models\ImportJob; use FireflyIII\Models\PiggyBank; use FireflyIII\Models\Preference; @@ -63,9 +62,47 @@ use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; * @property bool $has2FA used in admin user controller. * @property array $prefs used in admin user controller. * @property string password + * @property string $mfa_secret * @property Collection roles * @property string blocked_code * @property bool blocked + * @property \Illuminate\Support\Carbon|null $created_at + * @property \Illuminate\Support\Carbon|null $updated_at + * @property string|null $remember_token + * @property string|null $reset + * @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\Account[] $accounts + * @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\Attachment[] $attachments + * @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\AvailableBudget[] $availableBudgets + * @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\Bill[] $bills + * @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\Budget[] $budgets + * @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\Category[] $categories + * @property-read \Illuminate\Database\Eloquent\Collection|\Laravel\Passport\Client[] $clients + * @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\CurrencyExchangeRate[] $currencyExchangeRates + * @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\ImportJob[] $importJobs + * @property-read \Illuminate\Notifications\DatabaseNotificationCollection|\Illuminate\Notifications\DatabaseNotification[] $notifications + * @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\PiggyBank[] $piggyBanks + * @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\Preference[] $preferences + * @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\Recurrence[] $recurrences + * @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\RuleGroup[] $ruleGroups + * @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\Rule[] $rules + * @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\Tag[] $tags + * @property-read \Illuminate\Database\Eloquent\Collection|\Laravel\Passport\Token[] $tokens + * @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\TransactionGroup[] $transactionGroups + * @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\TransactionJournal[] $transactionJournals + * @property-read \Illuminate\Database\Eloquent\Collection|\FireflyIII\Models\Transaction[] $transactions + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\User newModelQuery() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\User newQuery() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\User query() + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\User whereBlocked($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\User whereBlockedCode($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\User whereCreatedAt($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\User whereEmail($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\User whereId($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\User wherePassword($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\User whereRememberToken($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\User whereReset($value) + * @method static \Illuminate\Database\Eloquent\Builder|\FireflyIII\User whereUpdatedAt($value) + * @mixin \Eloquent */ class User extends Authenticatable { @@ -196,17 +233,6 @@ class User extends Authenticatable return $this->hasMany(CurrencyExchangeRate::class); } - /** - * @codeCoverageIgnore - * Link to export jobs - * - * @return HasMany - */ - public function exportJobs(): HasMany - { - return $this->hasMany(ExportJob::class); - } - /** * @codeCoverageIgnore * Generates access token. diff --git a/app/Validation/AccountValidator.php b/app/Validation/AccountValidator.php new file mode 100644 index 0000000000..b5f3d42e4c --- /dev/null +++ b/app/Validation/AccountValidator.php @@ -0,0 +1,633 @@ +. + */ + +declare(strict_types=1); + +namespace FireflyIII\Validation; + +use FireflyIII\Models\Account; +use FireflyIII\Models\AccountType; +use FireflyIII\Models\TransactionType; +use FireflyIII\Repositories\Account\AccountRepositoryInterface; +use FireflyIII\User; +use Log; + +/** + * Class AccountValidator + */ +class AccountValidator +{ + /** @var bool */ + public $createMode; + /** @var string */ + public $destError; + /** @var Account */ + public $destination; + /** @var Account */ + public $source; + /** @var string */ + public $sourceError; + /** @var AccountRepositoryInterface */ + private $accountRepository; + /** @var array */ + private $combinations; + /** @var string */ + private $transactionType; + /** @var User */ + private $user; + + /** + * AccountValidator constructor. + */ + public function __construct() + { + $this->createMode = false; + $this->destError = 'No error yet.'; + $this->sourceError = 'No error yet.'; + $this->combinations = config('firefly.source_dests'); + /** @var AccountRepositoryInterface accountRepository */ + $this->accountRepository = app(AccountRepositoryInterface::class); + if ('testing' === config('app.env')) { + Log::warning(sprintf('%s should not be instantiated in the TEST environment!', get_class($this))); + } + } + + /** + * @param string $transactionType + */ + public function setTransactionType(string $transactionType): void + { + Log::debug(sprintf('Transaction type for validator is now %s', ucfirst($transactionType))); + $this->transactionType = ucfirst($transactionType); + } + + /** + * @param User $user + */ + public function setUser(User $user): void + { + $this->user = $user; + $this->accountRepository->setUser($user); + } + + /** + * @param int|null $destinationId + * @param $destinationName + * + * @return bool + */ + public function validateDestination(?int $destinationId, $destinationName): bool + { + + Log::debug(sprintf('Now in AccountValidator::validateDestination(%d, "%s")', $destinationId, $destinationName)); + if (null === $this->source) { + Log::error('Source is NULL, always FALSE.'); + $this->destError = 'No source account validation has taken place yet. Please do this first or overrule the object.'; + + return false; + } + switch ($this->transactionType) { + default: + $this->destError = sprintf('AccountValidator::validateDestination cannot handle "%s", so it will always return false.', $this->transactionType); + Log::error(sprintf('AccountValidator::validateDestination cannot handle "%s", so it will always return false.', $this->transactionType)); + + $result = false; + break; + + case TransactionType::WITHDRAWAL: + $result = $this->validateWithdrawalDestination($destinationId, $destinationName); + break; + case TransactionType::DEPOSIT: + $result = $this->validateDepositDestination($destinationId, $destinationName); + break; + case TransactionType::TRANSFER: + $result = $this->validateTransferDestination($destinationId, $destinationName); + break; + case TransactionType::OPENING_BALANCE: + $result = $this->validateOBDestination($destinationId, $destinationName); + break; + case TransactionType::RECONCILIATION: + $result = $this->validateReconciliationDestination($destinationId); + break; + } + + return $result; + } + + /** + * @param int|null $accountId + * @param string|null $accountName + * + * @return bool + */ + public function validateSource(?int $accountId, ?string $accountName): bool + { + Log::debug(sprintf('Now in AccountValidator::validateSource(%d, "%s")', $accountId, $accountName)); + switch ($this->transactionType) { + default: + $result = false; + $this->sourceError = sprintf('Cannot handle type "%s" :(', $this->transactionType); + Log::error(sprintf('AccountValidator::validateSource cannot handle "%s", so it will always return false.', $this->transactionType)); + break; + case TransactionType::WITHDRAWAL: + $result = $this->validateWithdrawalSource($accountId, $accountName); + break; + case TransactionType::DEPOSIT: + $result = $this->validateDepositSource($accountId, $accountName); + break; + case TransactionType::TRANSFER: + $result = $this->validateTransferSource($accountId, $accountName); + break; + case TransactionType::OPENING_BALANCE: + $result = $this->validateOBSource($accountId, $accountName); + break; + case TransactionType::RECONCILIATION: + $result = $this->validateReconciliationSource($accountId); + break; + } + + return $result; + } + + /** + * @param string $accountType + * + * @return bool + */ + private function canCreateType(string $accountType): bool + { + $result = false; + switch ($accountType) { + default: + Log::error(sprintf('AccountValidator::validateSource cannot handle "%s".', $this->transactionType)); + break; + case AccountType::ASSET: + case AccountType::LOAN: + case AccountType::MORTGAGE: + case AccountType::DEBT: + $result = false; + break; + case AccountType::EXPENSE: + case AccountType::REVENUE: + case AccountType::INITIAL_BALANCE: + $result = true; + break; + } + + return $result; + } + + /** + * @param array $accountTypes + * + * @return bool + */ + private function canCreateTypes(array $accountTypes): bool + { + Log::debug('Can we create any of these types?', $accountTypes); + /** @var string $accountType */ + foreach ($accountTypes as $accountType) { + if ($this->canCreateType($accountType)) { + Log::debug(sprintf('YES, we can create a %s', $accountType)); + + return true; + } + } + Log::debug('NO, we cant create any of those.'); + + return false; + } + + /** + * @param array $validTypes + * @param int|null $accountId + * @param string|null $accountName + * + * @return Account|null + */ + private function findExistingAccount(array $validTypes, int $accountId, string $accountName): ?Account + { + $result = null; + + // find by ID + if ($accountId > 0) { + $first = $this->accountRepository->findNull($accountId); + if ((null !== $first) && in_array($first->accountType->type, $validTypes, true)) { + $result = $first; + } + } + + // find by name: + if (null === $result && '' !== $accountName) { + $second = $this->accountRepository->findByName($accountName, $validTypes); + if (null !== $second) { + $result = $second; + } + } + + return $result; + } + + /** + * @param int|null $accountId + * @param $accountName + * + * @return bool + */ + private function validateDepositDestination(?int $accountId, $accountName): bool + { + $result = null; + Log::debug(sprintf('Now in validateDepositDestination(%d, "%s")', $accountId, $accountName)); + + // source can be any of the following types. + $validTypes = $this->combinations[$this->transactionType][$this->source->accountType->type] ?? []; + if (null === $accountId && null === $accountName && false === $this->canCreateTypes($validTypes)) { + // if both values are NULL we return false, + // because the destination of a deposit can't be created. + $this->destError = (string)trans('validation.deposit_dest_need_data'); + Log::error('Both values are NULL, cant create deposit destination.'); + $result = false; + } + // if the account can be created anyway we don't need to search. + if (null === $result && true === $this->canCreateTypes($validTypes)) { + Log::debug('Can create some of these types, so return true.'); + $result = true; + } + + if (null === $result) { + // otherwise try to find the account: + $search = $this->findExistingAccount($validTypes, (int)$accountId, (string)$accountName); + if (null === $search) { + Log::debug('findExistingAccount() returned NULL, so the result is false.'); + $this->destError = (string)trans('validation.deposit_dest_bad_data', ['id' => $accountId, 'name' => $accountName]); + $result = false; + } + if (null !== $search) { + Log::debug(sprintf('findExistingAccount() returned #%d ("%s"), so the result is true.', $search->id, $search->name)); + $this->destination = $search; + $result = true; + } + } + $result = $result ?? false; + Log::debug(sprintf('validateDepositDestination(%d, "%s") will return %s', $accountId, $accountName, var_export($result, true))); + + return $result; + } + + /** + * @param int|null $accountId + * @param $accountName + * + * @return bool + */ + private function validateOBDestination(?int $accountId, $accountName): bool + { + $result = null; + Log::debug(sprintf('Now in validateOBDestination(%d, "%s")', $accountId, $accountName)); + + // source can be any of the following types. + $validTypes = $this->combinations[$this->transactionType][$this->source->accountType->type] ?? []; + if (null === $accountId && null === $accountName && false === $this->canCreateTypes($validTypes)) { + // if both values are NULL we return false, + // because the destination of a deposit can't be created. + $this->destError = (string)trans('validation.ob_dest_need_data'); + Log::error('Both values are NULL, cant create OB destination.'); + $result = false; + } + // if the account can be created anyway we don't need to search. + if (null === $result && true === $this->canCreateTypes($validTypes)) { + Log::debug('Can create some of these types, so return true.'); + $result = true; + } + + if (null === $result) { + // otherwise try to find the account: + $search = $this->findExistingAccount($validTypes, (int)$accountId, (string)$accountName); + if (null === $search) { + Log::debug('findExistingAccount() returned NULL, so the result is false.', $validTypes); + $this->destError = (string)trans('validation.ob_dest_bad_data', ['id' => $accountId, 'name' => $accountName]); + $result = false; + } + if (null !== $search) { + Log::debug(sprintf('findExistingAccount() returned #%d ("%s"), so the result is true.', $search->id, $search->name)); + $this->destination = $search; + $result = true; + } + } + $result = $result ?? false; + Log::debug(sprintf('validateOBDestination(%d, "%s") will return %s', $accountId, $accountName, var_export($result, true))); + + return $result; + } + + /** + * @param int|null $accountId + * @param string|null $accountName + * + * @return bool + */ + private function validateDepositSource(?int $accountId, ?string $accountName): bool + { + Log::debug(sprintf('Now in validateDepositSource(%d, "%s")', $accountId, $accountName)); + $result = null; + // source can be any of the following types. + $validTypes = array_keys($this->combinations[$this->transactionType]); + if (null === $accountId && null === $accountName && false === $this->canCreateTypes($validTypes)) { + // if both values are NULL return false, + // because the source of a deposit can't be created. + // (this never happens). + $this->sourceError = (string)trans('validation.deposit_source_need_data'); + $result = false; + } + + // if the user submits an ID only but that ID is not of the correct type, + // return false. + if (null !== $accountId && null === $accountName) { + $search = $this->accountRepository->findNull($accountId); + if (null !== $search && !in_array($search->accountType->type, $validTypes, true)) { + Log::debug(sprintf('User submitted only an ID (#%d), which is a "%s", so this is not a valid source.', $accountId, $search->accountType->type)); + $result = false; + } + } + + // if the account can be created anyway we don't need to search. + if (null === $result && true === $this->canCreateTypes($validTypes)) { + $result = true; + + // set the source to be a (dummy) revenue account. + $account = new Account; + $accountType = AccountType::whereType(AccountType::REVENUE)->first(); + $account->accountType = $accountType; + $this->source = $account; + } + $result = $result ?? false; + + // don't expect to end up here: + return $result; + } + + /** + * Source of an opening balance can either be an asset account + * or an "initial balance account". The latter can be created. + * @param int|null $accountId + * @param string|null $accountName + * + * @return bool + */ + private function validateOBSource(?int $accountId, ?string $accountName): bool + { + Log::debug(sprintf('Now in validateOBSource(%d, "%s")', $accountId, $accountName)); + Log::debug(sprintf('The account name is null: %s', var_export(null === $accountName, true))); + $result = null; + // source can be any of the following types. + $validTypes = array_keys($this->combinations[$this->transactionType]); + + if (null === $accountId && null === $accountName && false === $this->canCreateTypes($validTypes)) { + // if both values are NULL return false, + // because the source of a deposit can't be created. + // (this never happens). + $this->sourceError = (string)trans('validation.ob_source_need_data'); + $result = false; + } + + // if the user submits an ID only but that ID is not of the correct type, + // return false. + if (null !== $accountId && null === $accountName) { + Log::debug('Source ID is not null, but name is null.'); + $search = $this->accountRepository->findNull($accountId); + + // the source resulted in an account, but it's not of a valid type. + if (null !== $search && !in_array($search->accountType->type, $validTypes, true)) { + $message = sprintf('User submitted only an ID (#%d), which is a "%s", so this is not a valid source.', $accountId, $search->accountType->type); + Log::debug($message); + $this->sourceError = $message; + $result = false; + } + // the source resulted in an account, AND it's of a valid type. + if (null !== $search && in_array($search->accountType->type, $validTypes, true)) { + Log::debug(sprintf('Found account of correct type: #%d, "%s"', $search->id, $search->name)); + $this->source = $search; + $result = true; + } + } + + // if the account can be created anyway we don't need to search. + if (null === $result && true === $this->canCreateTypes($validTypes)) { + Log::debug('Result is still null.'); + $result = true; + + // set the source to be a (dummy) initial balance account. + $account = new Account; + $accountType = AccountType::whereType(AccountType::INITIAL_BALANCE)->first(); + $account->accountType = $accountType; + $this->source = $account; + } + $result = $result ?? false; + + return $result; + } + + /** + * @param int|null $accountId + * @param $accountName + * + * @return bool + */ + private function validateTransferDestination(?int $accountId, $accountName): bool + { + Log::debug(sprintf('Now in validateTransferDestination(%d, "%s")', $accountId, $accountName)); + // source can be any of the following types. + $validTypes = $this->combinations[$this->transactionType][$this->source->accountType->type] ?? []; + if (null === $accountId && null === $accountName && false === $this->canCreateTypes($validTypes)) { + // if both values are NULL we return false, + // because the destination of a transfer can't be created. + $this->destError = (string)trans('validation.transfer_dest_need_data'); + Log::error('Both values are NULL, cant create transfer destination.'); + + return false; + } + + // otherwise try to find the account: + $search = $this->findExistingAccount($validTypes, (int)$accountId, (string)$accountName); + if (null === $search) { + $this->destError = (string)trans('validation.transfer_dest_bad_data', ['id' => $accountId, 'name' => $accountName]); + + return false; + } + $this->destination = $search; + // must not be the same as the source account + return !(null !== $this->source && $this->source->id === $this->destination->id); + } + + /** + * @param int|null $accountId + * @param string|null $accountName + * + * @return bool + */ + private function validateTransferSource(?int $accountId, ?string $accountName): bool + { + Log::debug(sprintf('Now in validateTransferSource(%d, "%s")', $accountId, $accountName)); + // source can be any of the following types. + $validTypes = array_keys($this->combinations[$this->transactionType]); + if (null === $accountId && null === $accountName && false === $this->canCreateTypes($validTypes)) { + // if both values are NULL we return false, + // because the source of a withdrawal can't be created. + $this->sourceError = (string)trans('validation.transfer_source_need_data'); + + return false; + } + + // otherwise try to find the account: + $search = $this->findExistingAccount($validTypes, (int)$accountId, (string)$accountName); + if (null === $search) { + $this->sourceError = (string)trans('validation.transfer_source_bad_data', ['id' => $accountId, 'name' => $accountName]); + + return false; + } + $this->source = $search; + + return true; + } + + /** + * @param int|null $accountId + * @param string|null $accountName + * + * @return bool + */ + private function validateWithdrawalDestination(?int $accountId, ?string $accountName): bool + { + Log::debug(sprintf('Now in validateWithdrawalDestination(%d, "%s")', $accountId, $accountName)); + // source can be any of the following types. + $validTypes = $this->combinations[$this->transactionType][$this->source->accountType->type] ?? []; + if (null === $accountId && null === $accountName && false === $this->canCreateTypes($validTypes)) { + // if both values are NULL return false, + // because the destination of a withdrawal can never be created automatically. + $this->destError = (string)trans('validation.withdrawal_dest_need_data'); + + return false; + } + + // if there's an ID it must be of the "validTypes". + if (null !== $accountId && 0 !== $accountId) { + $found = $this->accountRepository->findNull($accountId); + if (null !== $found) { + $type = $found->accountType->type; + if (in_array($type, $validTypes)) { + return true; + } + $this->destError = (string)trans('validation.withdrawal_dest_bad_data', ['id' => $accountId, 'name' => $accountName]); + + return false; + } + } + + // if the account can be created anyway don't need to search. + if (true === $this->canCreateTypes($validTypes)) { + + return true; + } + + // don't expect to end up here: + + return false; + } + + /** + * @param int|null $accountId + * @param string|null $accountName + * + * @return bool + */ + private function validateWithdrawalSource(?int $accountId, ?string $accountName): bool + { + Log::debug(sprintf('Now in validateWithdrawalSource(%d, "%s")', $accountId, $accountName)); + // source can be any of the following types. + $validTypes = array_keys($this->combinations[$this->transactionType]); + if (null === $accountId && null === $accountName && false === $this->canCreateTypes($validTypes)) { + // if both values are NULL we return false, + // because the source of a withdrawal can't be created. + $this->sourceError = (string)trans('validation.withdrawal_source_need_data'); + + return false; + } + + // otherwise try to find the account: + $search = $this->findExistingAccount($validTypes, (int)$accountId, (string)$accountName); + if (null === $search) { + $this->sourceError = (string)trans('validation.withdrawal_source_bad_data', ['id' => $accountId, 'name' => $accountName]); + + return false; + } + $this->source = $search; + + return true; + } + + /** + * @param int|null $accountId + * @return bool + */ + private function validateReconciliationSource(?int $accountId): bool + { + if (null === $accountId) { + return false; + } + $result = $this->accountRepository->findNull($accountId); + $types = [AccountType::ASSET, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE, AccountType::RECONCILIATION]; + if (null === $result) { + return false; + } + if (in_array($result->accountType->type, $types, true)) { + $this->source = $result; + + return true; + } + + return false; + } + + /** + * @param int|null $accountId + * @return bool + */ + private function validateReconciliationDestination(?int $accountId): bool + { + if (null === $accountId) { + return false; + } + $result = $this->accountRepository->findNull($accountId); + $types = [AccountType::ASSET, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE, AccountType::RECONCILIATION]; + if (null === $result) { + return false; + } + if (in_array($result->accountType->type, $types, true)) { + $this->destination = $result; + + return true; + } + + return false; + } + + +} \ No newline at end of file diff --git a/app/Validation/FireflyValidator.php b/app/Validation/FireflyValidator.php index 49a6d95749..0da6a0117d 100644 --- a/app/Validation/FireflyValidator.php +++ b/app/Validation/FireflyValidator.php @@ -55,7 +55,7 @@ class FireflyValidator extends Validator */ public function validate2faCode($attribute, $value): bool { - if (!\is_string($value) || null === $value || 6 !== \strlen($value)) { + if (!\is_string($value) || null === $value || 6 !== strlen($value)) { return false; } @@ -117,7 +117,7 @@ class FireflyValidator extends Validator */ public function validateIban($attribute, $value): bool { - if (!\is_string($value) || null === $value || \strlen($value) < 6) { + if (!\is_string($value) || null === $value || strlen($value) < 6) { return false; } // strip spaces @@ -268,6 +268,7 @@ class FireflyValidator extends Validator $repository = app(BudgetRepositoryInterface::class); $budgets = $repository->getBudgets(); // count budgets, should have at least one + // TODO no longer need to loop like this $count = $budgets->filter( function (Budget $budget) use ($value) { return $budget->name === $value; @@ -327,7 +328,7 @@ class FireflyValidator extends Validator // these trigger types need a numerical check: $numerical = ['amount_less', 'amount_more', 'amount_exactly']; - if (\in_array($triggerType, $numerical, true)) { + if (in_array($triggerType, $numerical, true)) { return is_numeric($value); } @@ -335,13 +336,13 @@ class FireflyValidator extends Validator $length = ['from_account_starts', 'from_account_ends', 'from_account_is', 'from_account_contains', 'to_account_starts', 'to_account_ends', 'to_account_is', 'to_account_contains', 'description_starts', 'description_ends', 'description_contains', 'description_is', 'category_is', 'budget_is', 'tag_is', 'currency_is', 'notes_contain', 'notes_start', 'notes_end', 'notes_are',]; - if (\in_array($triggerType, $length, true)) { + if (in_array($triggerType, $length, true)) { return '' !== $value; } // check transaction type. if ('transaction_type' === $triggerType) { - $count = TransactionType::where('type', strtolower($value))->count(); + $count = TransactionType::where('type', ucfirst($value))->count(); return 1 === $count; } @@ -391,10 +392,10 @@ class FireflyValidator extends Validator public function validateUniqueAccountForUser($attribute, $value, $parameters): bool { // because a user does not have to be logged in (tests and what-not). + if (!auth()->check()) { return $this->validateAccountAnonymously(); } - if (isset($this->data['what'])) { return $this->validateByAccountTypeString($value, $parameters, $this->data['what']); } @@ -409,7 +410,8 @@ class FireflyValidator extends Validator return $this->validateByAccountId($value); } - return false; + // without type, just try to validate the name. + return $this->validateByAccountName($value); } /** @@ -431,7 +433,7 @@ class FireflyValidator extends Validator $query = AccountMeta::leftJoin('accounts', 'accounts.id', '=', 'account_meta.account_id') ->whereNull('accounts.deleted_at') ->where('accounts.user_id', auth()->user()->id) - ->where('account_meta.name', 'accountNumber'); + ->where('account_meta.name', 'account_number'); if ($accountId > 0) { // exclude current account from check. @@ -538,6 +540,7 @@ class FireflyValidator extends Validator $value = $this->data['name']; $set = $user->accounts()->where('account_type_id', $type->id)->get(); + // TODO no longer need to loop like this /** @var Account $entry */ foreach ($set as $entry) { if ($entry->name === $value) { @@ -563,6 +566,7 @@ class FireflyValidator extends Validator /** @var Collection $set */ $set = auth()->user()->accounts()->where('account_type_id', $type->id)->where('id', '!=', $ignore)->get(); + // TODO no longer need to loop like this /** @var Account $entry */ foreach ($set as $entry) { if ($entry->name === $value) { @@ -586,8 +590,10 @@ class FireflyValidator extends Validator /** @var Collection $set */ $set = auth()->user()->accounts()->where('account_type_id', $type->id)->where('id', '!=', $ignore)->get(); + // TODO no longer need to loop like this /** @var Account $entry */ foreach ($set as $entry) { + // TODO no longer need to loop like this. if ($entry->name === $value) { return false; } @@ -598,7 +604,7 @@ class FireflyValidator extends Validator /** * @param string $value - * @param array $parameters + * @param array $parameters * @param string $type * * @return bool @@ -618,6 +624,7 @@ class FireflyValidator extends Validator $accountTypeIds = $accountTypes->pluck('id')->toArray(); /** @var Collection $set */ $set = auth()->user()->accounts()->whereIn('account_type_id', $accountTypeIds)->where('id', '!=', $ignore)->get(); + // TODO no longer need to loop like this /** @var Account $entry */ foreach ($set as $entry) { if ($entry->name === $value) { @@ -627,4 +634,13 @@ class FireflyValidator extends Validator return true; } + + /** + * @param string $value + * @return bool + */ + private function validateByAccountName(string $value): bool + { + return auth()->user()->accounts()->where('name', $value)->count() === 0; + } } diff --git a/app/Validation/RecurrenceValidation.php b/app/Validation/RecurrenceValidation.php index 91e4cc47de..88ae0c3e7c 100644 --- a/app/Validation/RecurrenceValidation.php +++ b/app/Validation/RecurrenceValidation.php @@ -47,7 +47,7 @@ trait RecurrenceValidation $data = $validator->getData(); $repetitions = $data['repetitions'] ?? []; // need at least one transaction - if (0 === \count($repetitions)) { + if (0 === count($repetitions)) { $validator->errors()->add('description', (string)trans('validation.at_least_one_repetition')); } } @@ -145,7 +145,7 @@ trait RecurrenceValidation protected function validateNdom(Validator $validator, int $index, string $moment): void { $parameters = explode(',', $moment); - if (2 !== \count($parameters)) { + if (2 !== count($parameters)) { $validator->errors()->add(sprintf('repetitions.%d.moment', $index), (string)trans('validation.valid_recurrence_rep_moment')); return; diff --git a/app/Validation/TransactionValidation.php b/app/Validation/TransactionValidation.php index 4f46b3fc1e..62dd43259a 100644 --- a/app/Validation/TransactionValidation.php +++ b/app/Validation/TransactionValidation.php @@ -23,11 +23,9 @@ declare(strict_types=1); namespace FireflyIII\Validation; -use FireflyIII\Models\Account; -use FireflyIII\Models\AccountType; use FireflyIII\Models\Transaction; -use FireflyIII\Repositories\Account\AccountRepositoryInterface; -use FireflyIII\User; +use FireflyIII\Models\TransactionGroup; +use FireflyIII\Models\TransactionJournal; use Illuminate\Validation\Validator; use Log; @@ -43,61 +41,42 @@ trait TransactionValidation */ public function validateAccountInformation(Validator $validator): void { - $data = $validator->getData(); - $transactions = $data['transactions'] ?? []; - $idField = 'description'; + //Log::debug('Now in validateAccountInformation()'); + $data = $validator->getData(); + $transactionType = $data['type'] ?? 'invalid'; - // get transaction type: - if (!isset($data['type'])) { - // the journal may exist in the request: - /** @var Transaction $transaction */ - $transaction = $this->route()->parameter('transaction'); - if (null !== $transaction) { - $transactionType = strtolower($transaction->transactionJournal->transactionType->type); - } - } + $transactions = $data['transactions'] ?? []; + /** @var AccountValidator $accountValidator */ + $accountValidator = app(AccountValidator::class); + + Log::debug(sprintf('Going to loop %d transaction(s)', count($transactions))); foreach ($transactions as $index => $transaction) { - $sourceId = isset($transaction['source_id']) ? (int)$transaction['source_id'] : null; - $sourceName = $transaction['source_name'] ?? null; - $destinationId = isset($transaction['destination_id']) ? (int)$transaction['destination_id'] : null; - $destinationName = $transaction['destination_name'] ?? null; - $sourceAccount = null; - $destinationAccount = null; - switch ($transactionType) { - case 'withdrawal': - $idField = 'transactions.' . $index . '.source_id'; - $nameField = 'transactions.' . $index . '.source_name'; - $sourceAccount = $this->assetAccountExists($validator, $sourceId, $sourceName, $idField, $nameField); - $idField = 'transactions.' . $index . '.destination_id'; - $destinationAccount = $this->opposingAccountExists($validator, AccountType::EXPENSE, $destinationId, $destinationName, $idField); - break; - case 'deposit': - $idField = 'transactions.' . $index . '.source_id'; - $sourceAccount = $this->opposingAccountExists($validator, AccountType::REVENUE, $sourceId, $sourceName, $idField); + $transactionType = $transaction['type'] ?? $transactionType; + $accountValidator->setTransactionType($transactionType); - $idField = 'transactions.' . $index . '.destination_id'; - $nameField = 'transactions.' . $index . '.destination_name'; - $destinationAccount = $this->assetAccountExists($validator, $destinationId, $destinationName, $idField, $nameField); - break; - case 'transfer': - $idField = 'transactions.' . $index . '.source_id'; - $nameField = 'transactions.' . $index . '.source_name'; - $sourceAccount = $this->assetAccountExists($validator, $sourceId, $sourceName, $idField, $nameField); + // validate source account. + $sourceId = isset($transaction['source_id']) ? (int)$transaction['source_id'] : null; + $sourceName = $transaction['source_name'] ?? null; + $validSource = $accountValidator->validateSource($sourceId, $sourceName); - $idField = 'transactions.' . $index . '.destination_id'; - $nameField = 'transactions.' . $index . '.destination_name'; - $destinationAccount = $this->assetAccountExists($validator, $destinationId, $destinationName, $idField, $nameField); - break; - default: - $validator->errors()->add($idField, (string)trans('validation.invalid_account_info')); - - return; + // do something with result: + if (false === $validSource) { + $validator->errors()->add(sprintf('transactions.%d.source_id', $index), $accountValidator->sourceError); + $validator->errors()->add(sprintf('transactions.%d.source_name', $index), $accountValidator->sourceError); + return; } - // add some errors in case of same account submitted: - if (null !== $sourceAccount && null !== $destinationAccount && $sourceAccount->id === $destinationAccount->id) { - $validator->errors()->add($idField, (string)trans('validation.source_equals_destination')); + // validate destination account + $destinationId = isset($transaction['destination_id']) ? (int)$transaction['destination_id'] : null; + $destinationName = $transaction['destination_name'] ?? null; + $validDestination = $accountValidator->validateDestination($destinationId, $destinationName); + // do something with result: + if (false === $validDestination) { + $validator->errors()->add(sprintf('transactions.%d.destination_id', $index), $accountValidator->destError); + $validator->errors()->add(sprintf('transactions.%d.destination_name', $index), $accountValidator->destError); + + return; } } } @@ -110,19 +89,20 @@ trait TransactionValidation */ public function validateDescriptions(Validator $validator): void { - $data = $validator->getData(); - $transactions = $data['transactions'] ?? []; - $journalDescription = (string)($data['description'] ?? null); - $validDescriptions = 0; + $data = $validator->getData(); + $transactions = $data['transactions'] ?? []; + $validDescriptions = 0; foreach ($transactions as $index => $transaction) { if ('' !== (string)($transaction['description'] ?? null)) { $validDescriptions++; } } - // no valid descriptions and empty journal description? error. - if (0 === $validDescriptions && '' === $journalDescription) { - $validator->errors()->add('description', (string)trans('validation.filled', ['attribute' => (string)trans('validation.attributes.description')])); + // no valid descriptions? + if (0 === $validDescriptions) { + $validator->errors()->add( + 'transactions.0.description', (string)trans('validation.filled', ['attribute' => (string)trans('validation.attributes.description')]) + ); } } @@ -136,34 +116,33 @@ trait TransactionValidation $data = $validator->getData(); $transactions = $data['transactions'] ?? []; foreach ($transactions as $index => $transaction) { - // must have currency info. - if (isset($transaction['foreign_amount']) - && !(isset($transaction['foreign_currency_id']) - || isset($transaction['foreign_currency_code']))) { + // if foreign amount is present, then the currency must be as well. + if (isset($transaction['foreign_amount']) && !(isset($transaction['foreign_currency_id']) || isset($transaction['foreign_currency_code']))) { $validator->errors()->add( 'transactions.' . $index . '.foreign_amount', (string)trans('validation.require_currency_info') ); } + // if the currency is present, then the amount must be present as well. + if ((isset($transaction['foreign_currency_id']) || isset($transaction['foreign_currency_code'])) && !isset($transaction['foreign_amount'])) { + $validator->errors()->add( + 'transactions.' . $index . '.foreign_amount', + (string)trans('validation.require_currency_amount') + ); + } } } /** - * Adds an error to the validator when any transaction descriptions are equal to the journal description. - * * @param Validator $validator */ - public function validateJournalDescription(Validator $validator): void + public function validateGroupDescription(Validator $validator): void { - $data = $validator->getData(); - $transactions = $data['transactions'] ?? []; - $journalDescription = (string)($data['description'] ?? null); - foreach ($transactions as $index => $transaction) { - $description = (string)($transaction['description'] ?? null); - // description cannot be equal to journal description. - if ($description === $journalDescription) { - $validator->errors()->add('transactions.' . $index . '.description', (string)trans('validation.equal_description')); - } + $data = $validator->getData(); + $transactions = $data['transactions'] ?? []; + $groupTitle = $data['group_title'] ?? ''; + if ('' === $groupTitle && count($transactions) > 1) { + $validator->errors()->add('group_title', (string)trans('validation.group_title_mandatory')); } } @@ -177,60 +156,155 @@ trait TransactionValidation $data = $validator->getData(); $transactions = $data['transactions'] ?? []; // need at least one transaction - if (0 === \count($transactions)) { + if (0 === count($transactions)) { + $validator->errors()->add('transactions.0.description', (string)trans('validation.at_least_one_transaction')); + } + } + + /** + * Adds an error to the validator when there are no transactions in the array of data. + * + * @param Validator $validator + */ + public function validateOneRecurrenceTransaction(Validator $validator): void + { + $data = $validator->getData(); + $transactions = $data['transactions'] ?? []; + // need at least one transaction + if (0 === count($transactions)) { $validator->errors()->add('description', (string)trans('validation.at_least_one_transaction')); } } /** - * Make sure that all the splits accounts are valid in combination with each other. + * All types of splits must be equal. * * @param Validator $validator */ - public function validateSplitAccounts(Validator $validator): void + public function validateTransactionTypes(Validator $validator): void { - $data = $validator->getData(); - $count = isset($data['transactions']) ? \count($data['transactions']) : 0; - if ($count < 2) { + $data = $validator->getData(); + $transactions = $data['transactions'] ?? []; + $types = []; + foreach ($transactions as $index => $transaction) { + $types[] = $transaction['type'] ?? 'invalid'; + } + $unique = array_unique($types); + if (count($unique) > 1) { + $validator->errors()->add('transactions.0.type', (string)trans('validation.transaction_types_equal')); + return; } - // this is pretty much impossible: - // @codeCoverageIgnoreStart - if (!isset($data['type'])) { - // the journal may exist in the request: - /** @var Transaction $transaction */ - $transaction = $this->route()->parameter('transaction'); - if (null === $transaction) { - return; + $first = $unique[0] ?? 'invalid'; + if ('invalid' === $first) { + $validator->errors()->add('transactions.0.type', (string)trans('validation.invalid_transaction_type')); + + } + } + + /** + * All types of splits must be equal. + * + * @param Validator $validator + */ + public function validateTransactionTypesForUpdate(Validator $validator): void + { + $data = $validator->getData(); + $transactions = $data['transactions'] ?? []; + $types = []; + foreach ($transactions as $index => $transaction) { + $originalType = $this->getOriginalType((int)($transaction['transaction_journal_id'] ?? 0)); + // if type is not set, fall back to the type of the journal, if one is given. + + + $types[] = $transaction['type'] ?? $originalType; + } + $unique = array_unique($types); + if (count($unique) > 1) { + $validator->errors()->add('transactions.0.type', (string)trans('validation.transaction_types_equal')); + + return; + } + } + + /** + * Validates the given account information. Switches on given transaction type. + * + * @param Validator $validator + */ + public function validateAccountInformationUpdate(Validator $validator): void + { + $data = $validator->getData(); + $transactions = $data['transactions'] ?? []; + + /** @var AccountValidator $accountValidator */ + $accountValidator = app(AccountValidator::class); + + foreach ($transactions as $index => $transaction) { + $originalType = $this->getOriginalType($transaction['transaction_journal_id'] ?? 0); + $originalData = $this->getOriginalData($transaction['transaction_journal_id'] ?? 0); + $transactionType = $transaction['type'] ?? $originalType; + $accountValidator->setTransactionType($transactionType); + + // validate source account. + $sourceId = isset($transaction['source_id']) ? (int)$transaction['source_id'] : $originalData['source_id']; + $sourceName = $transaction['source_name'] ?? $originalData['source_name']; + $validSource = $accountValidator->validateSource($sourceId, $sourceName); + + // do something with result: + if (false === $validSource) { + $validator->errors()->add(sprintf('transactions.%d.source_id', $index), $accountValidator->sourceError); + $validator->errors()->add(sprintf('transactions.%d.source_name', $index), $accountValidator->sourceError); + + continue; } - $data['type'] = strtolower($transaction->transactionJournal->transactionType->type); - } - // @codeCoverageIgnoreEnd + // validate destination account + $destinationId = isset($transaction['destination_id']) ? (int)$transaction['destination_id'] : $originalData['destination_id']; + $destinationName = $transaction['destination_name'] ?? $originalData['destination_name']; + $validDestination = $accountValidator->validateDestination($destinationId, $destinationName); + // do something with result: + if (false === $validDestination) { + $validator->errors()->add(sprintf('transactions.%d.destination_id', $index), $accountValidator->destError); + $validator->errors()->add(sprintf('transactions.%d.destination_name', $index), $accountValidator->destError); - // collect all source ID's and destination ID's, if present: - $sources = []; - $destinations = []; - - foreach ($data['transactions'] as $transaction) { - $sources[] = isset($transaction['source_id']) ? (int)$transaction['source_id'] : 0; - $destinations[] = isset($transaction['destination_id']) ? (int)$transaction['destination_id'] : 0; + continue; + } } - $destinations = array_unique($destinations); - $sources = array_unique($sources); - // switch on type: - switch ($data['type']) { + } + + /** + * @param Validator $validator + */ + private function validateEqualAccounts(Validator $validator): void + { + $data = $validator->getData(); + $transactions = $data['transactions'] ?? []; + // needs to be split + if (count($transactions) < 2) { + return; + } + $type = $transactions[0]['type'] ?? 'withdrawal'; + $sources = []; + $dests = []; + foreach ($transactions as $transaction) { + $sources[] = sprintf('%d-%s', $transaction['source_id'] ?? 0, $transaction['source_name'] ?? ''); + $dests[] = sprintf('%d-%s', $transaction['destination_id'] ?? 0, $transaction['destination_name'] ?? ''); + } + $sources = array_unique($sources); + $dests = array_unique($dests); + switch ($type) { case 'withdrawal': - if (\count($sources) > 1) { + if (count($sources) > 1) { $validator->errors()->add('transactions.0.source_id', (string)trans('validation.all_accounts_equal')); } break; case 'deposit': - if (\count($destinations) > 1) { + if (count($dests) > 1) { $validator->errors()->add('transactions.0.destination_id', (string)trans('validation.all_accounts_equal')); } break; - case 'transfer': - if (\count($sources) > 1 || \count($destinations) > 1) { + case'transfer': + if (count($sources) > 1 || count($dests) > 1) { $validator->errors()->add('transactions.0.source_id', (string)trans('validation.all_accounts_equal')); $validator->errors()->add('transactions.0.destination_id', (string)trans('validation.all_accounts_equal')); } @@ -239,126 +313,164 @@ trait TransactionValidation } /** - * Adds an error to the validator when the user submits a split transaction (more than 1 transactions) - * but does not give them a description. - * * @param Validator $validator + * @param TransactionGroup $transactionGroup */ - public function validateSplitDescriptions(Validator $validator): void + private function validateEqualAccountsForUpdate(Validator $validator, TransactionGroup $transactionGroup): void { $data = $validator->getData(); $transactions = $data['transactions'] ?? []; - foreach ($transactions as $index => $transaction) { - $description = (string)($transaction['description'] ?? null); - // filled description is mandatory for split transactions. - if ('' === $description && \count($transactions) > 1) { - $validator->errors()->add( - 'transactions.' . $index . '.description', - (string)trans('validation.filled', ['attribute' => (string)trans('validation.attributes.transaction_description')]) - ); + // needs to be split + if (count($transactions) < 2) { + return; + } + $type = $transactions[0]['type'] ?? strtolower($transactionGroup->transactionJournals()->first()->transactionType->type); + + // compare source ID's, destination ID's, source names and destination names. + // I think I can get away with one combination being equal, as long as the rest + // of the code picks up on this as well. + // either way all fields must be blank or all equal + // but if ID's are equal don't bother with the names. + + $fields = ['source_id', 'destination_id', 'source_name', 'destination_name']; + $comparison = []; + foreach ($fields as $field) { + $comparison[$field] = []; + /** @var array $transaction */ + foreach ($transactions as $transaction) { + // source or destination may be omitted. If this is the case, use the original source / destination name + ID. + $originalData = $this->getOriginalData((int)($transaction['transaction_journal_id'] ?? 0)); + + // get field. + $comparison[$field][] = $transaction[$field] ?? $originalData[$field]; } } - } - - /** - * Throws an error when this asset account is invalid. - * - * @noinspection MoreThanThreeArgumentsInspection - * - * @param Validator $validator - * @param int|null $accountId - * @param null|string $accountName - * @param string $idField - * @param string $nameField - * - * @return null|Account - */ - protected function assetAccountExists(Validator $validator, ?int $accountId, ?string $accountName, string $idField, string $nameField): ?Account - { - /** @var User $admin */ - $admin = auth()->user(); - $accountId = (int)$accountId; - $accountName = (string)$accountName; - // both empty? hard exit. - if ($accountId < 1 && '' === $accountName) { - $validator->errors()->add($idField, (string)trans('validation.filled', ['attribute' => $idField])); - - return null; - } - // ID belongs to user and is asset account: - /** @var AccountRepositoryInterface $repository */ - $repository = app(AccountRepositoryInterface::class); - $repository->setUser($admin); - $set = $repository->getAccountsById([$accountId]); - Log::debug(sprintf('Count of accounts found by ID %d is: %d', $accountId, $set->count())); - if (1 === $set->count()) { - /** @var Account $first */ - $first = $set->first(); - if ($first->accountType->type !== AccountType::ASSET) { - $validator->errors()->add($idField, (string)trans('validation.belongs_user')); - - return null; - } - - // we ignore the account name at this point. - return $first; - } - - $account = $repository->findByName($accountName, [AccountType::ASSET]); - if (null === $account) { - $validator->errors()->add($nameField, (string)trans('validation.belongs_user')); - - return null; - } - - return $account; - } - - /** - * Throws an error when the given opposing account (of type $type) is invalid. - * Empty data is allowed, system will default to cash. - * - * @noinspection MoreThanThreeArgumentsInspection - * - * @param Validator $validator - * @param string $type - * @param int|null $accountId - * @param null|string $accountName - * @param string $idField - * - * @return null|Account - */ - protected function opposingAccountExists(Validator $validator, string $type, ?int $accountId, ?string $accountName, string $idField): ?Account - { - /** @var User $admin */ - $admin = auth()->user(); - $accountId = (int)$accountId; - $accountName = (string)$accountName; - // both empty? done! - if ($accountId < 1 && '' === $accountName) { - return null; - } - if (0 !== $accountId) { - // ID belongs to user and is $type account: - /** @var AccountRepositoryInterface $repository */ - $repository = app(AccountRepositoryInterface::class); - $repository->setUser($admin); - $set = $repository->getAccountsById([$accountId]); - if (1 === $set->count()) { - /** @var Account $first */ - $first = $set->first(); - if ($first->accountType->type !== $type) { - $validator->errors()->add($idField, (string)trans('validation.belongs_user')); - - return null; + // TODO not the best way to loop this. + switch ($type) { + case 'withdrawal': + if ($this->arrayEqual($comparison['source_id'])) { + // source ID's are equal, return void. + return; } + if ($this->arrayEqual($comparison['source_name'])) { + // source names are equal, return void. + return; + } + // add error: + $validator->errors()->add('transactions.0.source_id', (string)trans('validation.all_accounts_equal')); + break; + case 'deposit': + if ($this->arrayEqual($comparison['destination_id'])) { + // destination ID's are equal, return void. + return; + } + if ($this->arrayEqual($comparison['destination_name'])) { + // destination names are equal, return void. + return; + } + // add error: + $validator->errors()->add('transactions.0.destination_id', (string)trans('validation.all_accounts_equal')); + break; + case 'transfer': + if ($this->arrayEqual($comparison['source_id'])) { + // source ID's are equal, return void. + return; + } + if ($this->arrayEqual($comparison['source_name'])) { + // source names are equal, return void. + return; + } + if ($this->arrayEqual($comparison['destination_id'])) { + // destination ID's are equal, return void. + return; + } + if ($this->arrayEqual($comparison['destination_name'])) { + // destination names are equal, return void. + return; + } + // add error: + $validator->errors()->add('transactions.0.source_id', (string)trans('validation.all_accounts_equal')); + $validator->errors()->add('transactions.0.destination_id', (string)trans('validation.all_accounts_equal')); + break; + } + } - // we ignore the account name at this point. - return $first; - } + /** + * @param int $journalId + * @return array + */ + private function getOriginalData(int $journalId): array + { + $return = [ + 'source_id' => 0, + 'source_name' => '', + 'destination_id' => 0, + 'destination_name' => '', + ]; + if (0 === $journalId) { + return $return; + } + /** @var Transaction $source */ + $source = Transaction::where('transaction_journal_id', $journalId)->where('amount', '<', 0)->with(['account'])->first(); + if (null !== $source) { + $return['source_id'] = $source->account_id; + $return['source_name'] = $source->account->name; + } + /** @var Transaction $destination */ + $destination = Transaction::where('transaction_journal_id', $journalId)->where('amount', '>', 0)->with(['account'])->first(); + if (null !== $source) { + $return['destination_id'] = $destination->account_id; + $return['destination_name'] = $destination->account->name; } - // not having an opposing account by this name is NOT a problem. - return null; + return $return; + } + + /** + * @param int $journalId + * @return string + */ + private function getOriginalType(int $journalId): string + { + if (0 === $journalId) { + return 'invalid'; + } + /** @var TransactionJournal $journal */ + $journal = TransactionJournal::with(['transactionType'])->find($journalId); + if (null !== $journal) { + return strtolower($journal->transactionType->type); + } + + return 'invalid'; + } + + /** + * @param Validator $validator + * @param TransactionGroup $transactionGroup + */ + private function validateJournalIds(Validator $validator, TransactionGroup $transactionGroup): void + { + $data = $validator->getData(); + $transactions = $data['transactions'] ?? []; + if (count($transactions) < 2) { + return; + } + foreach ($transactions as $index => $transaction) { + $journalId = $transaction['transaction_journal_id'] ?? null; + $journalId = null === $journalId ? null : (int)$journalId; + $count = $transactionGroup->transactionJournals()->where('id', $journalId)->count(); + if (null === $journalId || (null !== $journalId && 0 !== $journalId && 0 === $count)) { + $validator->errors()->add(sprintf('transactions.%d.source_name', $index), (string)trans('validation.need_id_in_edit')); + } + } + } + + /** + * @param array $array + * @return bool + */ + private function arrayEqual(array $array): bool + { + return 1 === count(array_unique($array)); } } diff --git a/changelog.md b/changelog.md index 47c7f4f1c3..b8117705c3 100644 --- a/changelog.md +++ b/changelog.md @@ -2,6 +2,64 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). +## [4.8.9 (API 0.10.0)] - 2019-08-22 + +A huge change that introduces significant database and API changes. Read more about it [in this Patreon post](https://www.patreon.com/posts/29044368). + +### Open and known issues +- The "new transaction"-form isn't translated. +- You can't drag and drop transactions. +- You can't clone transactions. + +### Added +- Hungarian translation! + +### Changed +- New database model that changes the concept of "split transactions"; +- New installation routine with rewritten database integrity tests and upgrade code; +- Rewritten screen to create transactions which will now completely rely on the API; +- Most terminal commands now have the prefix `firefly-iii`. +- New MFA code that will generate backup codes for you and is more robust. MFA will have to be re-enabled for ALL users. + +### Deprecated +- This will probably be the last Firefly III version to have import routines for files, Bunq and others. These will be moved to separate applications that use the Firefly III API. + +### Removed +- The export function has been removed. + +### Fixed +- [Issue 1652](https://github.com/firefly-iii/firefly-iii/issues/1652), new strings to use during the import. +- [Issue 1860](https://github.com/firefly-iii/firefly-iii/issues/1860), fixing the default currency not being on top in a JSON box. +- [Issue 2031](https://github.com/firefly-iii/firefly-iii/issues/2031), a fix for Triodos imports. +- [Issue 2153](https://github.com/firefly-iii/firefly-iii/issues/2153), problems with editing credit cards. +- [Issue 2179](https://github.com/firefly-iii/firefly-iii/issues/2179), consistent and correct redirect behavior. +- [Issue 2180](https://github.com/firefly-iii/firefly-iii/issues/2180), API issues with foreign amounts. +- [Issue 2187](https://github.com/firefly-iii/firefly-iii/issues/2187), bulk editing reconciled transactions was broken. +- [Issue 2188](https://github.com/firefly-iii/firefly-iii/issues/2188), redirect loop in bills +- [Issue 2189](https://github.com/firefly-iii/firefly-iii/issues/2189), bulk edit could not handle tags. +- [Issue 2203](https://github.com/firefly-iii/firefly-iii/issues/2203), [issue 2208](https://github.com/firefly-iii/firefly-iii/issues/2208), [issue 2352](https://github.com/firefly-iii/firefly-iii/issues/2352), reconciliation fixes +- [Issue 2204](https://github.com/firefly-iii/firefly-iii/issues/2204), transaction type fix +- [Issue 2211](https://github.com/firefly-iii/firefly-iii/issues/2211), mass edit fixes. +- [Issue 2212](https://github.com/firefly-iii/firefly-iii/issues/2212), bug in the API when deleting objects. +- [Issue 2214](https://github.com/firefly-iii/firefly-iii/issues/2214), could not view attachment. +- [Issue 2219](https://github.com/firefly-iii/firefly-iii/issues/2219), max amount was a little low. +- [Issue 2239](https://github.com/firefly-iii/firefly-iii/issues/2239), fixed ordering issue. +- [Issue 2246](https://github.com/firefly-iii/firefly-iii/issues/2246), could not disable EUR. +- [Issue 2268](https://github.com/firefly-iii/firefly-iii/issues/2268), could not import into liability accounts. +- [Issue 2293](https://github.com/firefly-iii/firefly-iii/issues/2293), could not trigger rule on deposits in some circumstances +- [Issue 2314](https://github.com/firefly-iii/firefly-iii/issues/2314), could not trigger rule on transfers in some circumstances +- [Issue 2325](https://github.com/firefly-iii/firefly-iii/issues/2325), some balance issues on the frontpage. +- [Issue 2328](https://github.com/firefly-iii/firefly-iii/issues/2328), some date range issues in reports +- [Issue 2331](https://github.com/firefly-iii/firefly-iii/issues/2331), some broken fields in reports. +- [Issue 2333](https://github.com/firefly-iii/firefly-iii/issues/2333), API issues with piggy banks. +- [Issue 2355](https://github.com/firefly-iii/firefly-iii/issues/2355), configuration issues with LDAP +- [Issue 2361](https://github.com/firefly-iii/firefly-iii/issues/2361), some ordering issues. + +### API +- Updated API to reflect the changes in the database. +- New API end-point for a summary of your data. +- Some new API charts. + ## [4.7.17.6 (API 0.9.2)] - 2019-08-02 ### Security diff --git a/composer.json b/composer.json index 96b087f25b..a8f1912d79 100644 --- a/composer.json +++ b/composer.json @@ -67,7 +67,7 @@ "ext-tokenizer": "*", "ext-xml": "*", "ext-zip": "*", - "adldap2/adldap2-laravel": "5.*", + "adldap2/adldap2-laravel": "6.*", "bacon/bacon-qr-code": "1.*", "bunq/sdk_php": "dev-master", "danhunsaker/laravel-flysystem-others": "1.*", @@ -77,14 +77,15 @@ "laravel/framework": "5.8.*", "laravel/passport": "7.*", "laravelcollective/html": "5.8.*", - "league/commonmark": "0.*", + "league/commonmark": "1.*", "league/csv": "9.*", "league/flysystem-replicate-adapter": "1.*", "league/flysystem-sftp": "1.*", "league/fractal": "0.*", "litipk/flysystem-fallback-adapter": "0.*", "mschindler83/fints-hbci-php": "1.*", - "pragmarx/google2fa-laravel": "0.*", + "pragmarx/google2fa-laravel": "1.*", + "pragmarx/recovery": "^0.1.0", "rcrowe/twigbridge": "0.9.*" }, "require-dev": { @@ -131,9 +132,39 @@ "Illuminate\\Foundation\\ComposerScripts::postAutoloadDump" ], "post-update-cmd": [ - "@php artisan firefly:upgrade-database", - "@php artisan firefly:decrypt-all", - "@php artisan firefly:verify", + "@php artisan cache:clear", + "@php artisan firefly-iii:decrypt-all", + + "@php artisan firefly-iii:transaction-identifiers", + "@php artisan firefly-iii:migrate-to-groups", + "@php artisan firefly-iii:account-currencies", + "@php artisan firefly-iii:transfer-currencies", + "@php artisan firefly-iii:other-currencies", + "@php artisan firefly-iii:migrate-notes", + "@php artisan firefly-iii:migrate-attachments", + "@php artisan firefly-iii:bills-to-rules", + "@php artisan firefly-iii:bl-currency", + "@php artisan firefly-iii:cc-liabilities", + "@php artisan firefly-iii:back-to-journals", + "@php artisan firefly-iii:rename-account-meta", + + "@php artisan firefly-iii:fix-piggies", + "@php artisan firefly-iii:create-link-types", + "@php artisan firefly-iii:create-access-tokens", + "@php artisan firefly-iii:remove-bills", + "@php artisan firefly-iii:enable-currencies", + "@php artisan firefly-iii:fix-transfer-budgets", + "@php artisan firefly-iii:fix-uneven-amount", + "@php artisan firefly-iii:delete-zero-amount", + "@php artisan firefly-iii:delete-orphaned-transactions", + "@php artisan firefly-iii:delete-empty-journals", + "@php artisan firefly-iii:delete-empty-groups", + "@php artisan firefly-iii:fix-account-types", + "@php artisan firefly-iii:rename-meta-fields", + + "@php artisan firefly-iii:report-empty-objects", + "@php artisan firefly-iii:report-sum", + "@php artisan firefly:instructions update", "@php artisan passport:install" ], diff --git a/composer.lock b/composer.lock index 0f67eb7bf6..f700899494 100644 --- a/composer.lock +++ b/composer.lock @@ -4,27 +4,27 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "574b38ec48f5e9c1400138b63f0a3d7d", + "content-hash": "6f0659be8b94240ba32a05fb5f693a01", "packages": [ { "name": "adldap2/adldap2", - "version": "v10.0.4", + "version": "v10.0.11", "source": { "type": "git", "url": "https://github.com/Adldap2/Adldap2.git", - "reference": "345631260d5aedbd5e196a88b57eb1bf19e68fd1" + "reference": "beb3c9cc28de752d6c3b0221605035659eccc42e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Adldap2/Adldap2/zipball/345631260d5aedbd5e196a88b57eb1bf19e68fd1", - "reference": "345631260d5aedbd5e196a88b57eb1bf19e68fd1", + "url": "https://api.github.com/repos/Adldap2/Adldap2/zipball/beb3c9cc28de752d6c3b0221605035659eccc42e", + "reference": "beb3c9cc28de752d6c3b0221605035659eccc42e", "shasum": "" }, "require": { "ext-ldap": "*", "illuminate/contracts": "~5.0", "php": ">=7.0", - "psr/log": "~1.1", + "psr/log": "~1.0", "tightenco/collect": "~5.0" }, "require-dev": { @@ -47,8 +47,8 @@ "authors": [ { "name": "Steve Bauman", - "email": "steven_bauman@outlook.com", - "role": "Developer" + "role": "Developer", + "email": "steven_bauman@outlook.com" } ], "description": "A PHP LDAP Package for humans.", @@ -61,24 +61,25 @@ "ldap", "windows" ], - "time": "2019-03-15T15:53:20+00:00" + "time": "2019-05-24T14:15:58+00:00" }, { "name": "adldap2/adldap2-laravel", - "version": "v5.1.2", + "version": "v6.0.7", "source": { "type": "git", "url": "https://github.com/Adldap2/Adldap2-Laravel.git", - "reference": "31f80dfad6950f80698986e91cb65eb0c441f182" + "reference": "e7e60f0f10ff845bf8e8c965258c86a33c9ab266" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Adldap2/Adldap2-Laravel/zipball/31f80dfad6950f80698986e91cb65eb0c441f182", - "reference": "31f80dfad6950f80698986e91cb65eb0c441f182", + "url": "https://api.github.com/repos/Adldap2/Adldap2-Laravel/zipball/e7e60f0f10ff845bf8e8c965258c86a33c9ab266", + "reference": "e7e60f0f10ff845bf8e8c965258c86a33c9ab266", "shasum": "" }, "require": { "adldap2/adldap2": "^10.0", + "illuminate/support": "~5.5.0|~5.6.0|~5.7.0|~5.8.0", "php": ">=7.1" }, "require-dev": { @@ -114,7 +115,7 @@ "laravel", "ldap" ], - "time": "2019-02-27T07:09:43+00:00" + "time": "2019-06-28T17:34:46+00:00" }, { "name": "bacon/bacon-qr-code", @@ -168,12 +169,12 @@ "source": { "type": "git", "url": "https://github.com/bunq/sdk_php.git", - "reference": "ef02fbc2e1445290d8779629b5b0e34a5e19ee54" + "reference": "be645736a2488ec247f0be528bab7619768da12f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bunq/sdk_php/zipball/ef02fbc2e1445290d8779629b5b0e34a5e19ee54", - "reference": "ef02fbc2e1445290d8779629b5b0e34a5e19ee54", + "url": "https://api.github.com/repos/bunq/sdk_php/zipball/be645736a2488ec247f0be528bab7619768da12f", + "reference": "be645736a2488ec247f0be528bab7619768da12f", "shasum": "" }, "require": { @@ -224,7 +225,7 @@ "payment", "sepa" ], - "time": "2018-11-21T18:55:40+00:00" + "time": "2019-06-15T12:22:02+00:00" }, { "name": "danhunsaker/laravel-flysystem-others", @@ -819,30 +820,35 @@ }, { "name": "doctrine/lexer", - "version": "v1.0.1", + "version": "1.1.0", "source": { "type": "git", "url": "https://github.com/doctrine/lexer.git", - "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c" + "reference": "e17f069ede36f7534b95adec71910ed1b49c74ea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/83893c552fd2045dd78aef794c31e694c37c0b8c", - "reference": "83893c552fd2045dd78aef794c31e694c37c0b8c", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/e17f069ede36f7534b95adec71910ed1b49c74ea", + "reference": "e17f069ede36f7534b95adec71910ed1b49c74ea", "shasum": "" }, "require": { - "php": ">=5.3.2" + "php": "^7.2" + }, + "require-dev": { + "doctrine/coding-standard": "^6.0", + "phpstan/phpstan": "^0.11.8", + "phpunit/phpunit": "^8.2" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "1.1.x-dev" } }, "autoload": { - "psr-0": { - "Doctrine\\Common\\Lexer\\": "lib/" + "psr-4": { + "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer" } }, "notification-url": "https://packagist.org/downloads/", @@ -850,48 +856,56 @@ "MIT" ], "authors": [ - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, { "name": "Guilherme Blanco", "email": "guilhermeblanco@gmail.com" }, + { + "name": "Roman Borschel", + "email": "roman@code-factory.org" + }, { "name": "Johannes Schmitt", "email": "schmittjoh@gmail.com" } ], - "description": "Base library for a lexer that can be used in Top-Down, Recursive Descent Parsers.", - "homepage": "http://www.doctrine-project.org", + "description": "PHP Doctrine Lexer parser library that can be used in Top-Down, Recursive Descent Parsers.", + "homepage": "https://www.doctrine-project.org/projects/lexer.html", "keywords": [ + "annotations", + "docblock", "lexer", - "parser" + "parser", + "php" ], - "time": "2014-09-09T13:34:57+00:00" + "time": "2019-07-30T19:33:28+00:00" }, { "name": "dragonmantank/cron-expression", - "version": "v2.2.0", + "version": "v2.3.0", "source": { "type": "git", "url": "https://github.com/dragonmantank/cron-expression.git", - "reference": "92a2c3768d50e21a1f26a53cb795ce72806266c5" + "reference": "72b6fbf76adb3cf5bc0db68559b33d41219aba27" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/92a2c3768d50e21a1f26a53cb795ce72806266c5", - "reference": "92a2c3768d50e21a1f26a53cb795ce72806266c5", + "url": "https://api.github.com/repos/dragonmantank/cron-expression/zipball/72b6fbf76adb3cf5bc0db68559b33d41219aba27", + "reference": "72b6fbf76adb3cf5bc0db68559b33d41219aba27", "shasum": "" }, "require": { - "php": ">=7.0.0" + "php": "^7.0" }, "require-dev": { - "phpunit/phpunit": "~6.4" + "phpunit/phpunit": "^6.4|^7.0" }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.3-dev" + } + }, "autoload": { "psr-4": { "Cron\\": "src/Cron/" @@ -918,20 +932,20 @@ "cron", "schedule" ], - "time": "2018-06-06T03:12:17+00:00" + "time": "2019-03-31T00:38:28+00:00" }, { "name": "egulias/email-validator", - "version": "2.1.7", + "version": "2.1.10", "source": { "type": "git", "url": "https://github.com/egulias/EmailValidator.git", - "reference": "709f21f92707308cdf8f9bcfa1af4cb26586521e" + "reference": "a6c8d7101b19a451c1707b1b79bbbc56e4bdb7ec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/709f21f92707308cdf8f9bcfa1af4cb26586521e", - "reference": "709f21f92707308cdf8f9bcfa1af4cb26586521e", + "url": "https://api.github.com/repos/egulias/EmailValidator/zipball/a6c8d7101b19a451c1707b1b79bbbc56e4bdb7ec", + "reference": "a6c8d7101b19a451c1707b1b79bbbc56e4bdb7ec", "shasum": "" }, "require": { @@ -941,7 +955,8 @@ "require-dev": { "dominicsayers/isemail": "dev-master", "phpunit/phpunit": "^4.8.35||^5.7||^6.0", - "satooshi/php-coveralls": "^1.0.1" + "satooshi/php-coveralls": "^1.0.1", + "symfony/phpunit-bridge": "^4.4@dev" }, "suggest": { "ext-intl": "PHP Internationalization Libraries are required to use the SpoofChecking validation" @@ -975,20 +990,20 @@ "validation", "validator" ], - "time": "2018-12-04T22:38:24+00:00" + "time": "2019-07-19T20:52:08+00:00" }, { "name": "erusev/parsedown", - "version": "1.7.1", + "version": "1.7.3", "source": { "type": "git", "url": "https://github.com/erusev/parsedown.git", - "reference": "92e9c27ba0e74b8b028b111d1b6f956a15c01fc1" + "reference": "6d893938171a817f4e9bc9e86f2da1e370b7bcd7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/erusev/parsedown/zipball/92e9c27ba0e74b8b028b111d1b6f956a15c01fc1", - "reference": "92e9c27ba0e74b8b028b111d1b6f956a15c01fc1", + "url": "https://api.github.com/repos/erusev/parsedown/zipball/6d893938171a817f4e9bc9e86f2da1e370b7bcd7", + "reference": "6d893938171a817f4e9bc9e86f2da1e370b7bcd7", "shasum": "" }, "require": { @@ -1021,28 +1036,28 @@ "markdown", "parser" ], - "time": "2018-03-08T01:11:30+00:00" + "time": "2019-03-17T18:48:37+00:00" }, { "name": "fideloper/proxy", - "version": "4.1.0", + "version": "4.2.0", "source": { "type": "git", "url": "https://github.com/fideloper/TrustedProxy.git", - "reference": "177c79a2d1f9970f89ee2fb4c12b429af38b6dfb" + "reference": "39a4c2165e578bc771f5dc031c273210a3a9b6d2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/fideloper/TrustedProxy/zipball/177c79a2d1f9970f89ee2fb4c12b429af38b6dfb", - "reference": "177c79a2d1f9970f89ee2fb4c12b429af38b6dfb", + "url": "https://api.github.com/repos/fideloper/TrustedProxy/zipball/39a4c2165e578bc771f5dc031c273210a3a9b6d2", + "reference": "39a4c2165e578bc771f5dc031c273210a3a9b6d2", "shasum": "" }, "require": { - "illuminate/contracts": "~5.0", + "illuminate/contracts": "~5.0|~6.0", "php": ">=5.4.0" }, "require-dev": { - "illuminate/http": "~5.6", + "illuminate/http": "~5.6|~6.0", "mockery/mockery": "~1.0", "phpunit/phpunit": "^6.0" }, @@ -1075,7 +1090,7 @@ "proxy", "trusted proxy" ], - "time": "2019-01-10T14:06:47+00:00" + "time": "2019-07-29T16:49:45+00:00" }, { "name": "firebase/php-jwt", @@ -1110,13 +1125,13 @@ "authors": [ { "name": "Neuman Vong", - "email": "neuman+pear@twilio.com", - "role": "Developer" + "role": "Developer", + "email": "neuman+pear@twilio.com" }, { "name": "Anant Narayanan", - "email": "anant@php.net", - "role": "Developer" + "role": "Developer", + "email": "anant@php.net" } ], "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.", @@ -1241,33 +1256,37 @@ }, { "name": "guzzlehttp/psr7", - "version": "1.5.2", + "version": "1.6.1", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "9f83dded91781a01c63574e387eaa769be769115" + "reference": "239400de7a173fe9901b9ac7c06497751f00727a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/9f83dded91781a01c63574e387eaa769be769115", - "reference": "9f83dded91781a01c63574e387eaa769be769115", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/239400de7a173fe9901b9ac7c06497751f00727a", + "reference": "239400de7a173fe9901b9ac7c06497751f00727a", "shasum": "" }, "require": { "php": ">=5.4.0", "psr/http-message": "~1.0", - "ralouphie/getallheaders": "^2.0.5" + "ralouphie/getallheaders": "^2.0.5 || ^3.0.0" }, "provide": { "psr/http-message-implementation": "1.0" }, "require-dev": { + "ext-zlib": "*", "phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.8" }, + "suggest": { + "zendframework/zend-httphandlerrunner": "Emit PSR-7 responses" + }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.5-dev" + "dev-master": "1.6-dev" } }, "autoload": { @@ -1304,20 +1323,20 @@ "uri", "url" ], - "time": "2018-12-04T20:46:45+00:00" + "time": "2019-07-01T23:21:34+00:00" }, { "name": "laravel/framework", - "version": "v5.8.4", + "version": "v5.8.31", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "d651f8bd25c6baf7ae4913bc51f02849fad4e925" + "reference": "24cc1786bd55876fa52380306354772355345efd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/d651f8bd25c6baf7ae4913bc51f02849fad4e925", - "reference": "d651f8bd25c6baf7ae4913bc51f02849fad4e925", + "url": "https://api.github.com/repos/laravel/framework/zipball/24cc1786bd55876fa52380306354772355345efd", + "reference": "24cc1786bd55876fa52380306354772355345efd", "shasum": "" }, "require": { @@ -1451,39 +1470,39 @@ "framework", "laravel" ], - "time": "2019-03-12T13:33:14+00:00" + "time": "2019-08-06T15:09:02+00:00" }, { "name": "laravel/passport", - "version": "v7.2.1", + "version": "v7.3.5", "source": { "type": "git", "url": "https://github.com/laravel/passport.git", - "reference": "bd8ae09775778f96b6642d87e2f579fea5bf92b5" + "reference": "57937b08dc8e444b4756782a5ba172b5ba54d4f5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/passport/zipball/bd8ae09775778f96b6642d87e2f579fea5bf92b5", - "reference": "bd8ae09775778f96b6642d87e2f579fea5bf92b5", + "url": "https://api.github.com/repos/laravel/passport/zipball/57937b08dc8e444b4756782a5ba172b5ba54d4f5", + "reference": "57937b08dc8e444b4756782a5ba172b5ba54d4f5", "shasum": "" }, "require": { "ext-json": "*", "firebase/php-jwt": "~3.0|~4.0|~5.0", "guzzlehttp/guzzle": "~6.0", - "illuminate/auth": "~5.6.0|~5.7.0|~5.8.0|~5.9.0", - "illuminate/console": "~5.6.0|~5.7.0|~5.8.0|~5.9.0", - "illuminate/container": "~5.6.0|~5.7.0|~5.8.0|~5.9.0", - "illuminate/contracts": "~5.6.0|~5.7.0|~5.8.0|~5.9.0", - "illuminate/database": "~5.6.0|~5.7.0|~5.8.0|~5.9.0", - "illuminate/encryption": "~5.6.0|~5.7.0|~5.8.0|~5.9.0", - "illuminate/http": "~5.6.0|~5.7.0|~5.8.0|~5.9.0", - "illuminate/support": "~5.6.0|~5.7.0|~5.8.0|~5.9.0", + "illuminate/auth": "~5.6.0|~5.7.0|~5.8.0|^6.0", + "illuminate/console": "~5.6.0|~5.7.0|~5.8.0|^6.0", + "illuminate/container": "~5.6.0|~5.7.0|~5.8.0|^6.0", + "illuminate/contracts": "~5.6.0|~5.7.0|~5.8.0|^6.0", + "illuminate/database": "~5.6.0|~5.7.0|~5.8.0|^6.0", + "illuminate/encryption": "~5.6.0|~5.7.0|~5.8.0|^6.0", + "illuminate/http": "~5.6.0|~5.7.0|~5.8.0|^6.0", + "illuminate/support": "~5.6.0|~5.7.0|~5.8.0|^6.0", "league/oauth2-server": "^7.0", "php": ">=7.1", "phpseclib/phpseclib": "^2.0", "symfony/psr-http-message-bridge": "~1.0", - "zendframework/zend-diactoros": "~1.0" + "zendframework/zend-diactoros": "~1.0|~2.0" }, "require-dev": { "mockery/mockery": "~1.0", @@ -1521,7 +1540,7 @@ "oauth", "passport" ], - "time": "2019-03-12T11:42:07+00:00" + "time": "2019-08-06T18:10:19+00:00" }, { "name": "laravelcollective/html", @@ -1593,33 +1612,30 @@ }, { "name": "lcobucci/jwt", - "version": "3.2.5", + "version": "3.3.1", "source": { "type": "git", "url": "https://github.com/lcobucci/jwt.git", - "reference": "82be04b4753f8b7693b62852b7eab30f97524f9b" + "reference": "a11ec5f4b4d75d1fcd04e133dede4c317aac9e18" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/lcobucci/jwt/zipball/82be04b4753f8b7693b62852b7eab30f97524f9b", - "reference": "82be04b4753f8b7693b62852b7eab30f97524f9b", + "url": "https://api.github.com/repos/lcobucci/jwt/zipball/a11ec5f4b4d75d1fcd04e133dede4c317aac9e18", + "reference": "a11ec5f4b4d75d1fcd04e133dede4c317aac9e18", "shasum": "" }, "require": { + "ext-mbstring": "*", "ext-openssl": "*", - "php": ">=5.5" + "php": "^5.6 || ^7.0" }, "require-dev": { - "mdanter/ecc": "~0.3.1", "mikey179/vfsstream": "~1.5", "phpmd/phpmd": "~2.2", "phpunit/php-invoker": "~1.1", - "phpunit/phpunit": "~4.5", + "phpunit/phpunit": "^5.7 || ^7.3", "squizlabs/php_codesniffer": "~2.3" }, - "suggest": { - "mdanter/ecc": "Required to use Elliptic Curves based algorithms." - }, "type": "library", "extra": { "branch-alias": { @@ -1647,38 +1663,39 @@ "JWS", "jwt" ], - "time": "2018-11-11T12:22:26+00:00" + "time": "2019-05-24T18:30:49+00:00" }, { "name": "league/commonmark", - "version": "0.18.2", + "version": "1.0.0", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "ad51c7cafb90e0bbd9f34b71d18d05994547e352" + "reference": "7a40f2b0931602c504c2a9692d9f1e33635fd5ef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/ad51c7cafb90e0bbd9f34b71d18d05994547e352", - "reference": "ad51c7cafb90e0bbd9f34b71d18d05994547e352", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/7a40f2b0931602c504c2a9692d9f1e33635fd5ef", + "reference": "7a40f2b0931602c504c2a9692d9f1e33635fd5ef", "shasum": "" }, "require": { "ext-mbstring": "*", - "php": ">=5.6.5" + "php": "^7.1" }, "replace": { "colinodell/commonmark-php": "*" }, "require-dev": { "cebe/markdown": "~1.0", - "commonmark/commonmark.js": "0.28", + "commonmark/commonmark.js": "0.29.0", "erusev/parsedown": "~1.0", "michelf/php-markdown": "~1.4", - "mikehaertl/php-shellcommand": "^1.2", - "phpunit/phpunit": "^5.7|^6.5", - "scrutinizer/ocular": "^1.1", - "symfony/finder": "^3.0|^4.0" + "mikehaertl/php-shellcommand": "^1.4", + "phpstan/phpstan-shim": "^0.11.5", + "phpunit/phpunit": "^7.5", + "scrutinizer/ocular": "^1.5", + "symfony/finder": "^4.2" }, "suggest": { "league/commonmark-extras": "Library of useful extensions including smart punctuation" @@ -1689,12 +1706,12 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "0.19-dev" + "dev-master": "1.1-dev" } }, "autoload": { "psr-4": { - "League\\CommonMark\\": "src/" + "League\\CommonMark\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -1710,29 +1727,31 @@ } ], "description": "PHP Markdown parser based on the CommonMark spec", - "homepage": "https://github.com/thephpleague/commonmark", + "homepage": "https://commonmark.thephpleague.com", "keywords": [ "commonmark", "markdown", "parser" ], - "time": "2019-03-17T01:41:59+00:00" + "time": "2019-06-29T11:19:01+00:00" }, { "name": "league/csv", - "version": "9.2.0", + "version": "9.3.0", "source": { "type": "git", "url": "https://github.com/thephpleague/csv.git", - "reference": "f3a3c69b6e152417e1b62d995bcad2237b053cc6" + "reference": "d16f85d1f958a765844db4bc7174017edf2dd637" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/csv/zipball/f3a3c69b6e152417e1b62d995bcad2237b053cc6", - "reference": "f3a3c69b6e152417e1b62d995bcad2237b053cc6", + "url": "https://api.github.com/repos/thephpleague/csv/zipball/d16f85d1f958a765844db4bc7174017edf2dd637", + "reference": "d16f85d1f958a765844db4bc7174017edf2dd637", "shasum": "" }, "require": { + "ext-dom": "*", + "ext-json": "*", "ext-mbstring": "*", "php": ">=7.0.10" }, @@ -1768,9 +1787,9 @@ "authors": [ { "name": "Ignace Nyamagana Butera", + "role": "Developer", "email": "nyamsprod@gmail.com", - "homepage": "https://github.com/nyamsprod/", - "role": "Developer" + "homepage": "https://github.com/nyamsprod/" } ], "description": "Csv data manipulation made easy in PHP", @@ -1783,7 +1802,7 @@ "read", "write" ], - "time": "2019-03-08T06:56:16+00:00" + "time": "2019-07-30T14:39:11+00:00" }, { "name": "league/event", @@ -1837,16 +1856,16 @@ }, { "name": "league/flysystem", - "version": "1.0.50", + "version": "1.0.53", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "dab4e7624efa543a943be978008f439c333f2249" + "reference": "08e12b7628f035600634a5e76d95b5eb66cea674" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/dab4e7624efa543a943be978008f439c333f2249", - "reference": "dab4e7624efa543a943be978008f439c333f2249", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/08e12b7628f035600634a5e76d95b5eb66cea674", + "reference": "08e12b7628f035600634a5e76d95b5eb66cea674", "shasum": "" }, "require": { @@ -1917,7 +1936,7 @@ "sftp", "storage" ], - "time": "2019-02-01T08:50:36+00:00" + "time": "2019-06-18T20:09:29+00:00" }, { "name": "league/flysystem-replicate-adapter", @@ -1967,16 +1986,16 @@ }, { "name": "league/flysystem-sftp", - "version": "1.0.18", + "version": "1.0.20", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem-sftp.git", - "reference": "61bc5a6ade892b5ac81e62b8c21be2c1798acc2a" + "reference": "518ac7dc9e80ca55ab6c3cebc8bccb4c2a5af302" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-sftp/zipball/61bc5a6ade892b5ac81e62b8c21be2c1798acc2a", - "reference": "61bc5a6ade892b5ac81e62b8c21be2c1798acc2a", + "url": "https://api.github.com/repos/thephpleague/flysystem-sftp/zipball/518ac7dc9e80ca55ab6c3cebc8bccb4c2a5af302", + "reference": "518ac7dc9e80ca55ab6c3cebc8bccb4c2a5af302", "shasum": "" }, "require": { @@ -2005,20 +2024,20 @@ } ], "description": "Flysystem adapter for SFTP", - "time": "2019-01-07T11:56:21+00:00" + "time": "2019-06-07T20:54:19+00:00" }, { "name": "league/fractal", - "version": "0.17.0", + "version": "0.18.0", "source": { "type": "git", "url": "https://github.com/thephpleague/fractal.git", - "reference": "a0b350824f22fc2fdde2500ce9d6851a3f275b0e" + "reference": "4e553dae1a9402adbe11c81430a64675dc97b4fc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/fractal/zipball/a0b350824f22fc2fdde2500ce9d6851a3f275b0e", - "reference": "a0b350824f22fc2fdde2500ce9d6851a3f275b0e", + "url": "https://api.github.com/repos/thephpleague/fractal/zipball/4e553dae1a9402adbe11c81430a64675dc97b4fc", + "reference": "4e553dae1a9402adbe11c81430a64675dc97b4fc", "shasum": "" }, "require": { @@ -2029,7 +2048,7 @@ "illuminate/contracts": "~5.0", "mockery/mockery": "~0.9", "pagerfanta/pagerfanta": "~1.0.0", - "phpunit/phpunit": "~4.0", + "phpunit/phpunit": "^4.8.35 || ^7.5", "squizlabs/php_codesniffer": "~1.5", "zendframework/zend-paginator": "~2.3" }, @@ -2069,20 +2088,20 @@ "league", "rest" ], - "time": "2017-06-12T11:04:56+00:00" + "time": "2019-05-10T02:16:43+00:00" }, { "name": "league/oauth2-server", - "version": "7.3.2", + "version": "7.4.0", "source": { "type": "git", "url": "https://github.com/thephpleague/oauth2-server.git", - "reference": "b71f382cd76e3f6905dfc53ef8148b3eebe1fd41" + "reference": "2eb1cf79e59d807d89c256e7ac5e2bf8bdbd4acf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/oauth2-server/zipball/b71f382cd76e3f6905dfc53ef8148b3eebe1fd41", - "reference": "b71f382cd76e3f6905dfc53ef8148b3eebe1fd41", + "url": "https://api.github.com/repos/thephpleague/oauth2-server/zipball/2eb1cf79e59d807d89c256e7ac5e2bf8bdbd4acf", + "reference": "2eb1cf79e59d807d89c256e7ac5e2bf8bdbd4acf", "shasum": "" }, "require": { @@ -2118,15 +2137,15 @@ "authors": [ { "name": "Alex Bilbie", + "role": "Developer", "email": "hello@alexbilbie.com", - "homepage": "http://www.alexbilbie.com", - "role": "Developer" + "homepage": "http://www.alexbilbie.com" }, { "name": "Andy Millington", + "role": "Developer", "email": "andrew@noexceptions.io", - "homepage": "https://www.noexceptions.io", - "role": "Developer" + "homepage": "https://www.noexceptions.io" } ], "description": "A lightweight and powerful OAuth 2.0 authorization and resource server library with support for all the core specification grants. This library will allow you to secure your API with OAuth and allow your applications users to approve apps that want to access their data from your API.", @@ -2146,7 +2165,7 @@ "secure", "server" ], - "time": "2018-11-21T21:42:43+00:00" + "time": "2019-05-05T09:22:01+00:00" }, { "name": "litipk/flysystem-fallback-adapter", @@ -2304,16 +2323,16 @@ }, { "name": "nesbot/carbon", - "version": "2.16.0", + "version": "2.22.3", "source": { "type": "git", "url": "https://github.com/briannesbitt/Carbon.git", - "reference": "dd16fedc022180ea4292a03aabe95e9895677911" + "reference": "738fbd8d80b2c5e158fda76c29c2de432fcc6f7e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/dd16fedc022180ea4292a03aabe95e9895677911", - "reference": "dd16fedc022180ea4292a03aabe95e9895677911", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/738fbd8d80b2c5e158fda76c29c2de432fcc6f7e", + "reference": "738fbd8d80b2c5e158fda76c29c2de432fcc6f7e", "shasum": "" }, "require": { @@ -2323,12 +2342,15 @@ }, "require-dev": { "friendsofphp/php-cs-fixer": "^2.14 || ^3.0", - "kylekatarnls/multi-tester": "^0.1", - "phpmd/phpmd": "^2.6", - "phpstan/phpstan": "^0.10.8", + "kylekatarnls/multi-tester": "^1.1", + "phpmd/phpmd": "dev-php-7.1-compatibility", + "phpstan/phpstan": "^0.11", "phpunit/phpunit": "^7.5 || ^8.0", "squizlabs/php_codesniffer": "^3.4" }, + "bin": [ + "bin/carbon" + ], "type": "library", "extra": { "laravel": { @@ -2351,6 +2373,10 @@ "name": "Brian Nesbitt", "email": "brian@nesbot.com", "homepage": "http://nesbot.com" + }, + { + "name": "kylekatarnls", + "homepage": "http://github.com/kylekatarnls" } ], "description": "A simple API extension for DateTime.", @@ -2360,20 +2386,20 @@ "datetime", "time" ], - "time": "2019-03-12T09:31:40+00:00" + "time": "2019-08-07T12:36:44+00:00" }, { "name": "opis/closure", - "version": "3.1.6", + "version": "3.3.1", "source": { "type": "git", "url": "https://github.com/opis/closure.git", - "reference": "ccb8e3928c5c8181c76cdd0ed9366c5bcaafd91b" + "reference": "92927e26d7fc3f271efe1f55bdbb073fbb2f0722" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/opis/closure/zipball/ccb8e3928c5c8181c76cdd0ed9366c5bcaafd91b", - "reference": "ccb8e3928c5c8181c76cdd0ed9366c5bcaafd91b", + "url": "https://api.github.com/repos/opis/closure/zipball/92927e26d7fc3f271efe1f55bdbb073fbb2f0722", + "reference": "92927e26d7fc3f271efe1f55bdbb073fbb2f0722", "shasum": "" }, "require": { @@ -2381,12 +2407,12 @@ }, "require-dev": { "jeremeamia/superclosure": "^2.0", - "phpunit/phpunit": "^4.0|^5.0|^6.0|^7.0" + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.1.x-dev" + "dev-master": "3.3.x-dev" } }, "autoload": { @@ -2421,7 +2447,7 @@ "serialization", "serialize" ], - "time": "2019-02-22T10:30:00+00:00" + "time": "2019-07-09T21:58:11+00:00" }, { "name": "paragonie/constant_time_encoding", @@ -2457,15 +2483,15 @@ "authors": [ { "name": "Paragon Initiative Enterprises", + "role": "Maintainer", "email": "security@paragonie.com", - "homepage": "https://paragonie.com", - "role": "Maintainer" + "homepage": "https://paragonie.com" }, { "name": "Steve 'Sc00bz' Thomas", + "role": "Original Developer", "email": "steve@tobtu.com", - "homepage": "https://www.tobtu.com", - "role": "Original Developer" + "homepage": "https://www.tobtu.com" } ], "description": "Constant-time Implementations of RFC 4648 Encoding (Base-64, Base-32, Base-16)", @@ -2582,16 +2608,16 @@ }, { "name": "phpseclib/phpseclib", - "version": "2.0.15", + "version": "2.0.21", "source": { "type": "git", "url": "https://github.com/phpseclib/phpseclib.git", - "reference": "11cf67cf78dc4acb18dc9149a57be4aee5036ce0" + "reference": "9f1287e68b3f283339a9f98f67515dd619e5bf9d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/11cf67cf78dc4acb18dc9149a57be4aee5036ce0", - "reference": "11cf67cf78dc4acb18dc9149a57be4aee5036ce0", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/9f1287e68b3f283339a9f98f67515dd619e5bf9d", + "reference": "9f1287e68b3f283339a9f98f67515dd619e5bf9d", "shasum": "" }, "require": { @@ -2625,28 +2651,28 @@ "authors": [ { "name": "Jim Wigginton", - "email": "terrafrost@php.net", - "role": "Lead Developer" + "role": "Lead Developer", + "email": "terrafrost@php.net" }, { "name": "Patrick Monnerat", - "email": "pm@datasphere.ch", - "role": "Developer" + "role": "Developer", + "email": "pm@datasphere.ch" }, { "name": "Andreas Fischer", - "email": "bantu@phpbb.com", - "role": "Developer" + "role": "Developer", + "email": "bantu@phpbb.com" }, { "name": "Hans-Jürgen Petrich", - "email": "petrich@tronic-media.com", - "role": "Developer" + "role": "Developer", + "email": "petrich@tronic-media.com" }, { "name": "Graham Campbell", - "email": "graham@alt-three.com", - "role": "Developer" + "role": "Developer", + "email": "graham@alt-three.com" } ], "description": "PHP Secure Communications Library - Pure-PHP implementations of RSA, AES, SSH2, SFTP, X.509 etc.", @@ -2670,20 +2696,20 @@ "x.509", "x509" ], - "time": "2019-03-10T16:53:45+00:00" + "time": "2019-07-12T12:53:49+00:00" }, { "name": "pragmarx/google2fa", - "version": "v3.0.3", + "version": "v5.0.0", "source": { "type": "git", "url": "https://github.com/antonioribeiro/google2fa.git", - "reference": "6949226739e4424f40031e6f1c96b1fd64047335" + "reference": "17c969c82f427dd916afe4be50bafc6299aef1b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/antonioribeiro/google2fa/zipball/6949226739e4424f40031e6f1c96b1fd64047335", - "reference": "6949226739e4424f40031e6f1c96b1fd64047335", + "url": "https://api.github.com/repos/antonioribeiro/google2fa/zipball/17c969c82f427dd916afe4be50bafc6299aef1b4", + "reference": "17c969c82f427dd916afe4be50bafc6299aef1b4", "shasum": "" }, "require": { @@ -2693,12 +2719,8 @@ "symfony/polyfill-php56": "~1.2" }, "require-dev": { - "bacon/bacon-qr-code": "~1.0", "phpunit/phpunit": "~4|~5|~6" }, - "suggest": { - "bacon/bacon-qr-code": "Required to generate inline QR Codes." - }, "type": "library", "extra": { "component": "package", @@ -2719,8 +2741,8 @@ "authors": [ { "name": "Antonio Carlos Ribeiro", - "email": "acr@antoniocarlosribeiro.com", - "role": "Creator & Designer" + "role": "Creator & Designer", + "email": "acr@antoniocarlosribeiro.com" } ], "description": "A One Time Password Authentication package, compatible with Google Authenticator.", @@ -2728,32 +2750,31 @@ "2fa", "Authentication", "Two Factor Authentication", - "google2fa", - "laravel" + "google2fa" ], - "time": "2018-08-29T13:28:06+00:00" + "time": "2019-03-19T22:44:16+00:00" }, { "name": "pragmarx/google2fa-laravel", - "version": "v0.2.0", + "version": "v1.0.1", "source": { "type": "git", "url": "https://github.com/antonioribeiro/google2fa-laravel.git", - "reference": "54f0c30c9be5497a7bd248844f1099156457e719" + "reference": "b5f5bc71dcc52c48720441bc01c701023bd82882" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/antonioribeiro/google2fa-laravel/zipball/54f0c30c9be5497a7bd248844f1099156457e719", - "reference": "54f0c30c9be5497a7bd248844f1099156457e719", + "url": "https://api.github.com/repos/antonioribeiro/google2fa-laravel/zipball/b5f5bc71dcc52c48720441bc01c701023bd82882", + "reference": "b5f5bc71dcc52c48720441bc01c701023bd82882", "shasum": "" }, "require": { - "laravel/framework": ">=5.2", - "php": ">=5.4", - "pragmarx/google2fa": "~3.0" + "laravel/framework": ">=5.4.36", + "php": ">=7.0", + "pragmarx/google2fa-qrcode": "^1.0" }, "require-dev": { - "orchestra/testbench-browser-kit": "~3.4|~3.5|~3.6", + "orchestra/testbench": "3.4.*|3.5.*|3.6.*|3.7.*", "phpunit/phpunit": "~5|~6|~7" }, "suggest": { @@ -2791,8 +2812,8 @@ "authors": [ { "name": "Antonio Carlos Ribeiro", - "email": "acr@antoniocarlosribeiro.com", - "role": "Creator & Designer" + "role": "Creator & Designer", + "email": "acr@antoniocarlosribeiro.com" } ], "description": "A One Time Password Authentication package, compatible with Google Authenticator.", @@ -2802,7 +2823,192 @@ "google2fa", "laravel" ], - "time": "2018-03-08T04:08:14+00:00" + "time": "2019-03-22T19:54:51+00:00" + }, + { + "name": "pragmarx/google2fa-qrcode", + "version": "v1.0.3", + "source": { + "type": "git", + "url": "https://github.com/antonioribeiro/google2fa-qrcode.git", + "reference": "fd5ff0531a48b193a659309cc5fb882c14dbd03f" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/antonioribeiro/google2fa-qrcode/zipball/fd5ff0531a48b193a659309cc5fb882c14dbd03f", + "reference": "fd5ff0531a48b193a659309cc5fb882c14dbd03f", + "shasum": "" + }, + "require": { + "bacon/bacon-qr-code": "~1.0|~2.0", + "php": ">=5.4", + "pragmarx/google2fa": ">=4.0" + }, + "require-dev": { + "khanamiryan/qrcode-detector-decoder": "^1.0", + "phpunit/phpunit": "~4|~5|~6|~7" + }, + "type": "library", + "extra": { + "component": "package", + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "PragmaRX\\Google2FAQRCode\\": "src/", + "PragmaRX\\Google2FAQRCode\\Tests\\": "tests/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Antonio Carlos Ribeiro", + "role": "Creator & Designer", + "email": "acr@antoniocarlosribeiro.com" + } + ], + "description": "QR Code package for Google2FA", + "keywords": [ + "2fa", + "Authentication", + "Two Factor Authentication", + "google2fa", + "qr code", + "qrcode" + ], + "time": "2019-03-20T16:42:58+00:00" + }, + { + "name": "pragmarx/random", + "version": "v0.2.2", + "source": { + "type": "git", + "url": "https://github.com/antonioribeiro/random.git", + "reference": "daf08a189c5d2d40d1a827db46364d3a741a51b7" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/antonioribeiro/random/zipball/daf08a189c5d2d40d1a827db46364d3a741a51b7", + "reference": "daf08a189c5d2d40d1a827db46364d3a741a51b7", + "shasum": "" + }, + "require": { + "php": ">=7.0" + }, + "require-dev": { + "fzaninotto/faker": "~1.7", + "phpunit/phpunit": "~6.4", + "pragmarx/trivia": "~0.1", + "squizlabs/php_codesniffer": "^2.3" + }, + "suggest": { + "fzaninotto/faker": "Allows you to get dozens of randomized types", + "pragmarx/trivia": "For the trivia database" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "PragmaRX\\Random\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Antonio Carlos Ribeiro", + "role": "Developer", + "email": "acr@antoniocarlosribeiro.com", + "homepage": "https://antoniocarlosribeiro.com" + } + ], + "description": "Create random chars, numbers, strings", + "homepage": "https://github.com/antonioribeiro/random", + "keywords": [ + "Randomize", + "faker", + "pragmarx", + "random", + "random number", + "random pattern", + "random string" + ], + "time": "2017-11-21T05:26:22+00:00" + }, + { + "name": "pragmarx/recovery", + "version": "v0.1.0", + "source": { + "type": "git", + "url": "https://github.com/antonioribeiro/recovery.git", + "reference": "e16573a1ae5345cc3b100eec6d0296a1a15a90fe" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/antonioribeiro/recovery/zipball/e16573a1ae5345cc3b100eec6d0296a1a15a90fe", + "reference": "e16573a1ae5345cc3b100eec6d0296a1a15a90fe", + "shasum": "" + }, + "require": { + "php": "~7.0", + "pragmarx/random": "~0.1" + }, + "require-dev": { + "phpunit/phpunit": ">=5.4.3", + "squizlabs/php_codesniffer": "^2.3", + "tightenco/collect": "^5" + }, + "suggest": { + "tightenco/collect": "Allows to generate recovery codes as collections" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0-dev" + } + }, + "autoload": { + "psr-4": { + "PragmaRX\\Recovery\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Antonio Carlos Ribeiro", + "role": "Developer", + "email": "acr@antoniocarlosribeiro.com", + "homepage": "https://antoniocarlosribeiro.com" + } + ], + "description": "Create recovery codes for two factor auth", + "homepage": "https://github.com/antonioribeiro/recovery", + "keywords": [ + "2fa", + "account recovery", + "auth", + "backup codes", + "google2fa", + "pragmarx", + "recovery", + "recovery codes", + "two factor auth" + ], + "time": "2017-09-19T16:58:00+00:00" }, { "name": "psr/container", @@ -2853,6 +3059,58 @@ ], "time": "2017-02-14T16:28:37+00:00" }, + { + "name": "psr/http-factory", + "version": "1.0.1", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "http://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "time": "2019-04-30T12:38:16+00:00" + }, { "name": "psr/http-message", "version": "1.0.1", @@ -3000,24 +3258,24 @@ }, { "name": "ralouphie/getallheaders", - "version": "2.0.5", + "version": "3.0.3", "source": { "type": "git", "url": "https://github.com/ralouphie/getallheaders.git", - "reference": "5601c8a83fbba7ef674a7369456d12f1e0d0eafa" + "reference": "120b605dfeb996808c31b6477290a714d356e822" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/5601c8a83fbba7ef674a7369456d12f1e0d0eafa", - "reference": "5601c8a83fbba7ef674a7369456d12f1e0d0eafa", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", "shasum": "" }, "require": { - "php": ">=5.3" + "php": ">=5.6" }, "require-dev": { - "phpunit/phpunit": "~3.7.0", - "satooshi/php-coveralls": ">=1.0" + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" }, "type": "library", "autoload": { @@ -3036,7 +3294,7 @@ } ], "description": "A polyfill for getallheaders.", - "time": "2016-02-11T07:05:27+00:00" + "time": "2019-03-08T08:55:37+00:00" }, { "name": "ramsey/uuid", @@ -3194,16 +3452,16 @@ }, { "name": "swiftmailer/swiftmailer", - "version": "v6.2.0", + "version": "v6.2.1", "source": { "type": "git", "url": "https://github.com/swiftmailer/swiftmailer.git", - "reference": "6fa3232ff9d3f8237c0fae4b7ff05e1baa4cd707" + "reference": "5397cd05b0a0f7937c47b0adcb4c60e5ab936b6a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/6fa3232ff9d3f8237c0fae4b7ff05e1baa4cd707", - "reference": "6fa3232ff9d3f8237c0fae4b7ff05e1baa4cd707", + "url": "https://api.github.com/repos/swiftmailer/swiftmailer/zipball/5397cd05b0a0f7937c47b0adcb4c60e5ab936b6a", + "reference": "5397cd05b0a0f7937c47b0adcb4c60e5ab936b6a", "shasum": "" }, "require": { @@ -3252,29 +3510,31 @@ "mail", "mailer" ], - "time": "2019-03-10T07:52:41+00:00" + "time": "2019-04-21T09:21:45+00:00" }, { "name": "symfony/console", - "version": "v4.2.4", + "version": "v4.3.3", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "9dc2299a016497f9ee620be94524e6c0af0280a9" + "reference": "8b0ae5742ce9aaa8b0075665862c1ca397d1c1d9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/9dc2299a016497f9ee620be94524e6c0af0280a9", - "reference": "9dc2299a016497f9ee620be94524e6c0af0280a9", + "url": "https://api.github.com/repos/symfony/console/zipball/8b0ae5742ce9aaa8b0075665862c1ca397d1c1d9", + "reference": "8b0ae5742ce9aaa8b0075665862c1ca397d1c1d9", "shasum": "" }, "require": { "php": "^7.1.3", - "symfony/contracts": "^1.0", - "symfony/polyfill-mbstring": "~1.0" + "symfony/polyfill-mbstring": "~1.0", + "symfony/polyfill-php73": "^1.8", + "symfony/service-contracts": "^1.1" }, "conflict": { "symfony/dependency-injection": "<3.4", + "symfony/event-dispatcher": "<4.3", "symfony/process": "<3.3" }, "provide": { @@ -3284,9 +3544,10 @@ "psr/log": "~1.0", "symfony/config": "~3.4|~4.0", "symfony/dependency-injection": "~3.4|~4.0", - "symfony/event-dispatcher": "~3.4|~4.0", + "symfony/event-dispatcher": "^4.3", "symfony/lock": "~3.4|~4.0", - "symfony/process": "~3.4|~4.0" + "symfony/process": "~3.4|~4.0", + "symfony/var-dumper": "^4.3" }, "suggest": { "psr/log": "For using the console logger", @@ -3297,7 +3558,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.2-dev" + "dev-master": "4.3-dev" } }, "autoload": { @@ -3324,88 +3585,20 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2019-02-23T15:17:42+00:00" - }, - { - "name": "symfony/contracts", - "version": "v1.0.2", - "source": { - "type": "git", - "url": "https://github.com/symfony/contracts.git", - "reference": "1aa7ab2429c3d594dd70689604b5cf7421254cdf" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/contracts/zipball/1aa7ab2429c3d594dd70689604b5cf7421254cdf", - "reference": "1aa7ab2429c3d594dd70689604b5cf7421254cdf", - "shasum": "" - }, - "require": { - "php": "^7.1.3" - }, - "require-dev": { - "psr/cache": "^1.0", - "psr/container": "^1.0" - }, - "suggest": { - "psr/cache": "When using the Cache contracts", - "psr/container": "When using the Service contracts", - "symfony/cache-contracts-implementation": "", - "symfony/service-contracts-implementation": "", - "symfony/translation-contracts-implementation": "" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "psr-4": { - "Symfony\\Contracts\\": "" - }, - "exclude-from-classmap": [ - "**/Tests/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "A set of abstractions extracted out of the Symfony components", - "homepage": "https://symfony.com", - "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" - ], - "time": "2018-12-05T08:06:11+00:00" + "time": "2019-07-24T17:13:59+00:00" }, { "name": "symfony/css-selector", - "version": "v4.2.4", + "version": "v4.3.3", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "48eddf66950fa57996e1be4a55916d65c10c604a" + "reference": "105c98bb0c5d8635bea056135304bd8edcc42b4d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/48eddf66950fa57996e1be4a55916d65c10c604a", - "reference": "48eddf66950fa57996e1be4a55916d65c10c604a", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/105c98bb0c5d8635bea056135304bd8edcc42b4d", + "reference": "105c98bb0c5d8635bea056135304bd8edcc42b4d", "shasum": "" }, "require": { @@ -3414,7 +3607,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.2-dev" + "dev-master": "4.3-dev" } }, "autoload": { @@ -3430,14 +3623,14 @@ "MIT" ], "authors": [ - { - "name": "Jean-François Simon", - "email": "jeanfrancois.simon@sensiolabs.com" - }, { "name": "Fabien Potencier", "email": "fabien@symfony.com" }, + { + "name": "Jean-François Simon", + "email": "jeanfrancois.simon@sensiolabs.com" + }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" @@ -3445,20 +3638,20 @@ ], "description": "Symfony CssSelector Component", "homepage": "https://symfony.com", - "time": "2019-01-16T20:31:39+00:00" + "time": "2019-01-16T21:53:39+00:00" }, { "name": "symfony/debug", - "version": "v4.2.4", + "version": "v4.3.3", "source": { "type": "git", "url": "https://github.com/symfony/debug.git", - "reference": "de73f48977b8eaf7ce22814d66e43a1662cc864f" + "reference": "527887c3858a2462b0137662c74837288b998ee3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/debug/zipball/de73f48977b8eaf7ce22814d66e43a1662cc864f", - "reference": "de73f48977b8eaf7ce22814d66e43a1662cc864f", + "url": "https://api.github.com/repos/symfony/debug/zipball/527887c3858a2462b0137662c74837288b998ee3", + "reference": "527887c3858a2462b0137662c74837288b998ee3", "shasum": "" }, "require": { @@ -3474,7 +3667,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.2-dev" + "dev-master": "4.3-dev" } }, "autoload": { @@ -3501,34 +3694,40 @@ ], "description": "Symfony Debug Component", "homepage": "https://symfony.com", - "time": "2019-03-03T18:11:24+00:00" + "time": "2019-07-23T11:21:36+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v4.2.4", + "version": "v4.3.3", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "3354d2e6af986dd71f68b4e5cf4a933ab58697fb" + "reference": "212b020949331b6531250584531363844b34a94e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/3354d2e6af986dd71f68b4e5cf4a933ab58697fb", - "reference": "3354d2e6af986dd71f68b4e5cf4a933ab58697fb", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/212b020949331b6531250584531363844b34a94e", + "reference": "212b020949331b6531250584531363844b34a94e", "shasum": "" }, "require": { "php": "^7.1.3", - "symfony/contracts": "^1.0" + "symfony/event-dispatcher-contracts": "^1.1" }, "conflict": { "symfony/dependency-injection": "<3.4" }, + "provide": { + "psr/event-dispatcher-implementation": "1.0", + "symfony/event-dispatcher-implementation": "1.1" + }, "require-dev": { "psr/log": "~1.0", "symfony/config": "~3.4|~4.0", "symfony/dependency-injection": "~3.4|~4.0", "symfony/expression-language": "~3.4|~4.0", + "symfony/http-foundation": "^3.4|^4.0", + "symfony/service-contracts": "^1.1", "symfony/stopwatch": "~3.4|~4.0" }, "suggest": { @@ -3538,7 +3737,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.2-dev" + "dev-master": "4.3-dev" } }, "autoload": { @@ -3565,20 +3764,78 @@ ], "description": "Symfony EventDispatcher Component", "homepage": "https://symfony.com", - "time": "2019-02-23T15:17:42+00:00" + "time": "2019-06-27T06:42:14+00:00" }, { - "name": "symfony/finder", - "version": "v4.2.4", + "name": "symfony/event-dispatcher-contracts", + "version": "v1.1.5", "source": { "type": "git", - "url": "https://github.com/symfony/finder.git", - "reference": "267b7002c1b70ea80db0833c3afe05f0fbde580a" + "url": "https://github.com/symfony/event-dispatcher-contracts.git", + "reference": "c61766f4440ca687de1084a5c00b08e167a2575c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/267b7002c1b70ea80db0833c3afe05f0fbde580a", - "reference": "267b7002c1b70ea80db0833c3afe05f0fbde580a", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/c61766f4440ca687de1084a5c00b08e167a2575c", + "reference": "c61766f4440ca687de1084a5c00b08e167a2575c", + "shasum": "" + }, + "require": { + "php": "^7.1.3" + }, + "suggest": { + "psr/event-dispatcher": "", + "symfony/event-dispatcher-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\EventDispatcher\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to dispatching event", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "time": "2019-06-20T06:46:26+00:00" + }, + { + "name": "symfony/finder", + "version": "v4.3.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/finder.git", + "reference": "9638d41e3729459860bb96f6247ccb61faaa45f2" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/finder/zipball/9638d41e3729459860bb96f6247ccb61faaa45f2", + "reference": "9638d41e3729459860bb96f6247ccb61faaa45f2", "shasum": "" }, "require": { @@ -3587,7 +3844,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.2-dev" + "dev-master": "4.3-dev" } }, "autoload": { @@ -3614,24 +3871,25 @@ ], "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "time": "2019-02-23T15:42:05+00:00" + "time": "2019-06-28T13:16:30+00:00" }, { "name": "symfony/http-foundation", - "version": "v4.2.4", + "version": "v4.3.3", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "850a667d6254ccf6c61d853407b16f21c4579c77" + "reference": "8b778ee0c27731105fbf1535f51793ad1ae0ba2b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/850a667d6254ccf6c61d853407b16f21c4579c77", - "reference": "850a667d6254ccf6c61d853407b16f21c4579c77", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/8b778ee0c27731105fbf1535f51793ad1ae0ba2b", + "reference": "8b778ee0c27731105fbf1535f51793ad1ae0ba2b", "shasum": "" }, "require": { "php": "^7.1.3", + "symfony/mime": "^4.3", "symfony/polyfill-mbstring": "~1.1" }, "require-dev": { @@ -3641,7 +3899,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.2-dev" + "dev-master": "4.3-dev" } }, "autoload": { @@ -3668,34 +3926,35 @@ ], "description": "Symfony HttpFoundation Component", "homepage": "https://symfony.com", - "time": "2019-02-26T08:03:39+00:00" + "time": "2019-07-23T11:21:36+00:00" }, { "name": "symfony/http-kernel", - "version": "v4.2.4", + "version": "v4.3.3", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "895ceccaa8149f9343e6134e607c21da42d73b7a" + "reference": "a414548d236ddd8fa3df52367d583e82339c5e95" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/895ceccaa8149f9343e6134e607c21da42d73b7a", - "reference": "895ceccaa8149f9343e6134e607c21da42d73b7a", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/a414548d236ddd8fa3df52367d583e82339c5e95", + "reference": "a414548d236ddd8fa3df52367d583e82339c5e95", "shasum": "" }, "require": { "php": "^7.1.3", "psr/log": "~1.0", - "symfony/contracts": "^1.0.2", "symfony/debug": "~3.4|~4.0", - "symfony/event-dispatcher": "~4.1", + "symfony/event-dispatcher": "^4.3", "symfony/http-foundation": "^4.1.1", - "symfony/polyfill-ctype": "~1.8" + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-php73": "^1.9" }, "conflict": { + "symfony/browser-kit": "<4.3", "symfony/config": "<3.4", - "symfony/dependency-injection": "<4.2", + "symfony/dependency-injection": "<4.3", "symfony/translation": "<4.2", "symfony/var-dumper": "<4.1.1", "twig/twig": "<1.34|<2.4,>=2" @@ -3705,11 +3964,11 @@ }, "require-dev": { "psr/cache": "~1.0", - "symfony/browser-kit": "~3.4|~4.0", + "symfony/browser-kit": "^4.3", "symfony/config": "~3.4|~4.0", "symfony/console": "~3.4|~4.0", "symfony/css-selector": "~3.4|~4.0", - "symfony/dependency-injection": "^4.2", + "symfony/dependency-injection": "^4.3", "symfony/dom-crawler": "~3.4|~4.0", "symfony/expression-language": "~3.4|~4.0", "symfony/finder": "~3.4|~4.0", @@ -3718,7 +3977,9 @@ "symfony/stopwatch": "~3.4|~4.0", "symfony/templating": "~3.4|~4.0", "symfony/translation": "~4.2", - "symfony/var-dumper": "^4.1.1" + "symfony/translation-contracts": "^1.1", + "symfony/var-dumper": "^4.1.1", + "twig/twig": "^1.34|^2.4" }, "suggest": { "symfony/browser-kit": "", @@ -3730,7 +3991,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.2-dev" + "dev-master": "4.3-dev" } }, "autoload": { @@ -3757,20 +4018,79 @@ ], "description": "Symfony HttpKernel Component", "homepage": "https://symfony.com", - "time": "2019-03-03T19:38:09+00:00" + "time": "2019-07-28T07:10:23+00:00" }, { - "name": "symfony/polyfill-ctype", - "version": "v1.10.0", + "name": "symfony/mime", + "version": "v4.3.3", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "e3d826245268269cd66f8326bd8bc066687b4a19" + "url": "https://github.com/symfony/mime.git", + "reference": "6b7148029b1dd5eda1502064f06d01357b7b2d8b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/e3d826245268269cd66f8326bd8bc066687b4a19", - "reference": "e3d826245268269cd66f8326bd8bc066687b4a19", + "url": "https://api.github.com/repos/symfony/mime/zipball/6b7148029b1dd5eda1502064f06d01357b7b2d8b", + "reference": "6b7148029b1dd5eda1502064f06d01357b7b2d8b", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/polyfill-intl-idn": "^1.10", + "symfony/polyfill-mbstring": "^1.0" + }, + "require-dev": { + "egulias/email-validator": "^2.0", + "symfony/dependency-injection": "~3.4|^4.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.3-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Component\\Mime\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "A library to manipulate MIME messages", + "homepage": "https://symfony.com", + "keywords": [ + "mime", + "mime-type" + ], + "time": "2019-07-19T16:21:19+00:00" + }, + { + "name": "symfony/polyfill-ctype", + "version": "v1.12.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "550ebaac289296ce228a706d0867afc34687e3f4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/550ebaac289296ce228a706d0867afc34687e3f4", + "reference": "550ebaac289296ce228a706d0867afc34687e3f4", "shasum": "" }, "require": { @@ -3782,7 +4102,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.9-dev" + "dev-master": "1.12-dev" } }, "autoload": { @@ -3798,13 +4118,13 @@ "MIT" ], "authors": [ - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - }, { "name": "Gert de Pagter", "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], "description": "Symfony polyfill for ctype functions", @@ -3815,20 +4135,20 @@ "polyfill", "portable" ], - "time": "2018-08-06T14:22:27+00:00" + "time": "2019-08-06T08:03:45+00:00" }, { "name": "symfony/polyfill-iconv", - "version": "v1.10.0", + "version": "v1.12.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-iconv.git", - "reference": "97001cfc283484c9691769f51cdf25259037eba2" + "reference": "685968b11e61a347c18bf25db32effa478be610f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/97001cfc283484c9691769f51cdf25259037eba2", - "reference": "97001cfc283484c9691769f51cdf25259037eba2", + "url": "https://api.github.com/repos/symfony/polyfill-iconv/zipball/685968b11e61a347c18bf25db32effa478be610f", + "reference": "685968b11e61a347c18bf25db32effa478be610f", "shasum": "" }, "require": { @@ -3840,7 +4160,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.9-dev" + "dev-master": "1.12-dev" } }, "autoload": { @@ -3874,20 +4194,20 @@ "portable", "shim" ], - "time": "2018-09-21T06:26:08+00:00" + "time": "2019-08-06T08:03:45+00:00" }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.10.0", + "version": "v1.12.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "89de1d44f2c059b266f22c9cc9124ddc4cd0987a" + "reference": "6af626ae6fa37d396dc90a399c0ff08e5cfc45b2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/89de1d44f2c059b266f22c9cc9124ddc4cd0987a", - "reference": "89de1d44f2c059b266f22c9cc9124ddc4cd0987a", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/6af626ae6fa37d396dc90a399c0ff08e5cfc45b2", + "reference": "6af626ae6fa37d396dc90a399c0ff08e5cfc45b2", "shasum": "" }, "require": { @@ -3901,7 +4221,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.9-dev" + "dev-master": "1.12-dev" } }, "autoload": { @@ -3917,13 +4237,13 @@ "MIT" ], "authors": [ - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - }, { "name": "Laurent Bassin", "email": "laurent@bassin.info" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], "description": "Symfony polyfill for intl's idn_to_ascii and idn_to_utf8 functions", @@ -3936,20 +4256,20 @@ "portable", "shim" ], - "time": "2018-09-30T16:36:12+00:00" + "time": "2019-08-06T08:03:45+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.10.0", + "version": "v1.12.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "c79c051f5b3a46be09205c73b80b346e4153e494" + "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/c79c051f5b3a46be09205c73b80b346e4153e494", - "reference": "c79c051f5b3a46be09205c73b80b346e4153e494", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/b42a2f66e8f1b15ccf25652c3424265923eb4f17", + "reference": "b42a2f66e8f1b15ccf25652c3424265923eb4f17", "shasum": "" }, "require": { @@ -3961,7 +4281,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.9-dev" + "dev-master": "1.12-dev" } }, "autoload": { @@ -3995,20 +4315,20 @@ "portable", "shim" ], - "time": "2018-09-21T13:07:52+00:00" + "time": "2019-08-06T08:03:45+00:00" }, { "name": "symfony/polyfill-php56", - "version": "v1.10.0", + "version": "v1.12.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php56.git", - "reference": "ff208829fe1aa48ab9af356992bb7199fed551af" + "reference": "0e3b212e96a51338639d8ce175c046d7729c3403" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/ff208829fe1aa48ab9af356992bb7199fed551af", - "reference": "ff208829fe1aa48ab9af356992bb7199fed551af", + "url": "https://api.github.com/repos/symfony/polyfill-php56/zipball/0e3b212e96a51338639d8ce175c046d7729c3403", + "reference": "0e3b212e96a51338639d8ce175c046d7729c3403", "shasum": "" }, "require": { @@ -4018,7 +4338,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.9-dev" + "dev-master": "1.12-dev" } }, "autoload": { @@ -4051,20 +4371,20 @@ "portable", "shim" ], - "time": "2018-09-21T06:26:08+00:00" + "time": "2019-08-06T08:03:45+00:00" }, { "name": "symfony/polyfill-php72", - "version": "v1.10.0", + "version": "v1.12.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "9050816e2ca34a8e916c3a0ae8b9c2fccf68b631" + "reference": "04ce3335667451138df4307d6a9b61565560199e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/9050816e2ca34a8e916c3a0ae8b9c2fccf68b631", - "reference": "9050816e2ca34a8e916c3a0ae8b9c2fccf68b631", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/04ce3335667451138df4307d6a9b61565560199e", + "reference": "04ce3335667451138df4307d6a9b61565560199e", "shasum": "" }, "require": { @@ -4073,7 +4393,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.9-dev" + "dev-master": "1.12-dev" } }, "autoload": { @@ -4106,20 +4426,20 @@ "portable", "shim" ], - "time": "2018-09-21T13:07:52+00:00" + "time": "2019-08-06T08:03:45+00:00" }, { - "name": "symfony/polyfill-util", - "version": "v1.10.0", + "name": "symfony/polyfill-php73", + "version": "v1.12.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-util.git", - "reference": "3b58903eae668d348a7126f999b0da0f2f93611c" + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "2ceb49eaccb9352bff54d22570276bb75ba4a188" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-util/zipball/3b58903eae668d348a7126f999b0da0f2f93611c", - "reference": "3b58903eae668d348a7126f999b0da0f2f93611c", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/2ceb49eaccb9352bff54d22570276bb75ba4a188", + "reference": "2ceb49eaccb9352bff54d22570276bb75ba4a188", "shasum": "" }, "require": { @@ -4128,7 +4448,65 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.9-dev" + "dev-master": "1.12-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Polyfill\\Php73\\": "" + }, + "files": [ + "bootstrap.php" + ], + "classmap": [ + "Resources/stubs" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", + "homepage": "https://symfony.com", + "keywords": [ + "compatibility", + "polyfill", + "portable", + "shim" + ], + "time": "2019-08-06T08:03:45+00:00" + }, + { + "name": "symfony/polyfill-util", + "version": "v1.12.0", + "source": { + "type": "git", + "url": "https://github.com/symfony/polyfill-util.git", + "reference": "4317de1386717b4c22caed7725350a8887ab205c" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/polyfill-util/zipball/4317de1386717b4c22caed7725350a8887ab205c", + "reference": "4317de1386717b4c22caed7725350a8887ab205c", + "shasum": "" + }, + "require": { + "php": ">=5.3.3" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.12-dev" } }, "autoload": { @@ -4158,20 +4536,20 @@ "polyfill", "shim" ], - "time": "2018-09-30T16:36:12+00:00" + "time": "2019-08-06T08:03:45+00:00" }, { "name": "symfony/process", - "version": "v4.2.4", + "version": "v4.3.3", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "6c05edb11fbeff9e2b324b4270ecb17911a8b7ad" + "reference": "856d35814cf287480465bb7a6c413bb7f5f5e69c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/6c05edb11fbeff9e2b324b4270ecb17911a8b7ad", - "reference": "6c05edb11fbeff9e2b324b4270ecb17911a8b7ad", + "url": "https://api.github.com/repos/symfony/process/zipball/856d35814cf287480465bb7a6c413bb7f5f5e69c", + "reference": "856d35814cf287480465bb7a6c413bb7f5f5e69c", "shasum": "" }, "require": { @@ -4180,7 +4558,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.2-dev" + "dev-master": "4.3-dev" } }, "autoload": { @@ -4207,7 +4585,7 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2019-01-24T22:05:03+00:00" + "time": "2019-05-30T16:10:05+00:00" }, { "name": "symfony/psr-http-message-bridge", @@ -4276,16 +4654,16 @@ }, { "name": "symfony/routing", - "version": "v4.2.4", + "version": "v4.3.3", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "ff03eae644e6b1e26d4a04b2385fe3a1a7f04e42" + "reference": "a88c47a5861549f5dc1197660818084c3b67d773" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/ff03eae644e6b1e26d4a04b2385fe3a1a7f04e42", - "reference": "ff03eae644e6b1e26d4a04b2385fe3a1a7f04e42", + "url": "https://api.github.com/repos/symfony/routing/zipball/a88c47a5861549f5dc1197660818084c3b67d773", + "reference": "a88c47a5861549f5dc1197660818084c3b67d773", "shasum": "" }, "require": { @@ -4297,7 +4675,7 @@ "symfony/yaml": "<3.4" }, "require-dev": { - "doctrine/annotations": "~1.0", + "doctrine/annotations": "~1.2", "psr/log": "~1.0", "symfony/config": "~4.2", "symfony/dependency-injection": "~3.4|~4.0", @@ -4308,7 +4686,6 @@ "suggest": { "doctrine/annotations": "For using the annotation loader", "symfony/config": "For using the all-in-one router or any loader", - "symfony/dependency-injection": "For loading routes from a service", "symfony/expression-language": "For using expression matching", "symfony/http-foundation": "For using a Symfony Request object", "symfony/yaml": "For using the YAML loader" @@ -4316,7 +4693,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.2-dev" + "dev-master": "4.3-dev" } }, "autoload": { @@ -4349,26 +4726,84 @@ "uri", "url" ], - "time": "2019-02-23T15:17:42+00:00" + "time": "2019-07-23T14:43:56+00:00" }, { - "name": "symfony/translation", - "version": "v4.2.4", + "name": "symfony/service-contracts", + "version": "v1.1.5", "source": { "type": "git", - "url": "https://github.com/symfony/translation.git", - "reference": "748464177a77011f8f4cdd076773862ce4915f8f" + "url": "https://github.com/symfony/service-contracts.git", + "reference": "f391a00de78ec7ec8cf5cdcdae59ec7b883edb8d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/748464177a77011f8f4cdd076773862ce4915f8f", - "reference": "748464177a77011f8f4cdd076773862ce4915f8f", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/f391a00de78ec7ec8cf5cdcdae59ec7b883edb8d", + "reference": "f391a00de78ec7ec8cf5cdcdae59ec7b883edb8d", "shasum": "" }, "require": { "php": "^7.1.3", - "symfony/contracts": "^1.0.2", - "symfony/polyfill-mbstring": "~1.0" + "psr/container": "^1.0" + }, + "suggest": { + "symfony/service-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Service\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "time": "2019-06-13T11:15:36+00:00" + }, + { + "name": "symfony/translation", + "version": "v4.3.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/translation.git", + "reference": "4e3e39cc485304f807622bdc64938e4633396406" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/translation/zipball/4e3e39cc485304f807622bdc64938e4633396406", + "reference": "4e3e39cc485304f807622bdc64938e4633396406", + "shasum": "" + }, + "require": { + "php": "^7.1.3", + "symfony/polyfill-mbstring": "~1.0", + "symfony/translation-contracts": "^1.1.2" }, "conflict": { "symfony/config": "<3.4", @@ -4376,7 +4811,7 @@ "symfony/yaml": "<3.4" }, "provide": { - "symfony/translation-contracts-implementation": "1.0" + "symfony/translation-implementation": "1.0" }, "require-dev": { "psr/log": "~1.0", @@ -4384,7 +4819,10 @@ "symfony/console": "~3.4|~4.0", "symfony/dependency-injection": "~3.4|~4.0", "symfony/finder": "~2.8|~3.0|~4.0", + "symfony/http-kernel": "~3.4|~4.0", "symfony/intl": "~3.4|~4.0", + "symfony/service-contracts": "^1.1.2", + "symfony/var-dumper": "~3.4|~4.0", "symfony/yaml": "~3.4|~4.0" }, "suggest": { @@ -4395,7 +4833,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.2-dev" + "dev-master": "4.3-dev" } }, "autoload": { @@ -4422,20 +4860,77 @@ ], "description": "Symfony Translation Component", "homepage": "https://symfony.com", - "time": "2019-02-27T03:31:50+00:00" + "time": "2019-07-18T10:34:59+00:00" }, { - "name": "symfony/var-dumper", - "version": "v4.2.4", + "name": "symfony/translation-contracts", + "version": "v1.1.5", "source": { "type": "git", - "url": "https://github.com/symfony/var-dumper.git", - "reference": "9f87189ac10b42edf7fb8edc846f1937c6d157cf" + "url": "https://github.com/symfony/translation-contracts.git", + "reference": "cb4b18ad7b92a26e83b65dde940fab78339e6f3c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/9f87189ac10b42edf7fb8edc846f1937c6d157cf", - "reference": "9f87189ac10b42edf7fb8edc846f1937c6d157cf", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/cb4b18ad7b92a26e83b65dde940fab78339e6f3c", + "reference": "cb4b18ad7b92a26e83b65dde940fab78339e6f3c", + "shasum": "" + }, + "require": { + "php": "^7.1.3" + }, + "suggest": { + "symfony/translation-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Translation\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to translation", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "time": "2019-06-13T11:15:36+00:00" + }, + { + "name": "symfony/var-dumper", + "version": "v4.3.3", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-dumper.git", + "reference": "e4110b992d2cbe198d7d3b244d079c1c58761d07" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/e4110b992d2cbe198d7d3b244d079c1c58761d07", + "reference": "e4110b992d2cbe198d7d3b244d079c1c58761d07", "shasum": "" }, "require": { @@ -4464,7 +4959,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.2-dev" + "dev-master": "4.3-dev" } }, "autoload": { @@ -4498,20 +4993,20 @@ "debug", "dump" ], - "time": "2019-02-23T15:17:42+00:00" + "time": "2019-07-27T06:42:46+00:00" }, { "name": "tightenco/collect", - "version": "v5.8.4", + "version": "v5.8.31", "source": { "type": "git", "url": "https://github.com/tightenco/collect.git", - "reference": "c77e52021fa9c04c6bf0c0d15aa61c7a4af27a1f" + "reference": "957e430c26c51b65c80acc87896023229dc05a21" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/tightenco/collect/zipball/c77e52021fa9c04c6bf0c0d15aa61c7a4af27a1f", - "reference": "c77e52021fa9c04c6bf0c0d15aa61c7a4af27a1f", + "url": "https://api.github.com/repos/tightenco/collect/zipball/957e430c26c51b65c80acc87896023229dc05a21", + "reference": "957e430c26c51b65c80acc87896023229dc05a21", "shasum": "" }, "require": { @@ -4548,7 +5043,7 @@ "collection", "laravel" ], - "time": "2019-03-12T16:57:57+00:00" + "time": "2019-07-30T19:16:47+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", @@ -4599,16 +5094,16 @@ }, { "name": "twig/twig", - "version": "v1.38.2", + "version": "v1.42.2", "source": { "type": "git", "url": "https://github.com/twigphp/Twig.git", - "reference": "874adbd9222f928f6998732b25b01b41dff15b0c" + "reference": "21707d6ebd05476854805e4f91b836531941bcd4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/twigphp/Twig/zipball/874adbd9222f928f6998732b25b01b41dff15b0c", - "reference": "874adbd9222f928f6998732b25b01b41dff15b0c", + "url": "https://api.github.com/repos/twigphp/Twig/zipball/21707d6ebd05476854805e4f91b836531941bcd4", + "reference": "21707d6ebd05476854805e4f91b836531941bcd4", "shasum": "" }, "require": { @@ -4618,12 +5113,12 @@ "require-dev": { "psr/container": "^1.0", "symfony/debug": "^2.7", - "symfony/phpunit-bridge": "^3.4.19|^4.1.8" + "symfony/phpunit-bridge": "^3.4.19|^4.1.8|^5.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.38-dev" + "dev-master": "1.42-dev" } }, "autoload": { @@ -4641,19 +5136,19 @@ "authors": [ { "name": "Fabien Potencier", + "role": "Lead Developer", "email": "fabien@symfony.com", - "homepage": "http://fabien.potencier.org", - "role": "Lead Developer" + "homepage": "http://fabien.potencier.org" }, { "name": "Armin Ronacher", - "email": "armin.ronacher@active-4.com", - "role": "Project Founder" + "role": "Project Founder", + "email": "armin.ronacher@active-4.com" }, { "name": "Twig Team", - "homepage": "https://twig.symfony.com/contributors", - "role": "Contributors" + "role": "Contributors", + "homepage": "https://twig.symfony.com/contributors" } ], "description": "Twig, the flexible, fast, and secure template language for PHP", @@ -4661,20 +5156,20 @@ "keywords": [ "templating" ], - "time": "2019-03-12T18:45:24+00:00" + "time": "2019-06-18T15:35:16+00:00" }, { "name": "vlucas/phpdotenv", - "version": "v3.3.3", + "version": "v3.4.0", "source": { "type": "git", "url": "https://github.com/vlucas/phpdotenv.git", - "reference": "dbcc609971dd9b55f48b8008b553d79fd372ddde" + "reference": "5084b23845c24dbff8ac6c204290c341e4776c92" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/dbcc609971dd9b55f48b8008b553d79fd372ddde", - "reference": "dbcc609971dd9b55f48b8008b553d79fd372ddde", + "url": "https://api.github.com/repos/vlucas/phpdotenv/zipball/5084b23845c24dbff8ac6c204290c341e4776c92", + "reference": "5084b23845c24dbff8ac6c204290c341e4776c92", "shasum": "" }, "require": { @@ -4688,7 +5183,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.3-dev" + "dev-master": "3.4-dev" } }, "autoload": { @@ -4713,42 +5208,45 @@ "env", "environment" ], - "time": "2019-03-06T09:39:45+00:00" + "time": "2019-06-15T22:40:20+00:00" }, { "name": "zendframework/zend-diactoros", - "version": "1.8.6", + "version": "2.1.3", "source": { "type": "git", "url": "https://github.com/zendframework/zend-diactoros.git", - "reference": "20da13beba0dde8fb648be3cc19765732790f46e" + "reference": "279723778c40164bcf984a2df12ff2c6ec5e61c1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zendframework/zend-diactoros/zipball/20da13beba0dde8fb648be3cc19765732790f46e", - "reference": "20da13beba0dde8fb648be3cc19765732790f46e", + "url": "https://api.github.com/repos/zendframework/zend-diactoros/zipball/279723778c40164bcf984a2df12ff2c6ec5e61c1", + "reference": "279723778c40164bcf984a2df12ff2c6ec5e61c1", "shasum": "" }, "require": { - "php": "^5.6 || ^7.0", + "php": "^7.1", + "psr/http-factory": "^1.0", "psr/http-message": "^1.0" }, "provide": { + "psr/http-factory-implementation": "1.0", "psr/http-message-implementation": "1.0" }, "require-dev": { "ext-dom": "*", "ext-libxml": "*", + "http-interop/http-factory-tests": "^0.5.0", "php-http/psr7-integration-tests": "dev-master", - "phpunit/phpunit": "^5.7.16 || ^6.0.8 || ^7.2.7", - "zendframework/zend-coding-standard": "~1.0" + "phpunit/phpunit": "^7.0.2", + "zendframework/zend-coding-standard": "~1.0.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.8.x-dev", - "dev-develop": "1.9.x-dev", - "dev-release-2.0": "2.0.x-dev" + "dev-master": "2.1.x-dev", + "dev-develop": "2.2.x-dev", + "dev-release-1.8": "1.8.x-dev" } }, "autoload": { @@ -4768,31 +5266,30 @@ }, "notification-url": "https://packagist.org/downloads/", "license": [ - "BSD-2-Clause" + "BSD-3-Clause" ], "description": "PSR HTTP Message implementations", - "homepage": "https://github.com/zendframework/zend-diactoros", "keywords": [ "http", "psr", "psr-7" ], - "time": "2018-09-05T19:29:37+00:00" + "time": "2019-07-10T16:13:25+00:00" } ], "packages-dev": [ { "name": "barryvdh/laravel-ide-helper", - "version": "v2.6.1", + "version": "v2.6.2", "source": { "type": "git", "url": "https://github.com/barryvdh/laravel-ide-helper.git", - "reference": "725711022be71c6fa2e7bc8f9648bf125986f027" + "reference": "39c148ad4273f5b8c49d0a363ddbc0462f1f2eec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/725711022be71c6fa2e7bc8f9648bf125986f027", - "reference": "725711022be71c6fa2e7bc8f9648bf125986f027", + "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/39c148ad4273f5b8c49d0a363ddbc0462f1f2eec", + "reference": "39c148ad4273f5b8c49d0a363ddbc0462f1f2eec", "shasum": "" }, "require": { @@ -4853,7 +5350,7 @@ "phpstorm", "sublime" ], - "time": "2019-03-05T09:24:51+00:00" + "time": "2019-03-26T10:38:22+00:00" }, { "name": "barryvdh/reflection-docblock", @@ -4906,25 +5403,25 @@ }, { "name": "composer/ca-bundle", - "version": "1.1.4", + "version": "1.2.3", "source": { "type": "git", "url": "https://github.com/composer/ca-bundle.git", - "reference": "558f321c52faeb4828c03e7dc0cfe39a09e09a2d" + "reference": "f26a67e397be0e5c00d7c52ec7b5010098e15ce5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/ca-bundle/zipball/558f321c52faeb4828c03e7dc0cfe39a09e09a2d", - "reference": "558f321c52faeb4828c03e7dc0cfe39a09e09a2d", + "url": "https://api.github.com/repos/composer/ca-bundle/zipball/f26a67e397be0e5c00d7c52ec7b5010098e15ce5", + "reference": "f26a67e397be0e5c00d7c52ec7b5010098e15ce5", "shasum": "" }, "require": { "ext-openssl": "*", "ext-pcre": "*", - "php": "^5.3.2 || ^7.0" + "php": "^5.3.2 || ^7.0 || ^8.0" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5", + "phpunit/phpunit": "^4.8.35 || ^5.7 || 6.5 - 8", "psr/log": "^1.0", "symfony/process": "^2.5 || ^3.0 || ^4.0" }, @@ -4958,20 +5455,20 @@ "ssl", "tls" ], - "time": "2019-01-28T09:30:10+00:00" + "time": "2019-08-02T09:05:43+00:00" }, { "name": "composer/composer", - "version": "1.8.4", + "version": "1.9.0", "source": { "type": "git", "url": "https://github.com/composer/composer.git", - "reference": "bc364c2480c17941e2135cfc568fa41794392534" + "reference": "314aa57fdcfc942065996f59fb73a8b3f74f3fa5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/composer/zipball/bc364c2480c17941e2135cfc568fa41794392534", - "reference": "bc364c2480c17941e2135cfc568fa41794392534", + "url": "https://api.github.com/repos/composer/composer/zipball/314aa57fdcfc942065996f59fb73a8b3f74f3fa5", + "reference": "314aa57fdcfc942065996f59fb73a8b3f74f3fa5", "shasum": "" }, "require": { @@ -5007,7 +5504,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.8-dev" + "dev-master": "1.9-dev" } }, "autoload": { @@ -5031,27 +5528,27 @@ "homepage": "http://seld.be" } ], - "description": "Composer helps you declare, manage and install dependencies of PHP projects, ensuring you have the right stack everywhere.", + "description": "Composer helps you declare, manage and install dependencies of PHP projects. It ensures you have the right stack everywhere.", "homepage": "https://getcomposer.org/", "keywords": [ "autoload", "dependency", "package" ], - "time": "2019-02-11T09:52:10+00:00" + "time": "2019-08-02T18:55:33+00:00" }, { "name": "composer/semver", - "version": "1.4.2", + "version": "1.5.0", "source": { "type": "git", "url": "https://github.com/composer/semver.git", - "reference": "c7cb9a2095a074d131b65a8a0cd294479d785573" + "reference": "46d9139568ccb8d9e7cdd4539cab7347568a5e2e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/c7cb9a2095a074d131b65a8a0cd294479d785573", - "reference": "c7cb9a2095a074d131b65a8a0cd294479d785573", + "url": "https://api.github.com/repos/composer/semver/zipball/46d9139568ccb8d9e7cdd4539cab7347568a5e2e", + "reference": "46d9139568ccb8d9e7cdd4539cab7347568a5e2e", "shasum": "" }, "require": { @@ -5100,28 +5597,27 @@ "validation", "versioning" ], - "time": "2016-08-30T16:08:34+00:00" + "time": "2019-03-19T17:25:45+00:00" }, { "name": "composer/spdx-licenses", - "version": "1.5.0", + "version": "1.5.2", "source": { "type": "git", "url": "https://github.com/composer/spdx-licenses.git", - "reference": "7a9556b22bd9d4df7cad89876b00af58ef20d3a2" + "reference": "7ac1e6aec371357df067f8a688c3d6974df68fa5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/7a9556b22bd9d4df7cad89876b00af58ef20d3a2", - "reference": "7a9556b22bd9d4df7cad89876b00af58ef20d3a2", + "url": "https://api.github.com/repos/composer/spdx-licenses/zipball/7ac1e6aec371357df067f8a688c3d6974df68fa5", + "reference": "7ac1e6aec371357df067f8a688c3d6974df68fa5", "shasum": "" }, "require": { - "php": "^5.3.2 || ^7.0" + "php": "^5.3.2 || ^7.0 || ^8.0" }, "require-dev": { - "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5", - "phpunit/phpunit-mock-objects": "2.3.0 || ^3.0" + "phpunit/phpunit": "^4.8.35 || ^5.7 || 6.5 - 7" }, "type": "library", "extra": { @@ -5161,20 +5657,20 @@ "spdx", "validator" ], - "time": "2018-11-01T09:45:54+00:00" + "time": "2019-07-29T10:31:59+00:00" }, { "name": "composer/xdebug-handler", - "version": "1.3.2", + "version": "1.3.3", "source": { "type": "git", "url": "https://github.com/composer/xdebug-handler.git", - "reference": "d17708133b6c276d6e42ef887a877866b909d892" + "reference": "46867cbf8ca9fb8d60c506895449eb799db1184f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/d17708133b6c276d6e42ef887a877866b909d892", - "reference": "d17708133b6c276d6e42ef887a877866b909d892", + "url": "https://api.github.com/repos/composer/xdebug-handler/zipball/46867cbf8ca9fb8d60c506895449eb799db1184f", + "reference": "46867cbf8ca9fb8d60c506895449eb799db1184f", "shasum": "" }, "require": { @@ -5205,31 +5701,33 @@ "Xdebug", "performance" ], - "time": "2019-01-28T20:25:53+00:00" + "time": "2019-05-27T17:52:04+00:00" }, { "name": "doctrine/instantiator", - "version": "1.1.0", + "version": "1.2.0", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda" + "reference": "a2c590166b2133a4633738648b6b064edae0814a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda", - "reference": "185b8868aa9bf7159f5f953ed5afb2d7fcdc3bda", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/a2c590166b2133a4633738648b6b064edae0814a", + "reference": "a2c590166b2133a4633738648b6b064edae0814a", "shasum": "" }, "require": { "php": "^7.1" }, "require-dev": { - "athletic/athletic": "~0.1.8", + "doctrine/coding-standard": "^6.0", "ext-pdo": "*", "ext-phar": "*", - "phpunit/phpunit": "^6.2.3", - "squizlabs/php_codesniffer": "^3.0.2" + "phpbench/phpbench": "^0.13", + "phpstan/phpstan-phpunit": "^0.11", + "phpstan/phpstan-shim": "^0.11", + "phpunit/phpunit": "^7.0" }, "type": "library", "extra": { @@ -5254,25 +5752,25 @@ } ], "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", - "homepage": "https://github.com/doctrine/instantiator", + "homepage": "https://www.doctrine-project.org/projects/instantiator.html", "keywords": [ "constructor", "instantiate" ], - "time": "2017-07-22T11:58:36+00:00" + "time": "2019-03-17T17:37:11+00:00" }, { "name": "filp/whoops", - "version": "2.3.1", + "version": "2.5.0", "source": { "type": "git", "url": "https://github.com/filp/whoops.git", - "reference": "bc0fd11bc455cc20ee4b5edabc63ebbf859324c7" + "reference": "cde50e6720a39fdacb240159d3eea6865d51fd96" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/filp/whoops/zipball/bc0fd11bc455cc20ee4b5edabc63ebbf859324c7", - "reference": "bc0fd11bc455cc20ee4b5edabc63ebbf859324c7", + "url": "https://api.github.com/repos/filp/whoops/zipball/cde50e6720a39fdacb240159d3eea6865d51fd96", + "reference": "cde50e6720a39fdacb240159d3eea6865d51fd96", "shasum": "" }, "require": { @@ -5306,8 +5804,8 @@ "authors": [ { "name": "Filipe Dobreira", - "homepage": "https://github.com/filp", - "role": "Developer" + "role": "Developer", + "homepage": "https://github.com/filp" } ], "description": "php error handling for cool kids", @@ -5320,7 +5818,7 @@ "throwable", "whoops" ], - "time": "2018-10-23T09:00:00+00:00" + "time": "2019-08-07T09:00:00+00:00" }, { "name": "fzaninotto/faker", @@ -5536,16 +6034,16 @@ }, { "name": "mockery/mockery", - "version": "1.2.2", + "version": "1.2.3", "source": { "type": "git", "url": "https://github.com/mockery/mockery.git", - "reference": "0eb0b48c3f07b3b89f5169ce005b7d05b18cf1d2" + "reference": "4eff936d83eb809bde2c57a3cea0ee9643769031" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/mockery/mockery/zipball/0eb0b48c3f07b3b89f5169ce005b7d05b18cf1d2", - "reference": "0eb0b48c3f07b3b89f5169ce005b7d05b18cf1d2", + "url": "https://api.github.com/repos/mockery/mockery/zipball/4eff936d83eb809bde2c57a3cea0ee9643769031", + "reference": "4eff936d83eb809bde2c57a3cea0ee9643769031", "shasum": "" }, "require": { @@ -5597,20 +6095,20 @@ "test double", "testing" ], - "time": "2019-02-13T09:37:52+00:00" + "time": "2019-08-07T15:01:07+00:00" }, { "name": "myclabs/deep-copy", - "version": "1.8.1", + "version": "1.9.1", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "3e01bdad3e18354c3dce54466b7fbe33a9f9f7f8" + "reference": "e6828efaba2c9b79f4499dae1d66ef8bfa7b2b72" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3e01bdad3e18354c3dce54466b7fbe33a9f9f7f8", - "reference": "3e01bdad3e18354c3dce54466b7fbe33a9f9f7f8", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/e6828efaba2c9b79f4499dae1d66ef8bfa7b2b72", + "reference": "e6828efaba2c9b79f4499dae1d66ef8bfa7b2b72", "shasum": "" }, "require": { @@ -5645,7 +6143,7 @@ "object", "object graph" ], - "time": "2018-06-11T23:09:50+00:00" + "time": "2019-04-07T13:18:21+00:00" }, { "name": "phar-io/manifest", @@ -5685,18 +6183,18 @@ "authors": [ { "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" + "role": "Developer", + "email": "arne@blankerts.de" }, { "name": "Sebastian Heuer", - "email": "sebastian@phpeople.de", - "role": "Developer" + "role": "Developer", + "email": "sebastian@phpeople.de" }, { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "Developer" + "role": "Developer", + "email": "sebastian@phpunit.de" } ], "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", @@ -5732,18 +6230,18 @@ "authors": [ { "name": "Arne Blankerts", - "email": "arne@blankerts.de", - "role": "Developer" + "role": "Developer", + "email": "arne@blankerts.de" }, { "name": "Sebastian Heuer", - "email": "sebastian@phpeople.de", - "role": "Developer" + "role": "Developer", + "email": "sebastian@phpeople.de" }, { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "Developer" + "role": "Developer", + "email": "sebastian@phpunit.de" } ], "description": "Library for handling version information and constraints", @@ -5805,16 +6303,16 @@ }, { "name": "phpdocumentor/reflection-docblock", - "version": "4.3.0", + "version": "4.3.1", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "94fd0001232e47129dd3504189fa1c7225010d08" + "reference": "bdd9f737ebc2a01c06ea7ff4308ec6697db9b53c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/94fd0001232e47129dd3504189fa1c7225010d08", - "reference": "94fd0001232e47129dd3504189fa1c7225010d08", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/bdd9f737ebc2a01c06ea7ff4308ec6697db9b53c", + "reference": "bdd9f737ebc2a01c06ea7ff4308ec6697db9b53c", "shasum": "" }, "require": { @@ -5852,7 +6350,7 @@ } ], "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", - "time": "2017-11-30T07:14:17+00:00" + "time": "2019-04-30T17:48:53+00:00" }, { "name": "phpdocumentor/type-resolver", @@ -5903,16 +6401,16 @@ }, { "name": "phpspec/prophecy", - "version": "1.8.0", + "version": "1.8.1", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06" + "reference": "1927e75f4ed19131ec9bcc3b002e07fb1173ee76" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/4ba436b55987b4bf311cb7c6ba82aa528aac0a06", - "reference": "4ba436b55987b4bf311cb7c6ba82aa528aac0a06", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/1927e75f4ed19131ec9bcc3b002e07fb1173ee76", + "reference": "1927e75f4ed19131ec9bcc3b002e07fb1173ee76", "shasum": "" }, "require": { @@ -5933,8 +6431,8 @@ } }, "autoload": { - "psr-0": { - "Prophecy\\": "src/" + "psr-4": { + "Prophecy\\": "src/Prophecy" } }, "notification-url": "https://packagist.org/downloads/", @@ -5962,20 +6460,20 @@ "spy", "stub" ], - "time": "2018-08-05T17:53:17+00:00" + "time": "2019-06-13T12:50:23+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "7.0.3", + "version": "7.0.7", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "0317a769a81845c390e19684d9ba25d7f6aa4707" + "reference": "7743bbcfff2a907e9ee4a25be13d0f8ec5e73800" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/0317a769a81845c390e19684d9ba25d7f6aa4707", - "reference": "0317a769a81845c390e19684d9ba25d7f6aa4707", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/7743bbcfff2a907e9ee4a25be13d0f8ec5e73800", + "reference": "7743bbcfff2a907e9ee4a25be13d0f8ec5e73800", "shasum": "" }, "require": { @@ -5984,17 +6482,17 @@ "php": "^7.2", "phpunit/php-file-iterator": "^2.0.2", "phpunit/php-text-template": "^1.2.1", - "phpunit/php-token-stream": "^3.0.1", + "phpunit/php-token-stream": "^3.1.0", "sebastian/code-unit-reverse-lookup": "^1.0.1", - "sebastian/environment": "^4.1", + "sebastian/environment": "^4.2.2", "sebastian/version": "^2.0.1", - "theseer/tokenizer": "^1.1" + "theseer/tokenizer": "^1.1.3" }, "require-dev": { - "phpunit/phpunit": "^8.0" + "phpunit/phpunit": "^8.2.2" }, "suggest": { - "ext-xdebug": "^2.6.1" + "ext-xdebug": "^2.7.2" }, "type": "library", "extra": { @@ -6014,8 +6512,8 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "role": "lead", + "email": "sebastian@phpunit.de" } ], "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", @@ -6025,7 +6523,7 @@ "testing", "xunit" ], - "time": "2019-02-26T07:38:26+00:00" + "time": "2019-07-25T05:31:54+00:00" }, { "name": "phpunit/php-file-iterator", @@ -6065,8 +6563,8 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "role": "lead", + "email": "sebastian@phpunit.de" } ], "description": "FilterIterator implementation that filters files based on a list of suffixes.", @@ -6107,8 +6605,8 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "role": "lead", + "email": "sebastian@phpunit.de" } ], "description": "Simple template engine.", @@ -6120,16 +6618,16 @@ }, { "name": "phpunit/php-timer", - "version": "2.1.1", + "version": "2.1.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "8b389aebe1b8b0578430bda0c7c95a829608e059" + "reference": "1038454804406b0b5f5f520358e78c1c2f71501e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/8b389aebe1b8b0578430bda0c7c95a829608e059", - "reference": "8b389aebe1b8b0578430bda0c7c95a829608e059", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/1038454804406b0b5f5f520358e78c1c2f71501e", + "reference": "1038454804406b0b5f5f520358e78c1c2f71501e", "shasum": "" }, "require": { @@ -6156,8 +6654,8 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "role": "lead", + "email": "sebastian@phpunit.de" } ], "description": "Utility class for timing", @@ -6165,20 +6663,20 @@ "keywords": [ "timer" ], - "time": "2019-02-20T10:12:59+00:00" + "time": "2019-06-07T04:22:29+00:00" }, { "name": "phpunit/php-token-stream", - "version": "3.0.1", + "version": "3.1.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-token-stream.git", - "reference": "c99e3be9d3e85f60646f152f9002d46ed7770d18" + "reference": "e899757bb3df5ff6e95089132f32cd59aac2220a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/c99e3be9d3e85f60646f152f9002d46ed7770d18", - "reference": "c99e3be9d3e85f60646f152f9002d46ed7770d18", + "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/e899757bb3df5ff6e95089132f32cd59aac2220a", + "reference": "e899757bb3df5ff6e95089132f32cd59aac2220a", "shasum": "" }, "require": { @@ -6191,7 +6689,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "3.0-dev" + "dev-master": "3.1-dev" } }, "autoload": { @@ -6214,46 +6712,47 @@ "keywords": [ "tokenizer" ], - "time": "2018-10-30T05:52:18+00:00" + "time": "2019-07-25T05:29:42+00:00" }, { "name": "phpunit/phpunit", - "version": "8.0.5", + "version": "8.3.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "19cbed2120839772c4a00e8b28456b0c77d1a7b4" + "reference": "c319d08ebd31e137034c84ad7339054709491485" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/19cbed2120839772c4a00e8b28456b0c77d1a7b4", - "reference": "19cbed2120839772c4a00e8b28456b0c77d1a7b4", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c319d08ebd31e137034c84ad7339054709491485", + "reference": "c319d08ebd31e137034c84ad7339054709491485", "shasum": "" }, "require": { - "doctrine/instantiator": "^1.1", + "doctrine/instantiator": "^1.2.0", "ext-dom": "*", "ext-json": "*", "ext-libxml": "*", "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.7", - "phar-io/manifest": "^1.0.2", - "phar-io/version": "^2.0", + "myclabs/deep-copy": "^1.9.1", + "phar-io/manifest": "^1.0.3", + "phar-io/version": "^2.0.1", "php": "^7.2", - "phpspec/prophecy": "^1.7", - "phpunit/php-code-coverage": "^7.0", - "phpunit/php-file-iterator": "^2.0.1", + "phpspec/prophecy": "^1.8.1", + "phpunit/php-code-coverage": "^7.0.7", + "phpunit/php-file-iterator": "^2.0.2", "phpunit/php-text-template": "^1.2.1", - "phpunit/php-timer": "^2.1", - "sebastian/comparator": "^3.0", - "sebastian/diff": "^3.0", - "sebastian/environment": "^4.1", - "sebastian/exporter": "^3.1", - "sebastian/global-state": "^3.0", + "phpunit/php-timer": "^2.1.2", + "sebastian/comparator": "^3.0.2", + "sebastian/diff": "^3.0.2", + "sebastian/environment": "^4.2.2", + "sebastian/exporter": "^3.1.0", + "sebastian/global-state": "^3.0.0", "sebastian/object-enumerator": "^3.0.3", - "sebastian/resource-operations": "^2.0", + "sebastian/resource-operations": "^2.0.1", + "sebastian/type": "^1.1.3", "sebastian/version": "^2.0.1" }, "require-dev": { @@ -6262,7 +6761,7 @@ "suggest": { "ext-soap": "*", "ext-xdebug": "*", - "phpunit/php-invoker": "^2.0" + "phpunit/php-invoker": "^2.0.0" }, "bin": [ "phpunit" @@ -6270,7 +6769,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "8.0-dev" + "dev-master": "8.3-dev" } }, "autoload": { @@ -6285,8 +6784,8 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "role": "lead", + "email": "sebastian@phpunit.de" } ], "description": "The PHP Unit Testing framework.", @@ -6296,7 +6795,7 @@ "testing", "xunit" ], - "time": "2019-03-16T07:33:46+00:00" + "time": "2019-08-03T15:41:47+00:00" }, { "name": "roave/security-advisories", @@ -6304,12 +6803,12 @@ "source": { "type": "git", "url": "https://github.com/Roave/SecurityAdvisories.git", - "reference": "3521da8036ce31b11490433aaae47f9601774191" + "reference": "ea693fa060702164985511acc3ceb5389c9ac761" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/3521da8036ce31b11490433aaae47f9601774191", - "reference": "3521da8036ce31b11490433aaae47f9601774191", + "url": "https://api.github.com/repos/Roave/SecurityAdvisories/zipball/ea693fa060702164985511acc3ceb5389c9ac761", + "reference": "ea693fa060702164985511acc3ceb5389c9ac761", "shasum": "" }, "conflict": { @@ -6323,14 +6822,14 @@ "aws/aws-sdk-php": ">=3,<3.2.1", "brightlocal/phpwhois": "<=4.2.5", "bugsnag/bugsnag-laravel": ">=2,<2.0.2", - "cakephp/cakephp": ">=1.3,<1.3.18|>=2,<2.4.99|>=2.5,<2.5.99|>=2.6,<2.6.12|>=2.7,<2.7.6|>=3,<3.0.15|>=3.1,<3.1.4|>=3.4,<3.4.14|>=3.5,<3.5.17|>=3.6,<3.6.4", + "cakephp/cakephp": ">=1.3,<1.3.18|>=2,<2.4.99|>=2.5,<2.5.99|>=2.6,<2.6.12|>=2.7,<2.7.6|>=3,<3.5.18|>=3.6,<3.6.15|>=3.7,<3.7.7", "cart2quote/module-quotation": ">=4.1.6,<=4.4.5|>=5,<5.4.4", "cartalyst/sentry": "<=2.1.6", "codeigniter/framework": "<=3.0.6", - "composer/composer": "<=1.0.0-alpha11", + "composer/composer": "<=1-alpha.11", "contao-components/mediaelement": ">=2.14.2,<2.21.1", - "contao/core": ">=2,<3.5.35", - "contao/core-bundle": ">=4,<4.4.18|>=4.5,<4.5.8", + "contao/core": ">=2,<3.5.39", + "contao/core-bundle": ">=4,<4.4.39|>=4.5,<4.7.5", "contao/listing-bundle": ">=4,<4.4.8", "contao/newsletter-bundle": ">=4,<4.1", "david-garcia/phpwhois": "<=4.3.1", @@ -6344,9 +6843,10 @@ "doctrine/mongodb-odm-bundle": ">=2,<3.0.1", "doctrine/orm": ">=2,<2.4.8|>=2.5,<2.5.1", "dompdf/dompdf": ">=0.6,<0.6.2", - "drupal/core": ">=7,<7.62|>=8,<8.5.11|>=8.6,<8.6.10", - "drupal/drupal": ">=7,<7.62|>=8,<8.5.11|>=8.6,<8.6.10", - "erusev/parsedown": "<1.7", + "drupal/core": ">=7,<7.67|>=8,<8.6.16|>=8.7,<8.7.1|>8.7.3,<8.7.5", + "drupal/drupal": ">=7,<7.67|>=8,<8.6.16|>=8.7,<8.7.1|>8.7.3,<8.7.5", + "erusev/parsedown": "<1.7.2", + "ezsystems/ezplatform-admin-ui": ">=1.3,<1.3.5|>=1.4,<1.4.4", "ezsystems/ezpublish-kernel": ">=5.3,<5.3.12.1|>=5.4,<5.4.13.1|>=6,<6.7.9.1|>=6.8,<6.13.5.1|>=7,<7.2.4.1|>=7.3,<7.3.2.1", "ezsystems/ezpublish-legacy": ">=5.3,<5.3.12.6|>=5.4,<5.4.12.3|>=2011,<2017.12.4.3|>=2018.6,<2018.6.1.4|>=2018.9,<2018.9.1.3", "ezsystems/repository-forms": ">=2.3,<2.3.2.1", @@ -6359,7 +6859,7 @@ "fuel/core": "<1.8.1", "gree/jose": "<=2.2", "gregwar/rst": "<1.0.3", - "guzzlehttp/guzzle": ">=6,<6.2.1|>=4.0.0-rc2,<4.2.4|>=5,<5.3.1", + "guzzlehttp/guzzle": ">=4-rc.2,<4.2.4|>=5,<5.3.1|>=6,<6.2.1", "illuminate/auth": ">=4,<4.0.99|>=4.1,<=4.1.31|>=4.2,<=4.2.22|>=5,<=5.0.35|>=5.1,<=5.1.46|>=5.2,<=5.2.45|>=5.3,<=5.3.31|>=5.4,<=5.4.36|>=5.5,<5.5.10", "illuminate/cookie": ">=4,<=4.0.11|>=4.1,<=4.1.31|>=4.2,<=4.2.22|>=5,<=5.0.35|>=5.1,<=5.1.46|>=5.2,<=5.2.45|>=5.3,<=5.3.31|>=5.4,<=5.4.36|>=5.5,<5.5.42|>=5.6,<5.6.30", "illuminate/database": ">=4,<4.0.99|>=4.1,<4.1.29", @@ -6373,10 +6873,10 @@ "la-haute-societe/tcpdf": "<6.2.22", "laravel/framework": ">=4,<4.0.99|>=4.1,<=4.1.31|>=4.2,<=4.2.22|>=5,<=5.0.35|>=5.1,<=5.1.46|>=5.2,<=5.2.45|>=5.3,<=5.3.31|>=5.4,<=5.4.36|>=5.5,<5.5.42|>=5.6,<5.6.30", "laravel/socialite": ">=1,<1.0.99|>=2,<2.0.10", - "league/commonmark": ">=0.15.6,<0.18.1", - "magento/magento1ce": "<1.9.4", - "magento/magento1ee": ">=1.9,<1.14.4", - "magento/product-community-edition": ">=2,<2.2.7", + "league/commonmark": "<0.18.3", + "magento/magento1ce": "<1.9.4.1", + "magento/magento1ee": ">=1.9,<1.14.4.1", + "magento/product-community-edition": ">=2,<2.2.8|>=2.3,<2.3.1", "monolog/monolog": ">=1.8,<1.12", "namshi/jose": "<2.2", "onelogin/php-saml": "<2.10.4", @@ -6394,7 +6894,7 @@ "phpunit/phpunit": ">=4.8.19,<4.8.28|>=5.0.10,<5.6.3", "phpwhois/phpwhois": "<=4.2.5", "phpxmlrpc/extras": "<0.6.1", - "propel/propel": ">=2.0.0-alpha1,<=2.0.0-alpha7", + "propel/propel": ">=2-alpha.1,<=2-alpha.7", "propel/propel1": ">=1,<=1.7.1", "pusher/pusher-php-server": "<2.2.1", "robrichards/xmlseclibs": ">=1,<3.0.2", @@ -6404,11 +6904,14 @@ "shopware/shopware": "<5.3.7", "silverstripe/cms": ">=3,<=3.0.11|>=3.1,<3.1.11", "silverstripe/forum": "<=0.6.1|>=0.7,<=0.7.3", - "silverstripe/framework": ">=3,<3.6.7|>=3.7,<3.7.3|>=4,<4.0.7|>=4.1,<4.1.5|>=4.2,<4.2.4|>=4.3,<4.3.1", + "silverstripe/framework": ">=3,<3.6.7|>=3.7,<3.7.3|>=4,<4.4", + "silverstripe/graphql": ">=2,<2.0.5|>=3,<3.1.2", + "silverstripe/registry": ">=2.1,<2.1.2|>=2.2,<2.2.1", + "silverstripe/restfulserver": ">=1,<1.0.9|>=2,<2.0.4", "silverstripe/userforms": "<3", "simple-updates/phpwhois": "<=1", "simplesamlphp/saml2": "<1.10.6|>=2,<2.3.8|>=3,<3.1.4", - "simplesamlphp/simplesamlphp": "<1.16.3", + "simplesamlphp/simplesamlphp": "<1.17.3", "simplesamlphp/simplesamlphp-module-infocard": "<1.0.1", "slim/slim": "<2.6", "smarty/smarty": "<3.1.33", @@ -6418,39 +6921,45 @@ "stormpath/sdk": ">=0,<9.9.99", "swiftmailer/swiftmailer": ">=4,<5.4.5", "sylius/admin-bundle": ">=1,<1.0.17|>=1.1,<1.1.9|>=1.2,<1.2.2", - "sylius/sylius": ">=1,<1.0.17|>=1.1,<1.1.9|>=1.2,<1.2.2", - "symfony/dependency-injection": ">=2,<2.0.17", + "sylius/grid": ">=1,<1.1.19|>=1.2,<1.2.18|>=1.3,<1.3.13|>=1.4,<1.4.5|>=1.5,<1.5.1", + "sylius/grid-bundle": ">=1,<1.1.19|>=1.2,<1.2.18|>=1.3,<1.3.13|>=1.4,<1.4.5|>=1.5,<1.5.1", + "sylius/sylius": ">=1,<1.1.18|>=1.2,<1.2.17|>=1.3,<1.3.12|>=1.4,<1.4.4", + "symfony/cache": ">=3.1,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", + "symfony/dependency-injection": ">=2,<2.0.17|>=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", "symfony/form": ">=2.3,<2.3.35|>=2.4,<2.6.12|>=2.7,<2.7.50|>=2.8,<2.8.49|>=3,<3.4.20|>=4,<4.0.15|>=4.1,<4.1.9|>=4.2,<4.2.1", - "symfony/framework-bundle": ">=2,<2.3.18|>=2.4,<2.4.8|>=2.5,<2.5.2", - "symfony/http-foundation": ">=2,<2.7.49|>=2.8,<2.8.44|>=3,<3.3.18|>=3.4,<3.4.14|>=4,<4.0.14|>=4.1,<4.1.3", + "symfony/framework-bundle": ">=2,<2.3.18|>=2.4,<2.4.8|>=2.5,<2.5.2|>=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", + "symfony/http-foundation": ">=2,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", "symfony/http-kernel": ">=2,<2.3.29|>=2.4,<2.5.12|>=2.6,<2.6.8", "symfony/intl": ">=2.7,<2.7.38|>=2.8,<2.8.31|>=3,<3.2.14|>=3.3,<3.3.13", + "symfony/phpunit-bridge": ">=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", "symfony/polyfill": ">=1,<1.10", "symfony/polyfill-php55": ">=1,<1.10", + "symfony/proxy-manager-bridge": ">=2.7,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", "symfony/routing": ">=2,<2.0.19", - "symfony/security": ">=2,<2.7.50|>=2.8,<2.8.49|>=3,<3.4.19|>=4,<4.0.15|>=4.1,<4.1.9|>=4.2,<4.2.1", + "symfony/security": ">=2,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", "symfony/security-bundle": ">=2,<2.7.48|>=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11", "symfony/security-core": ">=2.4,<2.6.13|>=2.7,<2.7.9|>=2.7.30,<2.7.32|>=2.8,<2.8.37|>=3,<3.3.17|>=3.4,<3.4.7|>=4,<4.0.7", "symfony/security-csrf": ">=2.4,<2.7.48|>=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11", "symfony/security-guard": ">=2.8,<2.8.41|>=3,<3.3.17|>=3.4,<3.4.11|>=4,<4.0.11", - "symfony/security-http": ">=2.3,<2.3.41|>=2.4,<2.7.50|>=2.8,<2.8.49|>=3,<3.4.20|>=4,<4.0.15|>=4.1,<4.1.9|>=4.2,<4.2.1", + "symfony/security-http": ">=2.3,<2.3.41|>=2.4,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", "symfony/serializer": ">=2,<2.0.11", - "symfony/symfony": ">=2,<2.7.50|>=2.8,<2.8.49|>=3,<3.4.20|>=4,<4.0.15|>=4.1,<4.1.9|>=4.2,<4.2.1", + "symfony/symfony": ">=2,<2.7.51|>=2.8,<2.8.50|>=3,<3.4.26|>=4,<4.1.12|>=4.2,<4.2.7", "symfony/translation": ">=2,<2.0.17", "symfony/validator": ">=2,<2.0.24|>=2.1,<2.1.12|>=2.2,<2.2.5|>=2.3,<2.3.3", "symfony/web-profiler-bundle": ">=2,<2.3.19|>=2.4,<2.4.9|>=2.5,<2.5.4", "symfony/yaml": ">=2,<2.0.22|>=2.1,<2.1.7", "tecnickcom/tcpdf": "<6.2.22", "thelia/backoffice-default-template": ">=2.1,<2.1.2", - "thelia/thelia": ">=2.1.0-beta1,<2.1.3|>=2.1,<2.1.2", + "thelia/thelia": ">=2.1-beta.1,<2.1.3", "theonedemon/phpwhois": "<=4.2.5", "titon/framework": ">=0,<9.9.99", "truckersmp/phpwhois": "<=4.3.1", "twig/twig": "<1.38|>=2,<2.7", - "typo3/cms": ">=6.2,<6.2.30|>=7,<7.6.32|>=8,<8.7.23|>=9,<9.5.4", - "typo3/cms-core": ">=8,<8.7.23|>=9,<9.5.4", + "typo3/cms": ">=6.2,<6.2.30|>=7,<7.6.32|>=8,<8.7.27|>=9,<9.5.8", + "typo3/cms-core": ">=8,<8.7.27|>=9,<9.5.8", "typo3/flow": ">=1,<1.0.4|>=1.1,<1.1.1|>=2,<2.0.1|>=2.3,<2.3.16|>=3,<3.0.10|>=3.1,<3.1.7|>=3.2,<3.2.7|>=3.3,<3.3.5", "typo3/neos": ">=1.1,<1.1.3|>=1.2,<1.2.13|>=2,<2.0.4", + "typo3/phar-stream-wrapper": ">=1,<2.1.1|>=3,<3.1.1", "ua-parser/uap-php": "<3.8", "wallabag/tcpdf": "<6.2.22", "willdurand/js-translation-bundle": "<2.1.1", @@ -6466,6 +6975,7 @@ "zendframework/zend-captcha": ">=2,<2.4.9|>=2.5,<2.5.2", "zendframework/zend-crypt": ">=2,<2.4.9|>=2.5,<2.5.2", "zendframework/zend-db": ">=2,<2.0.99|>=2.1,<2.1.99|>=2.2,<2.2.10|>=2.3,<2.3.5", + "zendframework/zend-developer-tools": ">=1.2.2,<1.2.3", "zendframework/zend-diactoros": ">=1,<1.8.4", "zendframework/zend-feed": ">=1,<2.10.3", "zendframework/zend-form": ">=2,<2.2.7|>=2.3,<2.3.1", @@ -6500,7 +7010,7 @@ } ], "description": "Prevents installation of composer packages with known security vulnerabilities: no API, simply require it", - "time": "2019-03-12T13:04:55+00:00" + "time": "2019-07-18T15:17:58+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", @@ -6669,16 +7179,16 @@ }, { "name": "sebastian/environment", - "version": "4.1.0", + "version": "4.2.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "6fda8ce1974b62b14935adc02a9ed38252eca656" + "reference": "f2a2c8e1c97c11ace607a7a667d73d47c19fe404" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/6fda8ce1974b62b14935adc02a9ed38252eca656", - "reference": "6fda8ce1974b62b14935adc02a9ed38252eca656", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/f2a2c8e1c97c11ace607a7a667d73d47c19fe404", + "reference": "f2a2c8e1c97c11ace607a7a667d73d47c19fe404", "shasum": "" }, "require": { @@ -6693,7 +7203,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.1-dev" + "dev-master": "4.2-dev" } }, "autoload": { @@ -6718,7 +7228,7 @@ "environment", "hhvm" ], - "time": "2019-02-01T05:27:49+00:00" + "time": "2019-05-05T09:05:15+00:00" }, { "name": "sebastian/exporter", @@ -7028,6 +7538,52 @@ "homepage": "https://www.github.com/sebastianbergmann/resource-operations", "time": "2018-10-04T04:07:39+00:00" }, + { + "name": "sebastian/type", + "version": "1.1.3", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/type.git", + "reference": "3aaaa15fa71d27650d62a948be022fe3b48541a3" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/3aaaa15fa71d27650d62a948be022fe3b48541a3", + "reference": "3aaaa15fa71d27650d62a948be022fe3b48541a3", + "shasum": "" + }, + "require": { + "php": "^7.2" + }, + "require-dev": { + "phpunit/phpunit": "^8.2" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.1-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "role": "lead", + "email": "sebastian@phpunit.de" + } + ], + "description": "Collection of value objects that represent the types of the PHP type system", + "homepage": "https://github.com/sebastianbergmann/type", + "time": "2019-07-02T08:10:15+00:00" + }, { "name": "sebastian/version", "version": "2.0.1", @@ -7063,8 +7619,8 @@ "authors": [ { "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de", - "role": "lead" + "role": "lead", + "email": "sebastian@phpunit.de" } ], "description": "Library that helps with managing the version number of Git-hosted PHP projects", @@ -7166,16 +7722,16 @@ }, { "name": "symfony/filesystem", - "version": "v4.2.4", + "version": "v4.3.3", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "e16b9e471703b2c60b95f14d31c1239f68f11601" + "reference": "b9896d034463ad6fd2bf17e2bf9418caecd6313d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/e16b9e471703b2c60b95f14d31c1239f68f11601", - "reference": "e16b9e471703b2c60b95f14d31c1239f68f11601", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/b9896d034463ad6fd2bf17e2bf9418caecd6313d", + "reference": "b9896d034463ad6fd2bf17e2bf9418caecd6313d", "shasum": "" }, "require": { @@ -7185,7 +7741,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.2-dev" + "dev-master": "4.3-dev" } }, "autoload": { @@ -7212,20 +7768,20 @@ ], "description": "Symfony Filesystem Component", "homepage": "https://symfony.com", - "time": "2019-02-07T11:40:08+00:00" + "time": "2019-06-23T08:51:25+00:00" }, { "name": "theseer/tokenizer", - "version": "1.1.0", + "version": "1.1.3", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b" + "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/cb2f008f3f05af2893a87208fe6a6c4985483f8b", - "reference": "cb2f008f3f05af2893a87208fe6a6c4985483f8b", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/11336f6f84e16a720dae9d8e6ed5019efa85a0f9", + "reference": "11336f6f84e16a720dae9d8e6ed5019efa85a0f9", "shasum": "" }, "require": { @@ -7252,7 +7808,7 @@ } ], "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", - "time": "2017-04-07T12:08:54+00:00" + "time": "2019-06-13T22:48:21+00:00" }, { "name": "webmozart/assert", diff --git a/config/app.php b/config/app.php index a59f29e61e..f95262a3b1 100644 --- a/config/app.php +++ b/config/app.php @@ -19,7 +19,9 @@ * along with Firefly III. If not, see . */ + declare(strict_types=1); +use FireflyIII\Providers\ImportServiceProvider; return [ @@ -87,7 +89,6 @@ return [ FireflyIII\Providers\BudgetServiceProvider::class, FireflyIII\Providers\CategoryServiceProvider::class, FireflyIII\Providers\CurrencyServiceProvider::class, - FireflyIII\Providers\ExportJobServiceProvider::class, FireflyIII\Providers\FireflyServiceProvider::class, FireflyIII\Providers\JournalServiceProvider::class, FireflyIII\Providers\PiggyBankServiceProvider::class, @@ -97,6 +98,7 @@ return [ FireflyIII\Providers\TagServiceProvider::class, FireflyIII\Providers\AdminServiceProvider::class, FireflyIII\Providers\RecurringServiceProvider::class, + ImportServiceProvider::class, ], diff --git a/config/csv.php b/config/csv.php index 02303455ee..7c9c8c650e 100644 --- a/config/csv.php +++ b/config/csv.php @@ -26,6 +26,8 @@ use FireflyIII\Import\Specifics\AbnAmroDescription; use FireflyIII\Import\Specifics\IngDescription; use FireflyIII\Import\Specifics\PresidentsChoice; use FireflyIII\Import\Specifics\SnsDescription; +use FireflyIII\Import\Specifics\Belfius; +use FireflyIII\Import\Specifics\IngBelgium; return [ @@ -37,6 +39,8 @@ return [ 'AbnAmroDescription' => AbnAmroDescription::class, 'SnsDescription' => SnsDescription::class, 'PresidentsChoice' => PresidentsChoice::class, + 'Belfius' => Belfius::class, + 'IngBelgium' => IngBelgium::class ], /* @@ -359,56 +363,56 @@ return [ ], // SEPA end to end ID - 'sepa-ct-id' => [ + 'sepa_ct_id' => [ 'mappable' => false, 'pre-process-map' => false, 'converter' => 'Description', 'field' => 'sepa_ct_id', ], // SEPA opposing account identifier - 'sepa-ct-op' => [ + 'sepa_ct_op' => [ 'mappable' => false, 'pre-process-map' => false, 'converter' => 'Description', 'field' => 'sepa_ct_op', ], // SEPA Direct Debit Mandate Identifier - 'sepa-db' => [ + 'sepa_db' => [ 'mappable' => false, 'pre-process-map' => false, 'converter' => 'Description', 'field' => 'sepa_db', ], // SEPA clearing code - 'sepa-cc' => [ + 'sepa_cc' => [ 'mappable' => false, 'pre-process-map' => false, 'converter' => 'Description', 'field' => 'sepa_cc', ], // SEPA country - 'sepa-country' => [ + 'sepa_country' => [ 'mappable' => false, 'pre-process-map' => false, 'converter' => 'Description', 'field' => 'sepa_country', ], // SEPA external purpose - 'sepa-ep' => [ + 'sepa_ep' => [ 'mappable' => false, 'pre-process-map' => false, 'converter' => 'Description', 'field' => 'sepa_ep', ], // SEPA creditor identifier - 'sepa-ci' => [ + 'sepa_ci' => [ 'mappable' => false, 'pre-process-map' => false, 'converter' => 'Description', 'field' => 'sepa_ci', ], // SEPA Batch ID - 'sepa-batch-id' => [ + 'sepa_batch_id' => [ 'mappable' => false, 'pre-process-map' => false, 'converter' => 'Description', diff --git a/config/firefly.php b/config/firefly.php index e2efe34136..4cb7cbab3a 100644 --- a/config/firefly.php +++ b/config/firefly.php @@ -23,9 +23,40 @@ declare(strict_types=1); -use FireflyIII\Export\Exporter\CsvExporter; +use FireflyIII\Models\Account; +use FireflyIII\Models\AccountType; +use FireflyIII\Models\Attachment; +use FireflyIII\Models\AvailableBudget; +use FireflyIII\Models\Bill; +use FireflyIII\Models\Budget; +use FireflyIII\Models\BudgetLimit; +use FireflyIII\Models\Category; +use FireflyIII\Models\ImportJob; +use FireflyIII\Models\LinkType; +use FireflyIII\Models\PiggyBank; +use FireflyIII\Models\Preference; +use FireflyIII\Models\Recurrence; +use FireflyIII\Models\Rule; +use FireflyIII\Models\RuleGroup; +use FireflyIII\Models\Tag; +use FireflyIII\Models\TransactionCurrency; +use FireflyIII\Models\TransactionGroup; +use FireflyIII\Models\TransactionJournal; +use FireflyIII\Models\TransactionJournalLink; +use FireflyIII\Models\TransactionType as TransactionTypeModel; use FireflyIII\Services\Currency\FixerIOv2; use FireflyIII\Services\Currency\RatesApiIOv1; +use FireflyIII\Support\Binder\AccountList; +use FireflyIII\Support\Binder\BudgetList; +use FireflyIII\Support\Binder\CategoryList; +use FireflyIII\Support\Binder\CLIToken; +use FireflyIII\Support\Binder\ConfigurationName; +use FireflyIII\Support\Binder\CurrencyCode; +use FireflyIII\Support\Binder\Date; +use FireflyIII\Support\Binder\ImportProvider; +use FireflyIII\Support\Binder\JournalList; +use FireflyIII\Support\Binder\TagList; +use FireflyIII\Support\Binder\TagOrId; use FireflyIII\TransactionRules\Actions\AddTag; use FireflyIII\TransactionRules\Actions\AppendDescription; use FireflyIII\TransactionRules\Actions\AppendNotes; @@ -80,6 +111,7 @@ use FireflyIII\TransactionRules\Triggers\ToAccountIs; use FireflyIII\TransactionRules\Triggers\ToAccountStarts; use FireflyIII\TransactionRules\Triggers\TransactionType; use FireflyIII\TransactionRules\Triggers\UserAction; +use FireflyIII\User; /* * DO NOT EDIT THIS FILE. IT IS AUTO GENERATED. @@ -88,14 +120,14 @@ use FireflyIII\TransactionRules\Triggers\UserAction; */ return [ - 'configuration' => [ + 'configuration' => [ 'single_user_mode' => true, 'is_demo_site' => false, ], 'encryption' => null === env('USE_ENCRYPTION') || env('USE_ENCRYPTION') === true, - 'version' => '4.7.17.6', - 'api_version' => '0.9.2', - 'db_version' => 10, + 'version' => '4.8.0', + 'api_version' => '0.10.0', + 'db_version' => 11, 'maxUploadSize' => 15242880, 'send_error_message' => env('SEND_ERROR_MESSAGE', true), 'site_owner' => env('SITE_OWNER', ''), @@ -177,10 +209,6 @@ return [ 'application/vnd.oasis.opendocument.image', ], 'list_length' => 10, - 'export_formats' => [ - 'csv' => CsvExporter::class, - ], - 'default_export_format' => 'csv', 'default_import_format' => 'csv', 'bill_periods' => ['weekly', 'monthly', 'quarterly', 'half-year', 'yearly'], 'accountRoles' => ['defaultAsset', 'sharedAsset', 'savingAsset', 'ccAsset', 'cashWalletAsset'], @@ -264,24 +292,27 @@ return [ ], 'languages' => [ 'en_US' => ['name_locale' => 'English', 'name_english' => 'English'], + 'cs_CZ' => ['name_locale' => 'Czech', 'name_english' => 'Czech'], // 35% 'es_ES' => ['name_locale' => 'Español', 'name_english' => 'Spanish'], // 92% 'de_DE' => ['name_locale' => 'Deutsch', 'name_english' => 'German'], // 100% 'fr_FR' => ['name_locale' => 'Français', 'name_english' => 'French'], // 100% //'id_ID' => ['name_locale' => 'Bahasa Indonesia', 'name_english' => 'Indonesian'], // 65% 'it_IT' => ['name_locale' => 'Italiano', 'name_english' => 'Italian'], // 100% + 'nb_NO' => ['name_locale' => 'Norsk', 'name_english' => 'Norwegian'], 'nl_NL' => ['name_locale' => 'Nederlands', 'name_english' => 'Dutch'], // 100% 'pl_PL' => ['name_locale' => 'Polski', 'name_english' => 'Polish '], // 87% 'pt_BR' => ['name_locale' => 'Português do Brasil', 'name_english' => 'Portuguese (Brazil)'], // 80% + 'ro_RO' => ['name_locale' => 'Română', 'name_english' => 'Romanian'], 'ru_RU' => ['name_locale' => 'Русский', 'name_english' => 'Russian'], // 83% 'zh_TW' => ['name_locale' => 'Chinese Traditional', 'name_english' => 'Chinese Traditional'], // 100% 'zh_CN' => ['name_locale' => 'Chinese Simplified', 'name_english' => 'Chinese Simplified'], // 99% + 'hu_HU' => ['name_locale' => 'Hungarian', 'name_english' => 'Hungarian'], // 40% + // hungarian! + //'tr_TR' => ['name_locale' => 'Türkçe', 'name_english' => 'Turkish'], // 71% - 'nb_NO' => ['name_locale' => 'Norsk', 'name_english' => 'Norwegian'], //'ca_ES' => ['name_locale' => 'Catalan', 'name_english' => 'Catalan'], // 0% //'ja_JA' => ['name_locale' => 'Japanese', 'name_english' => 'Japanese'], // 0% - //'cs_CZ' => ['name_locale' => 'Czech', 'name_english' => 'Czech'], // 35% //'he_IL' => ['name_locale' => 'Hebrew', 'name_english' => 'Hebrew'], // 2% - //'hu_HU' => ['name_locale' => 'Hungarian', 'name_english' => 'Hungarian'], // 40% //'sv_SE' => ['name_locale' => 'Svenska', 'name_english' => 'Swedish'], // 1% //'sr_CS' => ['name_locale' => 'Serbian (Latin)', 'name_english' => 'Serbian (Latin)'], // 0% //'sl_SI' => ['name_locale' => 'Slovenian', 'name_english' => 'Slovenian'], // 10% @@ -297,6 +328,14 @@ return [ 'transfer' => ['Transfer'], 'transfers' => ['Transfer'], ], + 'transactionTypesByType' => [ + 'expenses' => ['Withdrawal'], + 'withdrawal' => ['Withdrawal'], + 'revenue' => ['Deposit'], + 'deposit' => ['Deposit'], + 'transfer' => ['Transfer'], + 'transfers' => ['Transfer'], + ], 'transactionTypesToShort' => [ 'Withdrawal' => 'withdrawal', 'Deposit' => 'deposit', @@ -315,54 +354,51 @@ return [ ], 'bindables' => [ // models - 'account' => \FireflyIII\Models\Account::class, - 'attachment' => \FireflyIII\Models\Attachment::class, - 'availableBudget' => \FireflyIII\Models\AvailableBudget::class, - 'bill' => \FireflyIII\Models\Bill::class, - 'budget' => \FireflyIII\Models\Budget::class, - 'budgetLimit' => \FireflyIII\Models\BudgetLimit::class, - 'category' => \FireflyIII\Models\Category::class, - 'linkType' => \FireflyIII\Models\LinkType::class, - 'transactionType' => \FireflyIII\Models\TransactionType::class, - 'journalLink' => \FireflyIII\Models\TransactionJournalLink::class, - 'currency' => \FireflyIII\Models\TransactionCurrency::class, - 'piggyBank' => \FireflyIII\Models\PiggyBank::class, - 'preference' => \FireflyIII\Models\Preference::class, - 'tj' => \FireflyIII\Models\TransactionJournal::class, - 'tag' => \FireflyIII\Models\Tag::class, - 'recurrence' => \FireflyIII\Models\Recurrence::class, - 'rule' => \FireflyIII\Models\Rule::class, - 'ruleGroup' => \FireflyIII\Models\RuleGroup::class, - 'exportJob' => \FireflyIII\Models\ExportJob::class, - 'importJob' => \FireflyIII\Models\ImportJob::class, - 'transaction' => \FireflyIII\Models\Transaction::class, - 'user' => \FireflyIII\User::class, + 'account' => Account::class, + 'attachment' => Attachment::class, + 'availableBudget' => AvailableBudget::class, + 'bill' => Bill::class, + 'budget' => Budget::class, + 'budgetLimit' => BudgetLimit::class, + 'category' => Category::class, + 'linkType' => LinkType::class, + 'transactionType' => TransactionTypeModel::class, + 'journalLink' => TransactionJournalLink::class, + 'currency' => TransactionCurrency::class, + 'piggyBank' => PiggyBank::class, + 'preference' => Preference::class, + 'tj' => TransactionJournal::class, + 'tag' => Tag::class, + 'recurrence' => Recurrence::class, + 'rule' => Rule::class, + 'ruleGroup' => RuleGroup::class, + 'importJob' => ImportJob::class, + 'transactionGroup' => TransactionGroup::class, + 'user' => User::class, // strings - 'import_provider' => \FireflyIII\Support\Binder\ImportProvider::class, - 'currency_code' => \FireflyIII\Support\Binder\CurrencyCode::class, + 'import_provider' => ImportProvider::class, + 'currency_code' => CurrencyCode::class, // dates - 'start_date' => \FireflyIII\Support\Binder\Date::class, - 'end_date' => \FireflyIII\Support\Binder\Date::class, - 'date' => \FireflyIII\Support\Binder\Date::class, + 'start_date' => Date::class, + 'end_date' => Date::class, + 'date' => Date::class, // lists - 'accountList' => \FireflyIII\Support\Binder\AccountList::class, - 'expenseList' => \FireflyIII\Support\Binder\AccountList::class, - 'budgetList' => \FireflyIII\Support\Binder\BudgetList::class, - 'journalList' => \FireflyIII\Support\Binder\JournalList::class, - 'categoryList' => \FireflyIII\Support\Binder\CategoryList::class, - 'tagList' => \FireflyIII\Support\Binder\TagList::class, - 'simpleJournalList' => \FireflyIII\Support\Binder\SimpleJournalList::class, + 'accountList' => AccountList::class, + 'expenseList' => AccountList::class, + 'budgetList' => BudgetList::class, + 'journalList' => JournalList::class, + 'categoryList' => CategoryList::class, + 'tagList' => TagList::class, // others - 'fromCurrencyCode' => \FireflyIII\Support\Binder\CurrencyCode::class, - 'toCurrencyCode' => \FireflyIII\Support\Binder\CurrencyCode::class, - 'unfinishedJournal' => \FireflyIII\Support\Binder\UnfinishedJournal::class, - 'cliToken' => \FireflyIII\Support\Binder\CLIToken::class, - 'tagOrId' => \FireflyIII\Support\Binder\TagOrId::class, - 'configName' => \FireflyIII\Support\Binder\ConfigurationName::class, + 'fromCurrencyCode' => CurrencyCode::class, + 'toCurrencyCode' => CurrencyCode::class, + 'cliToken' => CLIToken::class, + 'tagOrId' => TagOrId::class, + 'configName' => ConfigurationName::class, ], @@ -470,17 +506,203 @@ return [ ], - 'test-triggers' => [ + 'test-triggers' => [ 'limit' => 10, 'range' => 200, ], - 'default_currency' => 'EUR', - 'default_language' => 'en_US', - 'search_modifiers' => ['amount_is', 'amount', 'amount_max', 'amount_min', 'amount_less', 'amount_more', 'source', 'destination', 'category', - 'budget', 'bill', 'type', 'date', 'date_before', 'date_after', 'on', 'before', 'after'], + 'default_currency' => 'EUR', + 'default_language' => 'en_US', + 'search_modifiers' => ['amount_is', 'amount', 'amount_max', 'amount_min', 'amount_less', 'amount_more', 'source', 'destination', 'category', + 'budget', 'bill', 'type', 'date', 'date_before', 'date_after', 'on', 'before', 'after','from','to'], // tag notes has_attachments - 'cer_providers' => [ + 'cer_providers' => [ 'fixer' => FixerIOv2::class, 'ratesapi' => RatesApiIOv1::class, ], + + // expected source types for each transaction type, in order of preference. + 'expected_source_types' => [ + 'source' => [ + TransactionTypeModel::WITHDRAWAL => [AccountType::ASSET, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE], + TransactionTypeModel::DEPOSIT => [AccountType::REVENUE, AccountType::CASH, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE, + AccountType::INITIAL_BALANCE, AccountType::RECONCILIATION], + TransactionTypeModel::TRANSFER => [AccountType::ASSET, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE], + TransactionTypeModel::OPENING_BALANCE => [AccountType::INITIAL_BALANCE, AccountType::ASSET, AccountType::LOAN, AccountType::DEBT, + AccountType::MORTGAGE], + TransactionTypeModel::RECONCILIATION => [AccountType::RECONCILIATION, AccountType::ASSET], + // in case no transaction type is known yet, it could be anything. + 'none' => [ + AccountType::ASSET, + AccountType::EXPENSE, + AccountType::REVENUE, + AccountType::LOAN, + AccountType::DEBT, + AccountType::MORTGAGE, + ], + ], + 'destination' => [ + TransactionTypeModel::WITHDRAWAL => [AccountType::EXPENSE, AccountType::CASH, AccountType::LOAN, AccountType::DEBT, + AccountType::MORTGAGE], + TransactionTypeModel::DEPOSIT => [AccountType::ASSET, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE], + TransactionTypeModel::TRANSFER => [AccountType::ASSET, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE], + TransactionTypeModel::OPENING_BALANCE => [AccountType::INITIAL_BALANCE, AccountType::ASSET, AccountType::LOAN, AccountType::DEBT, + AccountType::MORTGAGE], + TransactionTypeModel::RECONCILIATION => [AccountType::RECONCILIATION, AccountType::ASSET], + ], + ], + 'allowed_opposing_types' => [ + 'source' => [ + AccountType::ASSET => [AccountType::ASSET, AccountType::CASH, AccountType::DEBT, AccountType::EXPENSE, AccountType::INITIAL_BALANCE, + AccountType::LOAN, AccountType::RECONCILIATION], + AccountType::CASH => [AccountType::ASSET], + AccountType::DEBT => [AccountType::ASSET, AccountType::DEBT, AccountType::EXPENSE, AccountType::INITIAL_BALANCE, AccountType::LOAN, + AccountType::MORTGAGE], + AccountType::EXPENSE => [], // is not allowed as a source. + AccountType::INITIAL_BALANCE => [AccountType::ASSET, AccountType::DEBT, AccountType::LOAN, AccountType::MORTGAGE], + AccountType::LOAN => [AccountType::ASSET, AccountType::DEBT, AccountType::EXPENSE, AccountType::INITIAL_BALANCE, AccountType::LOAN, + AccountType::MORTGAGE], + AccountType::MORTGAGE => [AccountType::ASSET, AccountType::DEBT, AccountType::EXPENSE, AccountType::INITIAL_BALANCE, AccountType::LOAN, + AccountType::MORTGAGE], + AccountType::RECONCILIATION => [AccountType::ASSET], + AccountType::REVENUE => [AccountType::ASSET, AccountType::DEBT, AccountType::LOAN, AccountType::MORTGAGE], + + ], + 'destination' => [ + AccountType::ASSET => [AccountType::ASSET, AccountType::CASH, AccountType::DEBT, AccountType::INITIAL_BALANCE, AccountType::LOAN, + AccountType::MORTGAGE, AccountType::RECONCILIATION, AccountType::REVENUE], + AccountType::CASH => [AccountType::ASSET], + AccountType::DEBT => [AccountType::ASSET, AccountType::DEBT, AccountType::INITIAL_BALANCE, AccountType::LOAN, AccountType::MORTGAGE, + AccountType::REVENUE], + AccountType::EXPENSE => [AccountType::ASSET, AccountType::DEBT, AccountType::LOAN, AccountType::MORTGAGE], + AccountType::INITIAL_BALANCE => [AccountType::ASSET, AccountType::DEBT, AccountType::LOAN, AccountType::MORTGAGE], + AccountType::LOAN => [AccountType::ASSET, AccountType::DEBT, AccountType::INITIAL_BALANCE, AccountType::LOAN, AccountType::MORTGAGE, + AccountType::REVENUE], + AccountType::MORTGAGE => [AccountType::ASSET, AccountType::DEBT, AccountType::INITIAL_BALANCE, AccountType::LOAN, AccountType::MORTGAGE, + AccountType::REVENUE], + AccountType::RECONCILIATION => [AccountType::ASSET], + AccountType::REVENUE => [], // is not allowed as a destination + ], + ], + // depending on the account type, return the allowed transaction types: + 'allowed_transaction_types' => [ + 'source' => [ + AccountType::ASSET => [TransactionTypeModel::WITHDRAWAL, TransactionTypeModel::TRANSFER, TransactionTypeModel::OPENING_BALANCE, + TransactionTypeModel::RECONCILIATION], + AccountType::EXPENSE => [], // is not allowed as a source. + AccountType::REVENUE => [TransactionTypeModel::DEPOSIT], + AccountType::LOAN => [TransactionTypeModel::WITHDRAWAL, TransactionTypeModel::DEPOSIT, TransactionTypeModel::TRANSFER, + TransactionTypeModel::OPENING_BALANCE], + AccountType::DEBT => [TransactionTypeModel::WITHDRAWAL, TransactionTypeModel::DEPOSIT, TransactionTypeModel::TRANSFER, + TransactionTypeModel::OPENING_BALANCE], + AccountType::MORTGAGE => [TransactionTypeModel::WITHDRAWAL, TransactionTypeModel::DEPOSIT, TransactionTypeModel::TRANSFER, + TransactionTypeModel::OPENING_BALANCE], + AccountType::INITIAL_BALANCE => [], // todo fill me in. + AccountType::RECONCILIATION => [], // todo fill me in. + ], + 'destination' => [ + AccountType::ASSET => [TransactionTypeModel::DEPOSIT, TransactionTypeModel::TRANSFER, TransactionTypeModel::OPENING_BALANCE, + TransactionTypeModel::RECONCILIATION], + AccountType::EXPENSE => [TransactionTypeModel::WITHDRAWAL], + AccountType::REVENUE => [], // is not allowed as destination. + AccountType::LOAN => [TransactionTypeModel::WITHDRAWAL, TransactionTypeModel::DEPOSIT, TransactionTypeModel::TRANSFER, + TransactionTypeModel::OPENING_BALANCE], + AccountType::DEBT => [TransactionTypeModel::WITHDRAWAL, TransactionTypeModel::DEPOSIT, TransactionTypeModel::TRANSFER, + TransactionTypeModel::OPENING_BALANCE], + AccountType::MORTGAGE => [TransactionTypeModel::WITHDRAWAL, TransactionTypeModel::DEPOSIT, TransactionTypeModel::TRANSFER, + TransactionTypeModel::OPENING_BALANCE], + AccountType::INITIAL_BALANCE => [], // todo fill me in. + AccountType::RECONCILIATION => [], // todo fill me in. + ], + + ], + + // having the source + dest will tell you the transaction type. + 'account_to_transaction' => [ + AccountType::ASSET => [ + AccountType::ASSET => TransactionTypeModel::TRANSFER, + AccountType::CASH => TransactionTypeModel::WITHDRAWAL, + AccountType::DEBT => TransactionTypeModel::WITHDRAWAL, + AccountType::EXPENSE => TransactionTypeModel::WITHDRAWAL, + AccountType::INITIAL_BALANCE => TransactionTypeModel::OPENING_BALANCE, + AccountType::LOAN => TransactionTypeModel::WITHDRAWAL, + AccountType::MORTGAGE => TransactionTypeModel::WITHDRAWAL, + AccountType::RECONCILIATION => TransactionTypeModel::RECONCILIATION, + ], + AccountType::CASH => [ + AccountType::ASSET => TransactionTypeModel::DEPOSIT, + ], + AccountType::DEBT => [ + AccountType::ASSET => TransactionTypeModel::DEPOSIT, + AccountType::DEBT => TransactionTypeModel::TRANSFER, + AccountType::EXPENSE => TransactionTypeModel::WITHDRAWAL, + AccountType::INITIAL_BALANCE => TransactionTypeModel::OPENING_BALANCE, + AccountType::LOAN => TransactionTypeModel::TRANSFER, + AccountType::MORTGAGE => TransactionTypeModel::TRANSFER, + ], + AccountType::INITIAL_BALANCE => [ + AccountType::ASSET => TransactionTypeModel::OPENING_BALANCE, + AccountType::DEBT => TransactionTypeModel::OPENING_BALANCE, + AccountType::LOAN => TransactionTypeModel::OPENING_BALANCE, + AccountType::MORTGAGE => TransactionTypeModel::OPENING_BALANCE, + ], + AccountType::LOAN => [ + AccountType::ASSET => TransactionTypeModel::DEPOSIT, + AccountType::DEBT => TransactionTypeModel::TRANSFER, + AccountType::EXPENSE => TransactionTypeModel::WITHDRAWAL, + AccountType::INITIAL_BALANCE => TransactionTypeModel::OPENING_BALANCE, + AccountType::LOAN => TransactionTypeModel::TRANSFER, + AccountType::MORTGAGE => TransactionTypeModel::TRANSFER, + ], + AccountType::MORTGAGE => [ + AccountType::ASSET => TransactionTypeModel::DEPOSIT, + AccountType::DEBT => TransactionTypeModel::TRANSFER, + AccountType::EXPENSE => TransactionTypeModel::WITHDRAWAL, + AccountType::INITIAL_BALANCE => TransactionTypeModel::OPENING_BALANCE, + AccountType::LOAN => TransactionTypeModel::TRANSFER, + AccountType::MORTGAGE => TransactionTypeModel::TRANSFER, + ], + AccountType::RECONCILIATION => [ + AccountType::ASSET => TransactionTypeModel::RECONCILIATION, + ], + AccountType::REVENUE => [ + AccountType::ASSET => TransactionTypeModel::DEPOSIT, + AccountType::DEBT => TransactionTypeModel::DEPOSIT, + AccountType::LOAN => TransactionTypeModel::DEPOSIT, + AccountType::MORTGAGE => TransactionTypeModel::DEPOSIT, + ], + ], + + // allowed source / destination accounts. + 'source_dests' => [ + TransactionTypeModel::WITHDRAWAL => [ + AccountType::ASSET => [AccountType::EXPENSE, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE, AccountType::CASH], + AccountType::LOAN => [AccountType::EXPENSE], + AccountType::DEBT => [AccountType::EXPENSE], + AccountType::MORTGAGE => [AccountType::EXPENSE], + ], + TransactionTypeModel::DEPOSIT => [ + AccountType::REVENUE => [AccountType::ASSET, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE], + AccountType::CASH => [AccountType::ASSET], + AccountType::LOAN => [AccountType::ASSET], + AccountType::DEBT => [AccountType::ASSET], + AccountType::MORTGAGE => [AccountType::ASSET], + ], + TransactionTypeModel::TRANSFER => [ + AccountType::ASSET => [AccountType::ASSET], + AccountType::LOAN => [AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE], + AccountType::DEBT => [AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE], + AccountType::MORTGAGE => [AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE], + ], + TransactionTypeModel::OPENING_BALANCE => [ + AccountType::ASSET => [AccountType::INITIAL_BALANCE], + AccountType::LOAN => [AccountType::INITIAL_BALANCE], + AccountType::DEBT => [AccountType::INITIAL_BALANCE], + AccountType::MORTGAGE => [AccountType::INITIAL_BALANCE], + AccountType::INITIAL_BALANCE => [AccountType::ASSET, AccountType::LOAN, AccountType::DEBT, AccountType::MORTGAGE], + ], + TransactionTypeModel::RECONCILIATION => [ + AccountType::RECONCILIATION => [AccountType::ASSET], + AccountType::ASSET => [AccountType::RECONCILIATION], + ], + ], ]; diff --git a/config/google2fa.php b/config/google2fa.php index ff1ec61523..c14832b761 100644 --- a/config/google2fa.php +++ b/config/google2fa.php @@ -1,28 +1,5 @@ . - */ - -declare(strict_types=1); - - return [ /* @@ -54,17 +31,17 @@ return [ * 2FA verified session var */ - 'session_var' => 'google2fa', + 'session_var' => 'google2fa', /* * One Time Password request input name */ - 'otp_input' => 'one_time_password', + 'otp_input' => 'one_time_password', /* * One Time Password Window */ - 'window' => 1, + 'window' => 1, /* * Forbid user to reuse One Time Passwords. @@ -74,18 +51,23 @@ return [ /* * User's table column for google2fa secret */ - 'otp_secret_column' => 'google2fa_secret', + 'otp_secret_column' => 'mfa_secret', /* * One Time Password View */ - 'view' => 'google2fa.index', + 'view' => 'auth.mfa', /* * One Time Password error message */ - 'error_messages' => [ + 'error_messages' => [ 'wrong_otp' => "The 'One Time Password' typed was wrong.", ], + /* + * Throw exceptions or just fire events? + */ + 'throw_exceptions' => true, + ]; diff --git a/config/intro.php b/config/intro.php index eb427403fc..a18f7604b3 100644 --- a/config/intro.php +++ b/config/intro.php @@ -27,27 +27,53 @@ declare(strict_types=1); return [ // index - 'index' => [ + 'index' => [ 'intro' => [], 'accounts-chart' => ['element' => '#accounts-chart'], 'box_out_holder' => ['element' => '#box_out_holder'], 'help' => ['element' => '#help', 'position' => 'bottom'], 'sidebar-toggle' => ['element' => '#sidebar-toggle', 'position' => 'bottom'], + 'cash_account' => ['element' => '#all_transactions','position' => 'left'], 'outro' => [], ], // accounts: create - 'accounts_create' => [ + 'accounts_create' => [ 'iban' => ['element' => '#ffInput_iban'], ], + // transactions: create + 'transactions_create_withdrawal' => [ + 'source' => ['element' => 'input[name="source[]"]'], + 'destination' => ['element' => 'input[name="destination[]"]'], + 'foreign_currency' => ['element' => 'select[name="foreign_currency[]"]'], + 'more_meta' => ['element' => 'input[name="category[]"]'], + 'split_add' => ['element' => '.split_add_btn'], + ], + + 'transactions_create_deposit' => [ + 'source' => ['element' => 'input[name="source[]"]'], + 'destination' => ['element' => 'input[name="destination[]"]'], + 'foreign_currency' => ['element' => 'select[name="foreign_currency[]"]'], + 'more_meta' => ['element' => 'input[name="category[]"]'], + 'split_add' => ['element' => '.split_add_btn'], + ], + + 'transactions_create_transfer' => [ + 'source' => ['element' => 'input[name="source[]"]'], + 'destination' => ['element' => 'input[name="destination[]"]'], + 'foreign_currency' => ['element' => 'select[name="foreign_currency[]"]'], + 'more_meta' => ['element' => 'input[name="category[]"]'], + 'split_add' => ['element' => '.split_add_btn'], + ], + // extra text for asset account creation. - 'accounts_create_asset' => [ - 'opening_balance' => ['element' => '#ffInput_openingBalance'], + 'accounts_create_asset' => [ + 'opening_balance' => ['element' => '#ffInput_opening_balance'], 'currency' => ['element' => '#ffInput_currency_id'], - 'virtual' => ['element' => '#ffInput_virtualBalance'], + 'virtual' => ['element' => '#ffInput_virtual_balance'], ], // budgets: index - 'budgets_index' => [ + 'budgets_index' => [ 'intro' => [], 'set_budget' => ['element' => '#availableBar',], 'see_expenses_bar' => ['element' => '#spentBar'], @@ -58,17 +84,17 @@ return [ ], // reports: index, default report, audit, budget, cat, tag - 'reports_index' => [ + 'reports_index' => [ 'intro' => [], 'inputReportType' => ['element' => '#inputReportType'], 'inputAccountsSelect' => ['element' => '#inputAccountsSelect'], 'inputDateRange' => ['element' => '#inputDateRange'], 'extra-options-box' => ['element' => '#extra-options-box', 'position' => 'top'], ], - 'reports_report_default' => [ + 'reports_report_default' => [ 'intro' => [], ], - 'reports_report_audit' => [ + 'reports_report_audit' => [ 'intro' => [], 'optionsBox' => ['element' => '#optionsBox'], ], @@ -89,22 +115,6 @@ return [ 'incomeAndExpensesChart' => ['element' => '#incomeAndExpensesChart', 'position' => 'top'], ], - // transactions: create (also per type!) - 'transactions_create' => [ - 'switch_box' => ['element' => '#switch-box'], - 'ffInput_category' => ['element' => '#ffInput_category'], - ], - 'transactions_create_withdrawal' => [ - 'ffInput_budget' => ['element' => '#ffInput_budget_id'], - 'currency_dropdown_amount' => ['element' => '#currency_dropdown_amount'], - ], - 'transactions_create_deposit' => [ - 'currency_dropdown_amount' => ['element' => '#currency_dropdown_amount'], - ], - 'transactions_create_transfer' => [ - 'ffInput_piggy_bank_id' => ['element' => '#ffInput_piggy_bank_id'], - ], - // piggies: index, create, show 'piggy-banks_index' => [ 'saved' => ['element' => '.piggySaved'], diff --git a/config/twigbridge.php b/config/twigbridge.php index 9dc45d79e9..02f8f8c206 100644 --- a/config/twigbridge.php +++ b/config/twigbridge.php @@ -1,5 +1,26 @@ . + */ + use TwigBridge\Extension\Laravel\Url; use TwigBridge\Extension\Laravel\Str; use TwigBridge\Extension\Laravel\Translator; @@ -176,7 +197,11 @@ return [ 'is_safe' => ['date', 'text', 'select', 'balance', 'optionsList', 'checkbox', 'amount', 'tags', 'integer', 'textarea', 'location', 'file', 'staticText', 'password', 'nonSelectableAmount', 'number', 'assetAccountList', 'amountNoCurrency', 'currencyList', 'ruleGroupList', 'assetAccountCheckList', 'ruleGroupListWithEmpty', 'piggyBankList', 'currencyListEmpty', - 'activeAssetAccountList', 'percentage', 'activeLongAccountList', 'longAccountList','balanceAll'],], + 'activeAssetAccountList', 'percentage', 'activeLongAccountList', 'longAccountList','balanceAll', + 'activeWithdrawalDestinations','activeDepositDestinations' + + + ],], 'Form' => ['is_safe' => ['input', 'select', 'checkbox', 'model', 'open', 'radio', 'textarea', 'file',], ], ], diff --git a/config/upgrade.php b/config/upgrade.php index 03a7782640..99be24b77f 100644 --- a/config/upgrade.php +++ b/config/upgrade.php @@ -33,6 +33,7 @@ return [ '4.7.6' => 'This will be the last version to require PHP7.1. Future versions will require PHP7.2 minimum.', '4.7.7' => 'This version of Firefly III requires PHP7.2.', '4.7.10' => 'Firefly III no longer encrypts database values. To protect your data, make sure you use TDE or FDE. Read more: https://bit.ly/FF3-encryption', + '4.8.0' => 'This is a huge upgrade for Firefly III. Please expect bugs and errors, and bear with me as I fix them. I tested a lot of things but pretty sure I missed some. Thanks for understanding.', ], 'install' => [ @@ -44,6 +45,7 @@ return [ '4.7.6' => 'This will be the last version to require PHP7.1. Future versions will require PHP7.2 minimum.', '4.7.7' => 'This version of Firefly III requires PHP7.2.', '4.7.10' => 'Firefly III no longer encrypts database values. To protect your data, make sure you use TDE or FDE. Read more: https://bit.ly/FF3-encryption', + '4.8.0' => 'This is a huge upgrade for Firefly III. Please expect bugs and errors, and bear with me as I fix them. I tested a lot of things but pretty sure I missed some. Thanks for understanding.', ], ], ]; diff --git a/database/migrations/2017_06_02_105232_changes_for_v450.php b/database/migrations/2017_06_02_105232_changes_for_v450.php index 39c3ec1c9b..ea9224f416 100644 --- a/database/migrations/2017_06_02_105232_changes_for_v450.php +++ b/database/migrations/2017_06_02_105232_changes_for_v450.php @@ -33,15 +33,28 @@ class ChangesForV450 extends Migration */ public function down(): void { + // split up for sqlite compatibility Schema::table( 'transactions', - function (Blueprint $table) { + static function (Blueprint $table) { $table->dropColumn('foreign_amount'); + } + ); + + Schema::table( + 'transactions', + static function (Blueprint $table) { // cannot drop foreign keys in SQLite: if ('sqlite' !== config('database.default')) { $table->dropForeign('transactions_foreign_currency_id_foreign'); } + } + ); + + Schema::table( + 'transactions', + static function (Blueprint $table) { $table->dropColumn('foreign_currency_id'); } ); diff --git a/database/migrations/2018_04_29_174524_changes_for_v474.php b/database/migrations/2018_04_29_174524_changes_for_v474.php index dfa7e722ac..d9c2598cd7 100644 --- a/database/migrations/2018_04_29_174524_changes_for_v474.php +++ b/database/migrations/2018_04_29_174524_changes_for_v474.php @@ -37,19 +37,53 @@ class ChangesForV474 extends Migration */ public function down(): void { + // split up for sqlite compatibility. Schema::table( 'import_jobs', - function (Blueprint $table) { + static function (Blueprint $table) { // cannot drop foreign keys in SQLite: if ('sqlite' !== config('database.default')) { $table->dropForeign('import_jobs_tag_id_foreign'); } + } + ); + Schema::table( + 'import_jobs', + static function (Blueprint $table) { $table->dropColumn('provider'); + + } + ); + + Schema::table( + 'import_jobs', + static function (Blueprint $table) { $table->dropColumn('stage'); + + } + ); + + Schema::table( + 'import_jobs', + static function (Blueprint $table) { $table->dropColumn('transactions'); + + } + ); + + Schema::table( + 'import_jobs', + static function (Blueprint $table) { $table->dropColumn('errors'); + + } + ); + + Schema::table( + 'import_jobs', + static function (Blueprint $table) { $table->dropColumn('tag_id'); } @@ -65,7 +99,7 @@ class ChangesForV474 extends Migration { Schema::table( 'import_jobs', - function (Blueprint $table) { + static function (Blueprint $table) { $table->string('provider', 50)->after('file_type')->default(''); $table->string('stage', 50)->after('status')->default(''); $table->longText('transactions')->after('extended_status')->nullable(); diff --git a/database/migrations/2019_02_11_170529_changes_for_v4712.php b/database/migrations/2019_02_11_170529_changes_for_v4712.php index f0a9276cd2..ad4371e711 100644 --- a/database/migrations/2019_02_11_170529_changes_for_v4712.php +++ b/database/migrations/2019_02_11_170529_changes_for_v4712.php @@ -1,5 +1,26 @@ . + */ + use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; diff --git a/database/migrations/2019_03_22_183214_changes_for_v480.php b/database/migrations/2019_03_22_183214_changes_for_v480.php new file mode 100644 index 0000000000..21d19891c5 --- /dev/null +++ b/database/migrations/2019_03_22_183214_changes_for_v480.php @@ -0,0 +1,92 @@ +. + */ + +use Illuminate\Database\Migrations\Migration; +use Illuminate\Database\Schema\Blueprint; + +/** + * Class ChangesForV480 + */ +class ChangesForV480 extends Migration +{ + /** + * Reverse the migrations. + * + * @return void + */ + public function down(): void + { + Schema::table( + 'transaction_journals', + function (Blueprint $table) { + // drop transaction_group_id + foreign key. + // cannot drop foreign keys in SQLite: + if ('sqlite' !== config('database.default')) { + $table->dropForeign('transaction_journals_transaction_group_id_foreign'); + } + $table->dropColumn('transaction_group_id'); + } + ); + Schema::table('rule_groups', static function (Blueprint $table) { + $table->dropColumn('stop_processing'); + }); + + Schema::table( + 'users', static function (Blueprint $table) { + $table->dropColumn('mfa_secret'); + } + ); + } + + /** + * Run the migrations. + * + * @return void + */ + public function up(): void + { + + Schema::table( + 'transaction_journals', + static function (Blueprint $table) { + + $table->integer('transaction_currency_id', false, true)->nullable()->change(); + + // add column "group_id" after "transaction_type_id" + $table->integer('transaction_group_id', false, true) + ->nullable()->default(null)->after('transaction_type_id'); + + // add foreign key for "transaction_group_id" + $table->foreign('transaction_group_id')->references('id')->on('transaction_groups')->onDelete('cascade'); + } + ); + Schema::table('rule_groups', static function (Blueprint $table) { + $table->boolean('stop_processing')->default(false); + }); + Schema::table( + 'users', static function (Blueprint $table) { + $table->string('mfa_secret', 50)->nullable(); + } + ); + } +} diff --git a/database/seeds/LinkTypeSeeder.php b/database/seeds/LinkTypeSeeder.php index 47fecbbd7c..01a23cdfe4 100644 --- a/database/seeds/LinkTypeSeeder.php +++ b/database/seeds/LinkTypeSeeder.php @@ -46,10 +46,11 @@ class LinkTypeSeeder extends Seeder 'outward' => '(partially) refunds', 'editable' => false, ], - ['name' => 'Paid', - 'inward' => 'is (partially) paid for by', - 'outward' => '(partially) pays for', - 'editable' => false, + [ + 'name' => 'Paid', + 'inward' => 'is (partially) paid for by', + 'outward' => '(partially) pays for', + 'editable' => false, ], [ 'name' => 'Reimbursement', diff --git a/docker-compose.yml b/docker-compose.yml index 3e0311928b..deda288936 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,23 +4,14 @@ networks: driver: bridge services: firefly_iii_app: - environment: - - FF_DB_HOST=firefly_iii_db - - FF_DB_NAME=firefly - - FF_DB_USER=firefly - - FF_DB_PASSWORD=firefly - - FF_APP_KEY=S0m3R@nd0mStr1ngOf32Ch@rsEx@ctly - - FF_APP_ENV=local - - FF_DB_CONNECTION=pgsql - - TZ=Europe/Amsterdam - - APP_LOG_LEVEL=debug - image: jc5x/firefly-iii - links: + image: jc5x/firefly-iii:develop + depends_on: - firefly_iii_db networks: - firefly_iii_net ports: - "80:80" + env_file: .env volumes: - source: firefly_iii_export @@ -31,14 +22,14 @@ services: target: /var/www/firefly-iii/storage/upload type: volume firefly_iii_db: - environment: - - POSTGRES_PASSWORD=firefly - - POSTGRES_USER=firefly image: "postgres:10" + environment: + - POSTGRES_PASSWORD=secret_firefly_password + - POSTGRES_USER=firefly networks: - firefly_iii_net volumes: - - "firefly_iii_db:/var/lib/postgresql/data" + - firefly_iii_db:/var/lib/postgresql/data version: "3.2" volumes: firefly_iii_db: ~ diff --git a/.deploy/docker/.env.docker b/docker-variables.txt similarity index 61% rename from .deploy/docker/.env.docker rename to docker-variables.txt index 3da4a48d0a..443fb6d41d 100644 --- a/.deploy/docker/.env.docker +++ b/docker-variables.txt @@ -1,57 +1,57 @@ # You can leave this on "local". If you change it to production most console commands will ask for extra confirmation. # Never set it to "testing". -APP_ENV=${FF_APP_ENV} +APP_ENV=local # Set to true if you want to see debug information in error screens. -APP_DEBUG=${APP_DEBUG} +APP_DEBUG=false # This should be your email address -SITE_OWNER=${SITE_OWNER} +SITE_OWNER=mail@example.com -# The encryption key for your database and sessions. Keep this very secure. -# If you generate a new one all existing data must be considered LOST. +# The encryption key for your sessions. Keep this very secure. +# If you generate a new one existing data must be considered LOST. # Change it to a string of exactly 32 chars or use command `php artisan key:generate` to generate it -APP_KEY=${FF_APP_KEY} +APP_KEY=SomeRandomStringOf32CharsExactly # Change this value to your preferred time zone. # Example: Europe/Amsterdam -TZ=${TZ} +TZ=Europe/Amsterdam # This variable must match your installation's external address but keep in mind that # it's only used on the command line as a fallback value. -APP_URL=${APP_URL} +APP_URL=http://localhost # TRUSTED_PROXIES is a useful variable when using Docker and/or a reverse proxy. -TRUSTED_PROXIES=${TRUSTED_PROXIES} +# Set it to ** and reverse proxies work just fine. +TRUSTED_PROXIES= # The log channel defines where your log entries go to. # 'daily' is the default logging mode giving you 5 daily rotated log files in /storage/logs/. # Several other options exist. You can use 'single' for one big fat error log (not recommended). # Also available are 'syslog', 'errorlog' and 'stdout' which will log to the system itself. -LOG_CHANNEL=stdout +LOG_CHANNEL=daily # Log level. You can set this from least severe to most severe: # debug, info, notice, warning, error, critical, alert, emergency # If you set it to debug your logs will grow large, and fast. If you set it to emergency probably # nothing will get logged, ever. -APP_LOG_LEVEL=${APP_LOG_LEVEL} +APP_LOG_LEVEL=notice # Database credentials. Make sure the database exists. I recommend a dedicated user for Firefly III # For other database types, please see the FAQ: http://firefly-iii.readthedocs.io/en/latest/support/faq.html -DB_CONNECTION=${FF_DB_CONNECTION} -DB_HOST=${FF_DB_HOST} -DB_PORT=${FF_DB_PORT} -DB_DATABASE=${FF_DB_NAME} -DB_USERNAME=${FF_DB_USER} -DB_PASSWORD="${FF_DB_PASSWORD}" +DB_CONNECTION=pgsql +DB_HOST=127.0.0.1 +DB_PORT=5432 +DB_DATABASE=firefly +DB_USERNAME=firefly +DB_PASSWORD=secret_firefly_password # PostgreSQL supports SSL. You can configure it here. -PGSQL_SSL=${PGSQL_SSL} -PGSQL_SSL_MODE=${PGSQL_SSL_MODE} -PGSQL_SSL_ROOT_CERT=${PGSQL_SSL_ROOT_CERT} -PGSQL_SSL_CERT=${PGSQL_SSL_CERT} -PGSQL_SSL_KEY=${PGSQL_SSL_KEY} -PGSQL_SSL_CRL_FILE=${PGSQL_SSL_CRL_FILE} +PGSQL_SSL_MODE=prefer +PGSQL_SSL_ROOT_CERT=null +PGSQL_SSL_CERT=null +PGSQL_SSL_KEY=null +PGSQL_SSL_CRL_FILE=null # If you're looking for performance improvements, you could install memcached. CACHE_DRIVER=file @@ -60,15 +60,15 @@ SESSION_DRIVER=file # You can configure another file storage backend if you cannot use the local storage option. # To set this up, fill in the following variables. The upload path is used to store uploaded # files and the export path is to store exported data (before download). -SFTP_HOST=${SFTP_HOST} -SFTP_PORT=${SFTP_PORT} -SFTP_UPLOAD_PATH=${SFTP_UPLOAD_PATH} -SFTP_EXPORT_PATH=${SFTP_EXPORT_PATH} +SFTP_HOST= +SFTP_PORT= +SFTP_UPLOAD_PATH= +SFTP_EXPORT_PATH= # SFTP uses either the username/password combination or the private key to authenticate. -SFTP_USERNAME=${SFTP_USERNAME} -SFTP_PASSWORD="${SFTP_PASSWORD}" -SFTP_PRIV_KEY=${SFTP_PRIV_KEY} +SFTP_USERNAME= +SFTP_PASSWORD= +SFTP_PRIV_KEY= # Cookie settings. Should not be necessary to change these. COOKIE_PATH="/" @@ -77,90 +77,88 @@ COOKIE_SECURE=false # If you want Firefly III to mail you, update these settings # For instructions, see: https://firefly-iii.readthedocs.io/en/latest/installation/mail.html -MAIL_DRIVER=${MAIL_DRIVER} -MAIL_HOST=${MAIL_HOST} -MAIL_PORT=${MAIL_PORT} -MAIL_FROM=${MAIL_FROM} -MAIL_USERNAME=${MAIL_USERNAME} -MAIL_PASSWORD="${MAIL_PASSWORD}" -MAIL_ENCRYPTION=${MAIL_ENCRYPTION} +MAIL_DRIVER=log +MAIL_HOST=smtp.mailtrap.io +MAIL_PORT=2525 +MAIL_FROM=changeme@example.com +MAIL_USERNAME=null +MAIL_PASSWORD=null +MAIL_ENCRYPTION=null # Other mail drivers: -MAILGUN_DOMAIN=${MAILGUN_DOMAIN} -MAILGUN_SECRET=${MAILGUN_SECRET} -MANDRILL_SECRET=${MANDRILL_SECRET} -SPARKPOST_SECRET=${SPARKPOST_SECRET} +MAILGUN_DOMAIN= +MAILGUN_SECRET= +MANDRILL_SECRET= +SPARKPOST_SECRET= # Firefly III can send you the following messages SEND_REGISTRATION_MAIL=true -SEND_ERROR_MESSAGE=false +SEND_ERROR_MESSAGE=true # These messages contain (sensitive) transaction information: -SEND_REPORT_JOURNALS=${SEND_REPORT_JOURNALS} +SEND_REPORT_JOURNALS=true # Set a Mapbox API key here (see mapbox.com) so there might be a map available at various places. -MAPBOX_API_KEY=${MAPBOX_API_KEY} +MAPBOX_API_KEY= # Firefly III currently supports two provider for live Currency Exchange Rates: # "fixer" is the default (for backward compatibility), and "ratesapi" is the new one. # RatesApi.IO (see https://ratesapi.io) is a FREE and OPEN SOURCE live currency exchange rates, -# built compatible with Fixer.IO, based on data published by European Central Bank, and don't require API key. -CER_PROVIDER=${CER_PROVIDER} +# built compatible with Fixer.IO, based on data published by European Central Bank, and doesn't require API key. +CER_PROVIDER=ratesapi + # If you have select "fixer" as default currency exchange rates, # set a Fixer IO API key here (see https://fixer.io) to enable live currency exchange rates. # Please note that this WILL ONLY WORK FOR PAID fixer.io accounts because they severely limited # the free API up to the point where you might as well offer nothing. -FIXER_API_KEY=${FIXER_API_KEY} +FIXER_API_KEY= # If you wish to track your own behavior over Firefly III, set a valid analytics tracker ID here. -ANALYTICS_ID=${ANALYTICS_ID} - -# Most parts of the database are encrypted by default, but you can turn this off if you want to. -# This makes it easier to migrate your database. Not that some fields will never be decrypted. -USE_ENCRYPTION=true +ANALYTICS_ID= # Firefly III has two options for user authentication. "eloquent" is the default, # and "ldap" for LDAP servers. # For full instructions on these settings please visit: # https://firefly-iii.readthedocs.io/en/latest/installation/authentication.html -LOGIN_PROVIDER=${LOGIN_PROVIDER} +LOGIN_PROVIDER=eloquent # LDAP connection configuration -ADLDAP_CONNECTION_SCHEME=${ADLDAP_CONNECTION_SCHEME} -ADLDAP_AUTO_CONNECT=${ADLDAP_AUTO_CONNECT} +# OpenLDAP, FreeIPA or ActiveDirectory +ADLDAP_CONNECTION_SCHEME=OpenLDAP +ADLDAP_AUTO_CONNECT=true # LDAP connection settings -ADLDAP_CONTROLLERS=${ADLDAP_CONTROLLERS} -ADLDAP_PORT=${ADLDAP_PORT} -ADLDAP_TIMEOUT=${ADLDAP_TIMEOUT} -ADLDAP_BASEDN="${ADLDAP_BASEDN}" -ADLDAP_FOLLOW_REFFERALS=${ADLDAP_FOLLOW_REFFERALS} -ADLDAP_USE_SSL=${ADLDAP_USE_SSL} -ADLDAP_USE_TLS=${ADLDAP_USE_TLS} +ADLDAP_CONTROLLERS= +ADLDAP_PORT=389 +ADLDAP_TIMEOUT=5 +ADLDAP_BASEDN="" +ADLDAP_FOLLOW_REFFERALS=false +ADLDAP_USE_SSL=false +ADLDAP_USE_TLS=false -ADLDAP_ADMIN_USERNAME=${ADLDAP_ADMIN_USERNAME} -ADLDAP_ADMIN_PASSWORD="${ADLDAP_ADMIN_PASSWORD}" +ADLDAP_ADMIN_USERNAME= +ADLDAP_ADMIN_PASSWORD= -ADLDAP_ACCOUNT_PREFIX="${ADLDAP_ACCOUNT_PREFIX}" -ADLDAP_ACCOUNT_SUFFIX="${ADLDAP_ACCOUNT_SUFFIX}" +ADLDAP_ACCOUNT_PREFIX= +ADLDAP_ACCOUNT_SUFFIX= # LDAP authentication settings. -ADLDAP_PASSWORD_SYNC=${ADLDAP_PASSWORD_SYNC} -ADLDAP_LOGIN_FALLBACK=${ADLDAP_LOGIN_FALLBACK} +ADLDAP_PASSWORD_SYNC=false +ADLDAP_LOGIN_FALLBACK=false -ADLDAP_DISCOVER_FIELD=${ADLDAP_DISCOVER_FIELD} -ADLDAP_AUTH_FIELD=${ADLDAP_AUTH_FIELD} +ADLDAP_DISCOVER_FIELD=distinguishedname +ADLDAP_AUTH_FIELD=distinguishedname # Will allow SSO if your server provides an AUTH_USER field. -WINDOWS_SSO_DISCOVER=${WINDOWS_SSO_DISCOVER} -WINDOWS_SSO_KEY=${WINDOWS_SSO_KEY} +WINDOWS_SSO_DISCOVER=samaccountname +WINDOWS_SSO_KEY=AUTH_USER # field to sync as local username. -ADLDAP_SYNC_FIELD=${ADLDAP_SYNC_FIELD} +ADLDAP_SYNC_FIELD=userprincipalname # You can disable the X-Frame-Options header if it interfears with tools like # Organizr. This is at your own risk. -DISABLE_FRAME_HEADER=${DISABLE_FRAME_HEADER} +DISABLE_FRAME_HEADER=false # Leave the following configuration vars as is. # Unless you like to tinker and know what you're doing. @@ -178,7 +176,8 @@ PUSHER_SECRET= PUSHER_ID= DEMO_USERNAME= DEMO_PASSWORD= -IS_DOCKER=true +IS_DOCKER=false +USE_ENCRYPTION=false IS_SANDSTORM=false IS_HEROKU=false BUNQ_USE_SANDBOX=false diff --git a/package.json b/package.json index bd9df38cf5..844e314d26 100755 --- a/package.json +++ b/package.json @@ -10,15 +10,18 @@ "production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=node_modules/laravel-mix/setup/webpack.config.js" }, "devDependencies": { - "axios": "^0.17", + "axios": "^0.18.1", "bootstrap-sass": "^3.3.7", "cross-env": "^5.1", - "jquery": "^3.1.1", - "laravel-mix": "^1.0", - "lodash": "^4.17.4", - "vue": "^2.5.7" + "laravel-mix": "^4.1.2", + "lodash": "^4.17.13", + "vue": "^2.6.10", + "vue-template-compiler": "^2.6.10" }, "dependencies": { - "font-awesome": "^4.7.0" + "@johmun/vue-tags-input": "^2.0.1", + "font-awesome": "^4.7.0", + "jquery": "^3.1.1", + "uiv": "^0.31.5" } } diff --git a/phpunit.coverage.specific.xml b/phpunit.coverage.specific.xml index da7d5b821d..adacd4f17f 100644 --- a/phpunit.coverage.specific.xml +++ b/phpunit.coverage.specific.xml @@ -32,15 +32,14 @@ - - ./tests/Feature + + ./tests/Api - ./tests/Unit - - ./tests/Api + + ./tests/Feature diff --git a/phpunit.coverage.xml b/phpunit.coverage.xml index 60bc506b70..ef98e08323 100644 --- a/phpunit.coverage.xml +++ b/phpunit.coverage.xml @@ -32,15 +32,14 @@ - - ./tests/Feature + + ./tests/Api - ./tests/Unit - - ./tests/Api + + ./tests/Feature diff --git a/phpunit.xml b/phpunit.xml index 736d18a486..c0781ce0a5 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -32,15 +32,14 @@ - - ./tests/Feature + + ./tests/Api - ./tests/Unit - - ./tests/Api + + ./tests/Feature diff --git a/public/mix-manifest.json b/public/mix-manifest.json index 116bc8d4ed..d3a0ddf03e 100644 --- a/public/mix-manifest.json +++ b/public/mix-manifest.json @@ -1,7 +1,3 @@ { - "/v2/js/index.js": "/v2/js/index.js", - "/v2/js/manifest.js": "/v2/js/manifest.js", - "/v2/js/vendor.js": "/v2/js/vendor.js", - "/undefined.js": "/undefined.js", - "/v2/css/app.css": "/v2/css/app.css" + "/v1/js/app.js": "/v1/js/app.js" } diff --git a/public/v1/css/app.css b/public/v1/css/app.css new file mode 100644 index 0000000000..8971d6c324 --- /dev/null +++ b/public/v1/css/app.css @@ -0,0 +1,2 @@ +/* TODO REMOVE ME */ + diff --git a/public/v1/css/firefly.css b/public/v1/css/firefly.css index 8e64155a3c..92ef770452 100644 --- a/public/v1/css/firefly.css +++ b/public/v1/css/firefly.css @@ -18,6 +18,18 @@ * along with Firefly III. If not, see . */ + +.no-margin-pagination {padding-bottom:0;padding-top:0;} +.no-margin-pagination ul.pagination {margin:0 !important;} + + +input.ti-new-tag-input { + font-size: 14px !important; + line-height: 1.42857143; + color: #555; + font-family:"Source Sans Pro", "Helvetica Neue",Helvetica,Arial,sans-serif !important; +} + .split_amount_input { width: 40%; border-radius: 0; @@ -36,6 +48,13 @@ } +.autocomplete-suggestions { border: 1px solid #999; background: #FFF; overflow: auto; } +.autocomplete-suggestion { padding: 2px 5px; white-space: nowrap; overflow: hidden; } +.autocomplete-selected { background: #F0F0F0; } +.autocomplete-suggestions strong { font-weight: normal; color: #3399FF; } +.autocomplete-group { padding: 2px 5px; font-weight: bold;} +.autocomplete-group strong { display: block; border-bottom: 1px solid #000; } + .split_amount_input:focus { border-color: #98cbe8; outline: 0; @@ -61,7 +80,7 @@ .general-chart-error { height: 30px; - background: url('/images/error.png') no-repeat center center; + background: url('/v1/images/error.png') no-repeat center center; } p.tagcloud .label { diff --git a/public/v1/js/app.js b/public/v1/js/app.js index 5b86336397..8a4478589d 100644 --- a/public/v1/js/app.js +++ b/public/v1/js/app.js @@ -1 +1 @@ -!function(t){var e={};function n(r){if(e[r])return e[r].exports;var i=e[r]={i:r,l:!1,exports:{}};return t[r].call(i.exports,i,i.exports,n),i.l=!0,i.exports}n.m=t,n.c=e,n.d=function(t,e,r){n.o(t,e)||Object.defineProperty(t,e,{configurable:!1,enumerable:!0,get:r})},n.n=function(t){var e=t&&t.__esModule?function(){return t.default}:function(){return t};return n.d(e,"a",e),e},n.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},n.p="",n(n.s=12)}([function(t,e,n){"use strict";var r=n(6),i=n(21),o=Object.prototype.toString;function a(t){return"[object Array]"===o.call(t)}function s(t){return null!==t&&"object"==typeof t}function u(t){return"[object Function]"===o.call(t)}function c(t,e){if(null!==t&&void 0!==t)if("object"!=typeof t&&(t=[t]),a(t))for(var n=0,r=t.length;n=200&&t<300}};u.headers={common:{Accept:"application/json, text/plain, */*"}},r.forEach(["delete","get","head"],function(t){u.headers[t]={}}),r.forEach(["post","put","patch"],function(t){u.headers[t]=r.merge(o)}),t.exports=u}).call(e,n(7))},function(t,e){t.exports=function(t){var e=[];return e.toString=function(){return this.map(function(e){var n=function(t,e){var n=t[1]||"",r=t[3];if(!r)return n;if(e&&"function"==typeof btoa){var i=(a=r,"/*# sourceMappingURL=data:application/json;charset=utf-8;base64,"+btoa(unescape(encodeURIComponent(JSON.stringify(a))))+" */"),o=r.sources.map(function(t){return"/*# sourceURL="+r.sourceRoot+t+" */"});return[n].concat(o).concat([i]).join("\n")}var a;return[n].join("\n")}(e,t);return e[2]?"@media "+e[2]+"{"+n+"}":n}).join("")},e.i=function(t,n){"string"==typeof t&&(t=[[null,t,""]]);for(var r={},i=0;in.parts.length&&(r.parts.length=n.parts.length)}else{var a=[];for(i=0;i1)for(var n=1;n>>1,q=[["ary",T],["bind",g],["bindKey",y],["curry",_],["curryRight",w],["flip",$],["partial",x],["partialRight",C],["rearg",k]],U="[object Arguments]",B="[object Array]",H="[object AsyncFunction]",W="[object Boolean]",z="[object Date]",V="[object DOMException]",X="[object Error]",K="[object Function]",J="[object GeneratorFunction]",G="[object Map]",Q="[object Number]",Y="[object Null]",Z="[object Object]",tt="[object Proxy]",et="[object RegExp]",nt="[object Set]",rt="[object String]",it="[object Symbol]",ot="[object Undefined]",at="[object WeakMap]",st="[object WeakSet]",ut="[object ArrayBuffer]",ct="[object DataView]",lt="[object Float32Array]",ft="[object Float64Array]",pt="[object Int8Array]",dt="[object Int16Array]",ht="[object Int32Array]",vt="[object Uint8Array]",mt="[object Uint8ClampedArray]",gt="[object Uint16Array]",yt="[object Uint32Array]",bt=/\b__p \+= '';/g,_t=/\b(__p \+=) '' \+/g,wt=/(__e\(.*?\)|\b__t\)) \+\n'';/g,xt=/&(?:amp|lt|gt|quot|#39);/g,Ct=/[&<>"']/g,Tt=RegExp(xt.source),kt=RegExp(Ct.source),$t=/<%-([\s\S]+?)%>/g,St=/<%([\s\S]+?)%>/g,At=/<%=([\s\S]+?)%>/g,Et=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,Ot=/^\w*$/,jt=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g,Nt=/[\\^$.*+?()[\]{}|]/g,Dt=RegExp(Nt.source),It=/^\s+|\s+$/g,Lt=/^\s+/,Rt=/\s+$/,Pt=/\{(?:\n\/\* \[wrapped with .+\] \*\/)?\n?/,Ft=/\{\n\/\* \[wrapped with (.+)\] \*/,Mt=/,? & /,qt=/[^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+/g,Ut=/\\(\\)?/g,Bt=/\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g,Ht=/\w*$/,Wt=/^[-+]0x[0-9a-f]+$/i,zt=/^0b[01]+$/i,Vt=/^\[object .+?Constructor\]$/,Xt=/^0o[0-7]+$/i,Kt=/^(?:0|[1-9]\d*)$/,Jt=/[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g,Gt=/($^)/,Qt=/['\n\r\u2028\u2029\\]/g,Yt="\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff",Zt="\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000",te="[\\ud800-\\udfff]",ee="["+Zt+"]",ne="["+Yt+"]",re="\\d+",ie="[\\u2700-\\u27bf]",oe="[a-z\\xdf-\\xf6\\xf8-\\xff]",ae="[^\\ud800-\\udfff"+Zt+re+"\\u2700-\\u27bfa-z\\xdf-\\xf6\\xf8-\\xffA-Z\\xc0-\\xd6\\xd8-\\xde]",se="\\ud83c[\\udffb-\\udfff]",ue="[^\\ud800-\\udfff]",ce="(?:\\ud83c[\\udde6-\\uddff]){2}",le="[\\ud800-\\udbff][\\udc00-\\udfff]",fe="[A-Z\\xc0-\\xd6\\xd8-\\xde]",pe="(?:"+oe+"|"+ae+")",de="(?:"+fe+"|"+ae+")",he="(?:"+ne+"|"+se+")"+"?",ve="[\\ufe0e\\ufe0f]?"+he+("(?:\\u200d(?:"+[ue,ce,le].join("|")+")[\\ufe0e\\ufe0f]?"+he+")*"),me="(?:"+[ie,ce,le].join("|")+")"+ve,ge="(?:"+[ue+ne+"?",ne,ce,le,te].join("|")+")",ye=RegExp("['’]","g"),be=RegExp(ne,"g"),_e=RegExp(se+"(?="+se+")|"+ge+ve,"g"),we=RegExp([fe+"?"+oe+"+(?:['’](?:d|ll|m|re|s|t|ve))?(?="+[ee,fe,"$"].join("|")+")",de+"+(?:['’](?:D|LL|M|RE|S|T|VE))?(?="+[ee,fe+pe,"$"].join("|")+")",fe+"?"+pe+"+(?:['’](?:d|ll|m|re|s|t|ve))?",fe+"+(?:['’](?:D|LL|M|RE|S|T|VE))?","\\d*(?:1ST|2ND|3RD|(?![123])\\dTH)(?=\\b|[a-z_])","\\d*(?:1st|2nd|3rd|(?![123])\\dth)(?=\\b|[A-Z_])",re,me].join("|"),"g"),xe=RegExp("[\\u200d\\ud800-\\udfff"+Yt+"\\ufe0e\\ufe0f]"),Ce=/[a-z][A-Z]|[A-Z]{2}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/,Te=["Array","Buffer","DataView","Date","Error","Float32Array","Float64Array","Function","Int8Array","Int16Array","Int32Array","Map","Math","Object","Promise","RegExp","Set","String","Symbol","TypeError","Uint8Array","Uint8ClampedArray","Uint16Array","Uint32Array","WeakMap","_","clearTimeout","isFinite","parseInt","setTimeout"],ke=-1,$e={};$e[lt]=$e[ft]=$e[pt]=$e[dt]=$e[ht]=$e[vt]=$e[mt]=$e[gt]=$e[yt]=!0,$e[U]=$e[B]=$e[ut]=$e[W]=$e[ct]=$e[z]=$e[X]=$e[K]=$e[G]=$e[Q]=$e[Z]=$e[et]=$e[nt]=$e[rt]=$e[at]=!1;var Se={};Se[U]=Se[B]=Se[ut]=Se[ct]=Se[W]=Se[z]=Se[lt]=Se[ft]=Se[pt]=Se[dt]=Se[ht]=Se[G]=Se[Q]=Se[Z]=Se[et]=Se[nt]=Se[rt]=Se[it]=Se[vt]=Se[mt]=Se[gt]=Se[yt]=!0,Se[X]=Se[K]=Se[at]=!1;var Ae={"\\":"\\","'":"'","\n":"n","\r":"r","\u2028":"u2028","\u2029":"u2029"},Ee=parseFloat,Oe=parseInt,je="object"==typeof t&&t&&t.Object===Object&&t,Ne="object"==typeof self&&self&&self.Object===Object&&self,De=je||Ne||Function("return this")(),Ie="object"==typeof e&&e&&!e.nodeType&&e,Le=Ie&&"object"==typeof r&&r&&!r.nodeType&&r,Re=Le&&Le.exports===Ie,Pe=Re&&je.process,Fe=function(){try{var t=Le&&Le.require&&Le.require("util").types;return t||Pe&&Pe.binding&&Pe.binding("util")}catch(t){}}(),Me=Fe&&Fe.isArrayBuffer,qe=Fe&&Fe.isDate,Ue=Fe&&Fe.isMap,Be=Fe&&Fe.isRegExp,He=Fe&&Fe.isSet,We=Fe&&Fe.isTypedArray;function ze(t,e,n){switch(n.length){case 0:return t.call(e);case 1:return t.call(e,n[0]);case 2:return t.call(e,n[0],n[1]);case 3:return t.call(e,n[0],n[1],n[2])}return t.apply(e,n)}function Ve(t,e,n,r){for(var i=-1,o=null==t?0:t.length;++i-1}function Ye(t,e,n){for(var r=-1,i=null==t?0:t.length;++r-1;);return n}function wn(t,e){for(var n=t.length;n--&&un(e,t[n],0)>-1;);return n}var xn=dn({"À":"A","Á":"A","Â":"A","Ã":"A","Ä":"A","Å":"A","à":"a","á":"a","â":"a","ã":"a","ä":"a","å":"a","Ç":"C","ç":"c","Ð":"D","ð":"d","È":"E","É":"E","Ê":"E","Ë":"E","è":"e","é":"e","ê":"e","ë":"e","Ì":"I","Í":"I","Î":"I","Ï":"I","ì":"i","í":"i","î":"i","ï":"i","Ñ":"N","ñ":"n","Ò":"O","Ó":"O","Ô":"O","Õ":"O","Ö":"O","Ø":"O","ò":"o","ó":"o","ô":"o","õ":"o","ö":"o","ø":"o","Ù":"U","Ú":"U","Û":"U","Ü":"U","ù":"u","ú":"u","û":"u","ü":"u","Ý":"Y","ý":"y","ÿ":"y","Æ":"Ae","æ":"ae","Þ":"Th","þ":"th","ß":"ss","Ā":"A","Ă":"A","Ą":"A","ā":"a","ă":"a","ą":"a","Ć":"C","Ĉ":"C","Ċ":"C","Č":"C","ć":"c","ĉ":"c","ċ":"c","č":"c","Ď":"D","Đ":"D","ď":"d","đ":"d","Ē":"E","Ĕ":"E","Ė":"E","Ę":"E","Ě":"E","ē":"e","ĕ":"e","ė":"e","ę":"e","ě":"e","Ĝ":"G","Ğ":"G","Ġ":"G","Ģ":"G","ĝ":"g","ğ":"g","ġ":"g","ģ":"g","Ĥ":"H","Ħ":"H","ĥ":"h","ħ":"h","Ĩ":"I","Ī":"I","Ĭ":"I","Į":"I","İ":"I","ĩ":"i","ī":"i","ĭ":"i","į":"i","ı":"i","Ĵ":"J","ĵ":"j","Ķ":"K","ķ":"k","ĸ":"k","Ĺ":"L","Ļ":"L","Ľ":"L","Ŀ":"L","Ł":"L","ĺ":"l","ļ":"l","ľ":"l","ŀ":"l","ł":"l","Ń":"N","Ņ":"N","Ň":"N","Ŋ":"N","ń":"n","ņ":"n","ň":"n","ŋ":"n","Ō":"O","Ŏ":"O","Ő":"O","ō":"o","ŏ":"o","ő":"o","Ŕ":"R","Ŗ":"R","Ř":"R","ŕ":"r","ŗ":"r","ř":"r","Ś":"S","Ŝ":"S","Ş":"S","Š":"S","ś":"s","ŝ":"s","ş":"s","š":"s","Ţ":"T","Ť":"T","Ŧ":"T","ţ":"t","ť":"t","ŧ":"t","Ũ":"U","Ū":"U","Ŭ":"U","Ů":"U","Ű":"U","Ų":"U","ũ":"u","ū":"u","ŭ":"u","ů":"u","ű":"u","ų":"u","Ŵ":"W","ŵ":"w","Ŷ":"Y","ŷ":"y","Ÿ":"Y","Ź":"Z","Ż":"Z","Ž":"Z","ź":"z","ż":"z","ž":"z","IJ":"IJ","ij":"ij","Œ":"Oe","œ":"oe","ʼn":"'n","ſ":"s"}),Cn=dn({"&":"&","<":"<",">":">",'"':""","'":"'"});function Tn(t){return"\\"+Ae[t]}function kn(t){return xe.test(t)}function $n(t){var e=-1,n=Array(t.size);return t.forEach(function(t,r){n[++e]=[r,t]}),n}function Sn(t,e){return function(n){return t(e(n))}}function An(t,e){for(var n=-1,r=t.length,i=0,o=[];++n",""":'"',"'":"'"});var In=function t(e){var n,r=(e=null==e?De:In.defaults(De.Object(),e,In.pick(De,Te))).Array,i=e.Date,Yt=e.Error,Zt=e.Function,te=e.Math,ee=e.Object,ne=e.RegExp,re=e.String,ie=e.TypeError,oe=r.prototype,ae=Zt.prototype,se=ee.prototype,ue=e["__core-js_shared__"],ce=ae.toString,le=se.hasOwnProperty,fe=0,pe=(n=/[^.]+$/.exec(ue&&ue.keys&&ue.keys.IE_PROTO||""))?"Symbol(src)_1."+n:"",de=se.toString,he=ce.call(ee),ve=De._,me=ne("^"+ce.call(le).replace(Nt,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$"),ge=Re?e.Buffer:o,_e=e.Symbol,xe=e.Uint8Array,Ae=ge?ge.allocUnsafe:o,je=Sn(ee.getPrototypeOf,ee),Ne=ee.create,Ie=se.propertyIsEnumerable,Le=oe.splice,Pe=_e?_e.isConcatSpreadable:o,Fe=_e?_e.iterator:o,on=_e?_e.toStringTag:o,dn=function(){try{var t=Mo(ee,"defineProperty");return t({},"",{}),t}catch(t){}}(),Ln=e.clearTimeout!==De.clearTimeout&&e.clearTimeout,Rn=i&&i.now!==De.Date.now&&i.now,Pn=e.setTimeout!==De.setTimeout&&e.setTimeout,Fn=te.ceil,Mn=te.floor,qn=ee.getOwnPropertySymbols,Un=ge?ge.isBuffer:o,Bn=e.isFinite,Hn=oe.join,Wn=Sn(ee.keys,ee),zn=te.max,Vn=te.min,Xn=i.now,Kn=e.parseInt,Jn=te.random,Gn=oe.reverse,Qn=Mo(e,"DataView"),Yn=Mo(e,"Map"),Zn=Mo(e,"Promise"),tr=Mo(e,"Set"),er=Mo(e,"WeakMap"),nr=Mo(ee,"create"),rr=er&&new er,ir={},or=fa(Qn),ar=fa(Yn),sr=fa(Zn),ur=fa(tr),cr=fa(er),lr=_e?_e.prototype:o,fr=lr?lr.valueOf:o,pr=lr?lr.toString:o;function dr(t){if(As(t)&&!gs(t)&&!(t instanceof gr)){if(t instanceof mr)return t;if(le.call(t,"__wrapped__"))return pa(t)}return new mr(t)}var hr=function(){function t(){}return function(e){if(!Ss(e))return{};if(Ne)return Ne(e);t.prototype=e;var n=new t;return t.prototype=o,n}}();function vr(){}function mr(t,e){this.__wrapped__=t,this.__actions__=[],this.__chain__=!!e,this.__index__=0,this.__values__=o}function gr(t){this.__wrapped__=t,this.__actions__=[],this.__dir__=1,this.__filtered__=!1,this.__iteratees__=[],this.__takeCount__=P,this.__views__=[]}function yr(t){var e=-1,n=null==t?0:t.length;for(this.clear();++e=e?t:e)),t}function Lr(t,e,n,r,i,a){var s,u=e&p,c=e&d,l=e&h;if(n&&(s=i?n(t,r,i,a):n(t)),s!==o)return s;if(!Ss(t))return t;var f=gs(t);if(f){if(s=function(t){var e=t.length,n=new t.constructor(e);return e&&"string"==typeof t[0]&&le.call(t,"index")&&(n.index=t.index,n.input=t.input),n}(t),!u)return no(t,s)}else{var v=Bo(t),m=v==K||v==J;if(ws(t))return Gi(t,u);if(v==Z||v==U||m&&!i){if(s=c||m?{}:Wo(t),!u)return c?function(t,e){return ro(t,Uo(t),e)}(t,function(t,e){return t&&ro(e,ou(e),t)}(s,t)):function(t,e){return ro(t,qo(t),e)}(t,jr(s,t))}else{if(!Se[v])return i?t:{};s=function(t,e,n){var r,i,o,a=t.constructor;switch(e){case ut:return Qi(t);case W:case z:return new a(+t);case ct:return function(t,e){var n=e?Qi(t.buffer):t.buffer;return new t.constructor(n,t.byteOffset,t.byteLength)}(t,n);case lt:case ft:case pt:case dt:case ht:case vt:case mt:case gt:case yt:return Yi(t,n);case G:return new a;case Q:case rt:return new a(t);case et:return(o=new(i=t).constructor(i.source,Ht.exec(i))).lastIndex=i.lastIndex,o;case nt:return new a;case it:return r=t,fr?ee(fr.call(r)):{}}}(t,v,u)}}a||(a=new xr);var g=a.get(t);if(g)return g;if(a.set(t,s),Ds(t))return t.forEach(function(r){s.add(Lr(r,e,n,r,t,a))}),s;if(Es(t))return t.forEach(function(r,i){s.set(i,Lr(r,e,n,i,t,a))}),s;var y=f?o:(l?c?No:jo:c?ou:iu)(t);return Xe(y||t,function(r,i){y&&(r=t[i=r]),Ar(s,i,Lr(r,e,n,i,t,a))}),s}function Rr(t,e,n){var r=n.length;if(null==t)return!r;for(t=ee(t);r--;){var i=n[r],a=e[i],s=t[i];if(s===o&&!(i in t)||!a(s))return!1}return!0}function Pr(t,e,n){if("function"!=typeof t)throw new ie(u);return ia(function(){t.apply(o,n)},e)}function Fr(t,e,n,r){var i=-1,o=Qe,s=!0,u=t.length,c=[],l=e.length;if(!u)return c;n&&(e=Ze(e,gn(n))),r?(o=Ye,s=!1):e.length>=a&&(o=bn,s=!1,e=new wr(e));t:for(;++i-1},br.prototype.set=function(t,e){var n=this.__data__,r=Er(n,t);return r<0?(++this.size,n.push([t,e])):n[r][1]=e,this},_r.prototype.clear=function(){this.size=0,this.__data__={hash:new yr,map:new(Yn||br),string:new yr}},_r.prototype.delete=function(t){var e=Po(this,t).delete(t);return this.size-=e?1:0,e},_r.prototype.get=function(t){return Po(this,t).get(t)},_r.prototype.has=function(t){return Po(this,t).has(t)},_r.prototype.set=function(t,e){var n=Po(this,t),r=n.size;return n.set(t,e),this.size+=n.size==r?0:1,this},wr.prototype.add=wr.prototype.push=function(t){return this.__data__.set(t,c),this},wr.prototype.has=function(t){return this.__data__.has(t)},xr.prototype.clear=function(){this.__data__=new br,this.size=0},xr.prototype.delete=function(t){var e=this.__data__,n=e.delete(t);return this.size=e.size,n},xr.prototype.get=function(t){return this.__data__.get(t)},xr.prototype.has=function(t){return this.__data__.has(t)},xr.prototype.set=function(t,e){var n=this.__data__;if(n instanceof br){var r=n.__data__;if(!Yn||r.length0&&n(s)?e>1?Wr(s,e-1,n,r,i):tn(i,s):r||(i[i.length]=s)}return i}var zr=so(),Vr=so(!0);function Xr(t,e){return t&&zr(t,e,iu)}function Kr(t,e){return t&&Vr(t,e,iu)}function Jr(t,e){return Ge(e,function(e){return Ts(t[e])})}function Gr(t,e){for(var n=0,r=(e=Vi(e,t)).length;null!=t&&ne}function ti(t,e){return null!=t&&le.call(t,e)}function ei(t,e){return null!=t&&e in ee(t)}function ni(t,e,n){for(var i=n?Ye:Qe,a=t[0].length,s=t.length,u=s,c=r(s),l=1/0,f=[];u--;){var p=t[u];u&&e&&(p=Ze(p,gn(e))),l=Vn(p.length,l),c[u]=!n&&(e||a>=120&&p.length>=120)?new wr(u&&p):o}p=t[0];var d=-1,h=c[0];t:for(;++d=s)return u;var c=n[r];return u*("desc"==c?-1:1)}}return t.index-e.index}(t,e,n)})}function yi(t,e,n){for(var r=-1,i=e.length,o={};++r-1;)s!==t&&Le.call(s,u,1),Le.call(t,u,1);return t}function _i(t,e){for(var n=t?e.length:0,r=n-1;n--;){var i=e[n];if(n==r||i!==o){var o=i;Vo(i)?Le.call(t,i,1):Fi(t,i)}}return t}function wi(t,e){return t+Mn(Jn()*(e-t+1))}function xi(t,e){var n="";if(!t||e<1||e>I)return n;do{e%2&&(n+=t),(e=Mn(e/2))&&(t+=t)}while(e);return n}function Ci(t,e){return oa(ta(t,e,Ou),t+"")}function Ti(t){return Tr(du(t))}function ki(t,e){var n=du(t);return ua(n,Ir(e,0,n.length))}function $i(t,e,n,r){if(!Ss(t))return t;for(var i=-1,a=(e=Vi(e,t)).length,s=a-1,u=t;null!=u&&++io?0:o+e),(n=n>o?o:n)<0&&(n+=o),o=e>n?0:n-e>>>0,e>>>=0;for(var a=r(o);++i>>1,a=t[o];null!==a&&!Ls(a)&&(n?a<=e:a=a){var l=e?null:Co(t);if(l)return En(l);s=!1,i=bn,c=new wr}else c=e?[]:u;t:for(;++r=r?t:Oi(t,e,n)}var Ji=Ln||function(t){return De.clearTimeout(t)};function Gi(t,e){if(e)return t.slice();var n=t.length,r=Ae?Ae(n):new t.constructor(n);return t.copy(r),r}function Qi(t){var e=new t.constructor(t.byteLength);return new xe(e).set(new xe(t)),e}function Yi(t,e){var n=e?Qi(t.buffer):t.buffer;return new t.constructor(n,t.byteOffset,t.length)}function Zi(t,e){if(t!==e){var n=t!==o,r=null===t,i=t==t,a=Ls(t),s=e!==o,u=null===e,c=e==e,l=Ls(e);if(!u&&!l&&!a&&t>e||a&&s&&c&&!u&&!l||r&&s&&c||!n&&c||!i)return 1;if(!r&&!a&&!l&&t1?n[i-1]:o,s=i>2?n[2]:o;for(a=t.length>3&&"function"==typeof a?(i--,a):o,s&&Xo(n[0],n[1],s)&&(a=i<3?o:a,i=1),e=ee(e);++r-1?i[a?e[s]:s]:o}}function po(t){return Oo(function(e){var n=e.length,r=n,i=mr.prototype.thru;for(t&&e.reverse();r--;){var a=e[r];if("function"!=typeof a)throw new ie(u);if(i&&!s&&"wrapper"==Io(a))var s=new mr([],!0)}for(r=s?r:n;++r1&&_.reverse(),p&&lu))return!1;var l=a.get(t);if(l&&a.get(e))return l==e;var f=-1,p=!0,d=n&m?new wr:o;for(a.set(t,e),a.set(e,t);++f-1&&t%1==0&&t1?"& ":"")+e[r],e=e.join(n>2?", ":" "),t.replace(Pt,"{\n/* [wrapped with "+e+"] */\n")}(r,function(t,e){return Xe(q,function(n){var r="_."+n[0];e&n[1]&&!Qe(t,r)&&t.push(r)}),t.sort()}(function(t){var e=t.match(Ft);return e?e[1].split(Mt):[]}(r),n)))}function sa(t){var e=0,n=0;return function(){var r=Xn(),i=O-(r-n);if(n=r,i>0){if(++e>=E)return arguments[0]}else e=0;return t.apply(o,arguments)}}function ua(t,e){var n=-1,r=t.length,i=r-1;for(e=e===o?r:e;++n1?t[e-1]:o;return Na(t,n="function"==typeof n?(t.pop(),n):o)});function Ma(t){var e=dr(t);return e.__chain__=!0,e}function qa(t,e){return e(t)}var Ua=Oo(function(t){var e=t.length,n=e?t[0]:0,r=this.__wrapped__,i=function(e){return Dr(e,t)};return!(e>1||this.__actions__.length)&&r instanceof gr&&Vo(n)?((r=r.slice(n,+n+(e?1:0))).__actions__.push({func:qa,args:[i],thisArg:o}),new mr(r,this.__chain__).thru(function(t){return e&&!t.length&&t.push(o),t})):this.thru(i)});var Ba=io(function(t,e,n){le.call(t,n)?++t[n]:Nr(t,n,1)});var Ha=fo(ma),Wa=fo(ga);function za(t,e){return(gs(t)?Xe:Mr)(t,Ro(e,3))}function Va(t,e){return(gs(t)?Ke:qr)(t,Ro(e,3))}var Xa=io(function(t,e,n){le.call(t,n)?t[n].push(e):Nr(t,n,[e])});var Ka=Ci(function(t,e,n){var i=-1,o="function"==typeof e,a=bs(t)?r(t.length):[];return Mr(t,function(t){a[++i]=o?ze(e,t,n):ri(t,e,n)}),a}),Ja=io(function(t,e,n){Nr(t,n,e)});function Ga(t,e){return(gs(t)?Ze:pi)(t,Ro(e,3))}var Qa=io(function(t,e,n){t[n?0:1].push(e)},function(){return[[],[]]});var Ya=Ci(function(t,e){if(null==t)return[];var n=e.length;return n>1&&Xo(t,e[0],e[1])?e=[]:n>2&&Xo(e[0],e[1],e[2])&&(e=[e[0]]),gi(t,Wr(e,1),[])}),Za=Rn||function(){return De.Date.now()};function ts(t,e,n){return e=n?o:e,e=t&&null==e?t.length:e,ko(t,T,o,o,o,o,e)}function es(t,e){var n;if("function"!=typeof e)throw new ie(u);return t=Us(t),function(){return--t>0&&(n=e.apply(this,arguments)),t<=1&&(e=o),n}}var ns=Ci(function(t,e,n){var r=g;if(n.length){var i=An(n,Lo(ns));r|=x}return ko(t,r,e,n,i)}),rs=Ci(function(t,e,n){var r=g|y;if(n.length){var i=An(n,Lo(rs));r|=x}return ko(e,r,t,n,i)});function is(t,e,n){var r,i,a,s,c,l,f=0,p=!1,d=!1,h=!0;if("function"!=typeof t)throw new ie(u);function v(e){var n=r,a=i;return r=i=o,f=e,s=t.apply(a,n)}function m(t){var n=t-l;return l===o||n>=e||n<0||d&&t-f>=a}function g(){var t=Za();if(m(t))return y(t);c=ia(g,function(t){var n=e-(t-l);return d?Vn(n,a-(t-f)):n}(t))}function y(t){return c=o,h&&r?v(t):(r=i=o,s)}function b(){var t=Za(),n=m(t);if(r=arguments,i=this,l=t,n){if(c===o)return function(t){return f=t,c=ia(g,e),p?v(t):s}(l);if(d)return c=ia(g,e),v(l)}return c===o&&(c=ia(g,e)),s}return e=Hs(e)||0,Ss(n)&&(p=!!n.leading,a=(d="maxWait"in n)?zn(Hs(n.maxWait)||0,e):a,h="trailing"in n?!!n.trailing:h),b.cancel=function(){c!==o&&Ji(c),f=0,r=l=i=c=o},b.flush=function(){return c===o?s:y(Za())},b}var os=Ci(function(t,e){return Pr(t,1,e)}),as=Ci(function(t,e,n){return Pr(t,Hs(e)||0,n)});function ss(t,e){if("function"!=typeof t||null!=e&&"function"!=typeof e)throw new ie(u);var n=function(){var r=arguments,i=e?e.apply(this,r):r[0],o=n.cache;if(o.has(i))return o.get(i);var a=t.apply(this,r);return n.cache=o.set(i,a)||o,a};return n.cache=new(ss.Cache||_r),n}function us(t){if("function"!=typeof t)throw new ie(u);return function(){var e=arguments;switch(e.length){case 0:return!t.call(this);case 1:return!t.call(this,e[0]);case 2:return!t.call(this,e[0],e[1]);case 3:return!t.call(this,e[0],e[1],e[2])}return!t.apply(this,e)}}ss.Cache=_r;var cs=Xi(function(t,e){var n=(e=1==e.length&&gs(e[0])?Ze(e[0],gn(Ro())):Ze(Wr(e,1),gn(Ro()))).length;return Ci(function(r){for(var i=-1,o=Vn(r.length,n);++i=e}),ms=ii(function(){return arguments}())?ii:function(t){return As(t)&&le.call(t,"callee")&&!Ie.call(t,"callee")},gs=r.isArray,ys=Me?gn(Me):function(t){return As(t)&&Yr(t)==ut};function bs(t){return null!=t&&$s(t.length)&&!Ts(t)}function _s(t){return As(t)&&bs(t)}var ws=Un||Hu,xs=qe?gn(qe):function(t){return As(t)&&Yr(t)==z};function Cs(t){if(!As(t))return!1;var e=Yr(t);return e==X||e==V||"string"==typeof t.message&&"string"==typeof t.name&&!js(t)}function Ts(t){if(!Ss(t))return!1;var e=Yr(t);return e==K||e==J||e==H||e==tt}function ks(t){return"number"==typeof t&&t==Us(t)}function $s(t){return"number"==typeof t&&t>-1&&t%1==0&&t<=I}function Ss(t){var e=typeof t;return null!=t&&("object"==e||"function"==e)}function As(t){return null!=t&&"object"==typeof t}var Es=Ue?gn(Ue):function(t){return As(t)&&Bo(t)==G};function Os(t){return"number"==typeof t||As(t)&&Yr(t)==Q}function js(t){if(!As(t)||Yr(t)!=Z)return!1;var e=je(t);if(null===e)return!0;var n=le.call(e,"constructor")&&e.constructor;return"function"==typeof n&&n instanceof n&&ce.call(n)==he}var Ns=Be?gn(Be):function(t){return As(t)&&Yr(t)==et};var Ds=He?gn(He):function(t){return As(t)&&Bo(t)==nt};function Is(t){return"string"==typeof t||!gs(t)&&As(t)&&Yr(t)==rt}function Ls(t){return"symbol"==typeof t||As(t)&&Yr(t)==it}var Rs=We?gn(We):function(t){return As(t)&&$s(t.length)&&!!$e[Yr(t)]};var Ps=_o(fi),Fs=_o(function(t,e){return t<=e});function Ms(t){if(!t)return[];if(bs(t))return Is(t)?Nn(t):no(t);if(Fe&&t[Fe])return function(t){for(var e,n=[];!(e=t.next()).done;)n.push(e.value);return n}(t[Fe]());var e=Bo(t);return(e==G?$n:e==nt?En:du)(t)}function qs(t){return t?(t=Hs(t))===D||t===-D?(t<0?-1:1)*L:t==t?t:0:0===t?t:0}function Us(t){var e=qs(t),n=e%1;return e==e?n?e-n:e:0}function Bs(t){return t?Ir(Us(t),0,P):0}function Hs(t){if("number"==typeof t)return t;if(Ls(t))return R;if(Ss(t)){var e="function"==typeof t.valueOf?t.valueOf():t;t=Ss(e)?e+"":e}if("string"!=typeof t)return 0===t?t:+t;t=t.replace(It,"");var n=zt.test(t);return n||Xt.test(t)?Oe(t.slice(2),n?2:8):Wt.test(t)?R:+t}function Ws(t){return ro(t,ou(t))}function zs(t){return null==t?"":Ri(t)}var Vs=oo(function(t,e){if(Qo(e)||bs(e))ro(e,iu(e),t);else for(var n in e)le.call(e,n)&&Ar(t,n,e[n])}),Xs=oo(function(t,e){ro(e,ou(e),t)}),Ks=oo(function(t,e,n,r){ro(e,ou(e),t,r)}),Js=oo(function(t,e,n,r){ro(e,iu(e),t,r)}),Gs=Oo(Dr);var Qs=Ci(function(t,e){t=ee(t);var n=-1,r=e.length,i=r>2?e[2]:o;for(i&&Xo(e[0],e[1],i)&&(r=1);++n1),e}),ro(t,No(t),n),r&&(n=Lr(n,p|d|h,Ao));for(var i=e.length;i--;)Fi(n,e[i]);return n});var cu=Oo(function(t,e){return null==t?{}:function(t,e){return yi(t,e,function(e,n){return tu(t,n)})}(t,e)});function lu(t,e){if(null==t)return{};var n=Ze(No(t),function(t){return[t]});return e=Ro(e),yi(t,n,function(t,n){return e(t,n[0])})}var fu=To(iu),pu=To(ou);function du(t){return null==t?[]:yn(t,iu(t))}var hu=co(function(t,e,n){return e=e.toLowerCase(),t+(n?vu(e):e)});function vu(t){return Cu(zs(t).toLowerCase())}function mu(t){return(t=zs(t))&&t.replace(Jt,xn).replace(be,"")}var gu=co(function(t,e,n){return t+(n?"-":"")+e.toLowerCase()}),yu=co(function(t,e,n){return t+(n?" ":"")+e.toLowerCase()}),bu=uo("toLowerCase");var _u=co(function(t,e,n){return t+(n?"_":"")+e.toLowerCase()});var wu=co(function(t,e,n){return t+(n?" ":"")+Cu(e)});var xu=co(function(t,e,n){return t+(n?" ":"")+e.toUpperCase()}),Cu=uo("toUpperCase");function Tu(t,e,n){return t=zs(t),(e=n?o:e)===o?function(t){return Ce.test(t)}(t)?function(t){return t.match(we)||[]}(t):function(t){return t.match(qt)||[]}(t):t.match(e)||[]}var ku=Ci(function(t,e){try{return ze(t,o,e)}catch(t){return Cs(t)?t:new Yt(t)}}),$u=Oo(function(t,e){return Xe(e,function(e){e=la(e),Nr(t,e,ns(t[e],t))}),t});function Su(t){return function(){return t}}var Au=po(),Eu=po(!0);function Ou(t){return t}function ju(t){return ui("function"==typeof t?t:Lr(t,p))}var Nu=Ci(function(t,e){return function(n){return ri(n,t,e)}}),Du=Ci(function(t,e){return function(n){return ri(t,n,e)}});function Iu(t,e,n){var r=iu(e),i=Jr(e,r);null!=n||Ss(e)&&(i.length||!r.length)||(n=e,e=t,t=this,i=Jr(e,iu(e)));var o=!(Ss(n)&&"chain"in n&&!n.chain),a=Ts(t);return Xe(i,function(n){var r=e[n];t[n]=r,a&&(t.prototype[n]=function(){var e=this.__chain__;if(o||e){var n=t(this.__wrapped__);return(n.__actions__=no(this.__actions__)).push({func:r,args:arguments,thisArg:t}),n.__chain__=e,n}return r.apply(t,tn([this.value()],arguments))})}),t}function Lu(){}var Ru=go(Ze),Pu=go(Je),Fu=go(rn);function Mu(t){return Ko(t)?pn(la(t)):function(t){return function(e){return Gr(e,t)}}(t)}var qu=bo(),Uu=bo(!0);function Bu(){return[]}function Hu(){return!1}var Wu=mo(function(t,e){return t+e},0),zu=xo("ceil"),Vu=mo(function(t,e){return t/e},1),Xu=xo("floor");var Ku,Ju=mo(function(t,e){return t*e},1),Gu=xo("round"),Qu=mo(function(t,e){return t-e},0);return dr.after=function(t,e){if("function"!=typeof e)throw new ie(u);return t=Us(t),function(){if(--t<1)return e.apply(this,arguments)}},dr.ary=ts,dr.assign=Vs,dr.assignIn=Xs,dr.assignInWith=Ks,dr.assignWith=Js,dr.at=Gs,dr.before=es,dr.bind=ns,dr.bindAll=$u,dr.bindKey=rs,dr.castArray=function(){if(!arguments.length)return[];var t=arguments[0];return gs(t)?t:[t]},dr.chain=Ma,dr.chunk=function(t,e,n){e=(n?Xo(t,e,n):e===o)?1:zn(Us(e),0);var i=null==t?0:t.length;if(!i||e<1)return[];for(var a=0,s=0,u=r(Fn(i/e));ai?0:i+n),(r=r===o||r>i?i:Us(r))<0&&(r+=i),r=n>r?0:Bs(r);n>>0)?(t=zs(t))&&("string"==typeof e||null!=e&&!Ns(e))&&!(e=Ri(e))&&kn(t)?Ki(Nn(t),0,n):t.split(e,n):[]},dr.spread=function(t,e){if("function"!=typeof t)throw new ie(u);return e=null==e?0:zn(Us(e),0),Ci(function(n){var r=n[e],i=Ki(n,0,e);return r&&tn(i,r),ze(t,this,i)})},dr.tail=function(t){var e=null==t?0:t.length;return e?Oi(t,1,e):[]},dr.take=function(t,e,n){return t&&t.length?Oi(t,0,(e=n||e===o?1:Us(e))<0?0:e):[]},dr.takeRight=function(t,e,n){var r=null==t?0:t.length;return r?Oi(t,(e=r-(e=n||e===o?1:Us(e)))<0?0:e,r):[]},dr.takeRightWhile=function(t,e){return t&&t.length?qi(t,Ro(e,3),!1,!0):[]},dr.takeWhile=function(t,e){return t&&t.length?qi(t,Ro(e,3)):[]},dr.tap=function(t,e){return e(t),t},dr.throttle=function(t,e,n){var r=!0,i=!0;if("function"!=typeof t)throw new ie(u);return Ss(n)&&(r="leading"in n?!!n.leading:r,i="trailing"in n?!!n.trailing:i),is(t,e,{leading:r,maxWait:e,trailing:i})},dr.thru=qa,dr.toArray=Ms,dr.toPairs=fu,dr.toPairsIn=pu,dr.toPath=function(t){return gs(t)?Ze(t,la):Ls(t)?[t]:no(ca(zs(t)))},dr.toPlainObject=Ws,dr.transform=function(t,e,n){var r=gs(t),i=r||ws(t)||Rs(t);if(e=Ro(e,4),null==n){var o=t&&t.constructor;n=i?r?new o:[]:Ss(t)&&Ts(o)?hr(je(t)):{}}return(i?Xe:Xr)(t,function(t,r,i){return e(n,t,r,i)}),n},dr.unary=function(t){return ts(t,1)},dr.union=Aa,dr.unionBy=Ea,dr.unionWith=Oa,dr.uniq=function(t){return t&&t.length?Pi(t):[]},dr.uniqBy=function(t,e){return t&&t.length?Pi(t,Ro(e,2)):[]},dr.uniqWith=function(t,e){return e="function"==typeof e?e:o,t&&t.length?Pi(t,o,e):[]},dr.unset=function(t,e){return null==t||Fi(t,e)},dr.unzip=ja,dr.unzipWith=Na,dr.update=function(t,e,n){return null==t?t:Mi(t,e,zi(n))},dr.updateWith=function(t,e,n,r){return r="function"==typeof r?r:o,null==t?t:Mi(t,e,zi(n),r)},dr.values=du,dr.valuesIn=function(t){return null==t?[]:yn(t,ou(t))},dr.without=Da,dr.words=Tu,dr.wrap=function(t,e){return ls(zi(e),t)},dr.xor=Ia,dr.xorBy=La,dr.xorWith=Ra,dr.zip=Pa,dr.zipObject=function(t,e){return Hi(t||[],e||[],Ar)},dr.zipObjectDeep=function(t,e){return Hi(t||[],e||[],$i)},dr.zipWith=Fa,dr.entries=fu,dr.entriesIn=pu,dr.extend=Xs,dr.extendWith=Ks,Iu(dr,dr),dr.add=Wu,dr.attempt=ku,dr.camelCase=hu,dr.capitalize=vu,dr.ceil=zu,dr.clamp=function(t,e,n){return n===o&&(n=e,e=o),n!==o&&(n=(n=Hs(n))==n?n:0),e!==o&&(e=(e=Hs(e))==e?e:0),Ir(Hs(t),e,n)},dr.clone=function(t){return Lr(t,h)},dr.cloneDeep=function(t){return Lr(t,p|h)},dr.cloneDeepWith=function(t,e){return Lr(t,p|h,e="function"==typeof e?e:o)},dr.cloneWith=function(t,e){return Lr(t,h,e="function"==typeof e?e:o)},dr.conformsTo=function(t,e){return null==e||Rr(t,e,iu(e))},dr.deburr=mu,dr.defaultTo=function(t,e){return null==t||t!=t?e:t},dr.divide=Vu,dr.endsWith=function(t,e,n){t=zs(t),e=Ri(e);var r=t.length,i=n=n===o?r:Ir(Us(n),0,r);return(n-=e.length)>=0&&t.slice(n,i)==e},dr.eq=ds,dr.escape=function(t){return(t=zs(t))&&kt.test(t)?t.replace(Ct,Cn):t},dr.escapeRegExp=function(t){return(t=zs(t))&&Dt.test(t)?t.replace(Nt,"\\$&"):t},dr.every=function(t,e,n){var r=gs(t)?Je:Ur;return n&&Xo(t,e,n)&&(e=o),r(t,Ro(e,3))},dr.find=Ha,dr.findIndex=ma,dr.findKey=function(t,e){return an(t,Ro(e,3),Xr)},dr.findLast=Wa,dr.findLastIndex=ga,dr.findLastKey=function(t,e){return an(t,Ro(e,3),Kr)},dr.floor=Xu,dr.forEach=za,dr.forEachRight=Va,dr.forIn=function(t,e){return null==t?t:zr(t,Ro(e,3),ou)},dr.forInRight=function(t,e){return null==t?t:Vr(t,Ro(e,3),ou)},dr.forOwn=function(t,e){return t&&Xr(t,Ro(e,3))},dr.forOwnRight=function(t,e){return t&&Kr(t,Ro(e,3))},dr.get=Zs,dr.gt=hs,dr.gte=vs,dr.has=function(t,e){return null!=t&&Ho(t,e,ti)},dr.hasIn=tu,dr.head=ba,dr.identity=Ou,dr.includes=function(t,e,n,r){t=bs(t)?t:du(t),n=n&&!r?Us(n):0;var i=t.length;return n<0&&(n=zn(i+n,0)),Is(t)?n<=i&&t.indexOf(e,n)>-1:!!i&&un(t,e,n)>-1},dr.indexOf=function(t,e,n){var r=null==t?0:t.length;if(!r)return-1;var i=null==n?0:Us(n);return i<0&&(i=zn(r+i,0)),un(t,e,i)},dr.inRange=function(t,e,n){return e=qs(e),n===o?(n=e,e=0):n=qs(n),function(t,e,n){return t>=Vn(e,n)&&t=-I&&t<=I},dr.isSet=Ds,dr.isString=Is,dr.isSymbol=Ls,dr.isTypedArray=Rs,dr.isUndefined=function(t){return t===o},dr.isWeakMap=function(t){return As(t)&&Bo(t)==at},dr.isWeakSet=function(t){return As(t)&&Yr(t)==st},dr.join=function(t,e){return null==t?"":Hn.call(t,e)},dr.kebabCase=gu,dr.last=Ca,dr.lastIndexOf=function(t,e,n){var r=null==t?0:t.length;if(!r)return-1;var i=r;return n!==o&&(i=(i=Us(n))<0?zn(r+i,0):Vn(i,r-1)),e==e?function(t,e,n){for(var r=n+1;r--;)if(t[r]===e)return r;return r}(t,e,i):sn(t,ln,i,!0)},dr.lowerCase=yu,dr.lowerFirst=bu,dr.lt=Ps,dr.lte=Fs,dr.max=function(t){return t&&t.length?Br(t,Ou,Zr):o},dr.maxBy=function(t,e){return t&&t.length?Br(t,Ro(e,2),Zr):o},dr.mean=function(t){return fn(t,Ou)},dr.meanBy=function(t,e){return fn(t,Ro(e,2))},dr.min=function(t){return t&&t.length?Br(t,Ou,fi):o},dr.minBy=function(t,e){return t&&t.length?Br(t,Ro(e,2),fi):o},dr.stubArray=Bu,dr.stubFalse=Hu,dr.stubObject=function(){return{}},dr.stubString=function(){return""},dr.stubTrue=function(){return!0},dr.multiply=Ju,dr.nth=function(t,e){return t&&t.length?mi(t,Us(e)):o},dr.noConflict=function(){return De._===this&&(De._=ve),this},dr.noop=Lu,dr.now=Za,dr.pad=function(t,e,n){t=zs(t);var r=(e=Us(e))?jn(t):0;if(!e||r>=e)return t;var i=(e-r)/2;return yo(Mn(i),n)+t+yo(Fn(i),n)},dr.padEnd=function(t,e,n){t=zs(t);var r=(e=Us(e))?jn(t):0;return e&&re){var r=t;t=e,e=r}if(n||t%1||e%1){var i=Jn();return Vn(t+i*(e-t+Ee("1e-"+((i+"").length-1))),e)}return wi(t,e)},dr.reduce=function(t,e,n){var r=gs(t)?en:hn,i=arguments.length<3;return r(t,Ro(e,4),n,i,Mr)},dr.reduceRight=function(t,e,n){var r=gs(t)?nn:hn,i=arguments.length<3;return r(t,Ro(e,4),n,i,qr)},dr.repeat=function(t,e,n){return e=(n?Xo(t,e,n):e===o)?1:Us(e),xi(zs(t),e)},dr.replace=function(){var t=arguments,e=zs(t[0]);return t.length<3?e:e.replace(t[1],t[2])},dr.result=function(t,e,n){var r=-1,i=(e=Vi(e,t)).length;for(i||(i=1,t=o);++rI)return[];var n=P,r=Vn(t,P);e=Ro(e),t-=P;for(var i=mn(r,e);++n=a)return t;var u=n-jn(r);if(u<1)return r;var c=s?Ki(s,0,u).join(""):t.slice(0,u);if(i===o)return c+r;if(s&&(u+=c.length-u),Ns(i)){if(t.slice(u).search(i)){var l,f=c;for(i.global||(i=ne(i.source,zs(Ht.exec(i))+"g")),i.lastIndex=0;l=i.exec(f);)var p=l.index;c=c.slice(0,p===o?u:p)}}else if(t.indexOf(Ri(i),u)!=u){var d=c.lastIndexOf(i);d>-1&&(c=c.slice(0,d))}return c+r},dr.unescape=function(t){return(t=zs(t))&&Tt.test(t)?t.replace(xt,Dn):t},dr.uniqueId=function(t){var e=++fe;return zs(t)+e},dr.upperCase=xu,dr.upperFirst=Cu,dr.each=za,dr.eachRight=Va,dr.first=ba,Iu(dr,(Ku={},Xr(dr,function(t,e){le.call(dr.prototype,e)||(Ku[e]=t)}),Ku),{chain:!1}),dr.VERSION="4.17.11",Xe(["bind","bindKey","curry","curryRight","partial","partialRight"],function(t){dr[t].placeholder=dr}),Xe(["drop","take"],function(t,e){gr.prototype[t]=function(n){n=n===o?1:zn(Us(n),0);var r=this.__filtered__&&!e?new gr(this):this.clone();return r.__filtered__?r.__takeCount__=Vn(n,r.__takeCount__):r.__views__.push({size:Vn(n,P),type:t+(r.__dir__<0?"Right":"")}),r},gr.prototype[t+"Right"]=function(e){return this.reverse()[t](e).reverse()}}),Xe(["filter","map","takeWhile"],function(t,e){var n=e+1,r=n==j||3==n;gr.prototype[t]=function(t){var e=this.clone();return e.__iteratees__.push({iteratee:Ro(t,3),type:n}),e.__filtered__=e.__filtered__||r,e}}),Xe(["head","last"],function(t,e){var n="take"+(e?"Right":"");gr.prototype[t]=function(){return this[n](1).value()[0]}}),Xe(["initial","tail"],function(t,e){var n="drop"+(e?"":"Right");gr.prototype[t]=function(){return this.__filtered__?new gr(this):this[n](1)}}),gr.prototype.compact=function(){return this.filter(Ou)},gr.prototype.find=function(t){return this.filter(t).head()},gr.prototype.findLast=function(t){return this.reverse().find(t)},gr.prototype.invokeMap=Ci(function(t,e){return"function"==typeof t?new gr(this):this.map(function(n){return ri(n,t,e)})}),gr.prototype.reject=function(t){return this.filter(us(Ro(t)))},gr.prototype.slice=function(t,e){t=Us(t);var n=this;return n.__filtered__&&(t>0||e<0)?new gr(n):(t<0?n=n.takeRight(-t):t&&(n=n.drop(t)),e!==o&&(n=(e=Us(e))<0?n.dropRight(-e):n.take(e-t)),n)},gr.prototype.takeRightWhile=function(t){return this.reverse().takeWhile(t).reverse()},gr.prototype.toArray=function(){return this.take(P)},Xr(gr.prototype,function(t,e){var n=/^(?:filter|find|map|reject)|While$/.test(e),r=/^(?:head|last)$/.test(e),i=dr[r?"take"+("last"==e?"Right":""):e],a=r||/^find/.test(e);i&&(dr.prototype[e]=function(){var e=this.__wrapped__,s=r?[1]:arguments,u=e instanceof gr,c=s[0],l=u||gs(e),f=function(t){var e=i.apply(dr,tn([t],s));return r&&p?e[0]:e};l&&n&&"function"==typeof c&&1!=c.length&&(u=l=!1);var p=this.__chain__,d=!!this.__actions__.length,h=a&&!p,v=u&&!d;if(!a&&l){e=v?e:new gr(this);var m=t.apply(e,s);return m.__actions__.push({func:qa,args:[f],thisArg:o}),new mr(m,p)}return h&&v?t.apply(this,s):(m=this.thru(f),h?r?m.value()[0]:m.value():m)})}),Xe(["pop","push","shift","sort","splice","unshift"],function(t){var e=oe[t],n=/^(?:push|sort|unshift)$/.test(t)?"tap":"thru",r=/^(?:pop|shift)$/.test(t);dr.prototype[t]=function(){var t=arguments;if(r&&!this.__chain__){var i=this.value();return e.apply(gs(i)?i:[],t)}return this[n](function(n){return e.apply(gs(n)?n:[],t)})}}),Xr(gr.prototype,function(t,e){var n=dr[e];if(n){var r=n.name+"";(ir[r]||(ir[r]=[])).push({name:e,func:n})}}),ir[ho(o,y).name]=[{name:"wrapper",func:o}],gr.prototype.clone=function(){var t=new gr(this.__wrapped__);return t.__actions__=no(this.__actions__),t.__dir__=this.__dir__,t.__filtered__=this.__filtered__,t.__iteratees__=no(this.__iteratees__),t.__takeCount__=this.__takeCount__,t.__views__=no(this.__views__),t},gr.prototype.reverse=function(){if(this.__filtered__){var t=new gr(this);t.__dir__=-1,t.__filtered__=!0}else(t=this.clone()).__dir__*=-1;return t},gr.prototype.value=function(){var t=this.__wrapped__.value(),e=this.__dir__,n=gs(t),r=e<0,i=n?t.length:0,o=function(t,e,n){for(var r=-1,i=n.length;++r=this.__values__.length;return{done:t,value:t?o:this.__values__[this.__index__++]}},dr.prototype.plant=function(t){for(var e,n=this;n instanceof vr;){var r=pa(n);r.__index__=0,r.__values__=o,e?i.__wrapped__=r:e=r;var i=r;n=n.__wrapped__}return i.__wrapped__=t,e},dr.prototype.reverse=function(){var t=this.__wrapped__;if(t instanceof gr){var e=t;return this.__actions__.length&&(e=new gr(this)),(e=e.reverse()).__actions__.push({func:qa,args:[Sa],thisArg:o}),new mr(e,this.__chain__)}return this.thru(Sa)},dr.prototype.toJSON=dr.prototype.valueOf=dr.prototype.value=function(){return Ui(this.__wrapped__,this.__actions__)},dr.prototype.first=dr.prototype.head,Fe&&(dr.prototype[Fe]=function(){return this}),dr}();De._=In,(i=function(){return In}.call(e,n,e,r))===o||(r.exports=i)}).call(this)}).call(e,n(1),n(16)(t))},function(t,e){t.exports=function(t){return t.webpackPolyfill||(t.deprecate=function(){},t.paths=[],t.children||(t.children=[]),Object.defineProperty(t,"loaded",{enumerable:!0,get:function(){return t.l}}),Object.defineProperty(t,"id",{enumerable:!0,get:function(){return t.i}}),t.webpackPolyfill=1),t}},function(t,e,n){var r;!function(e,n){"use strict";"object"==typeof t&&"object"==typeof t.exports?t.exports=e.document?n(e,!0):function(t){if(!t.document)throw new Error("jQuery requires a window with a document");return n(t)}:n(e)}("undefined"!=typeof window?window:this,function(n,i){"use strict";var o=[],a=n.document,s=Object.getPrototypeOf,u=o.slice,c=o.concat,l=o.push,f=o.indexOf,p={},d=p.toString,h=p.hasOwnProperty,v=h.toString,m=v.call(Object),g={},y=function(t){return"function"==typeof t&&"number"!=typeof t.nodeType},b=function(t){return null!=t&&t===t.window},_={type:!0,src:!0,noModule:!0};function w(t,e,n){var r,i=(e=e||a).createElement("script");if(i.text=t,n)for(r in _)n[r]&&(i[r]=n[r]);e.head.appendChild(i).parentNode.removeChild(i)}function x(t){return null==t?t+"":"object"==typeof t||"function"==typeof t?p[d.call(t)]||"object":typeof t}var C=function(t,e){return new C.fn.init(t,e)},T=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;function k(t){var e=!!t&&"length"in t&&t.length,n=x(t);return!y(t)&&!b(t)&&("array"===n||0===e||"number"==typeof e&&e>0&&e-1 in t)}C.fn=C.prototype={jquery:"3.3.1",constructor:C,length:0,toArray:function(){return u.call(this)},get:function(t){return null==t?u.call(this):t<0?this[t+this.length]:this[t]},pushStack:function(t){var e=C.merge(this.constructor(),t);return e.prevObject=this,e},each:function(t){return C.each(this,t)},map:function(t){return this.pushStack(C.map(this,function(e,n){return t.call(e,n,e)}))},slice:function(){return this.pushStack(u.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(t){var e=this.length,n=+t+(t<0?e:0);return this.pushStack(n>=0&&n+~]|"+R+")"+R+"*"),W=new RegExp("="+R+"*([^\\]'\"]*?)"+R+"*\\]","g"),z=new RegExp(M),V=new RegExp("^"+P+"$"),X={ID:new RegExp("^#("+P+")"),CLASS:new RegExp("^\\.("+P+")"),TAG:new RegExp("^("+P+"|[*])"),ATTR:new RegExp("^"+F),PSEUDO:new RegExp("^"+M),CHILD:new RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+R+"*(even|odd|(([+-]|)(\\d*)n|)"+R+"*(?:([+-]|)"+R+"*(\\d+)|))"+R+"*\\)|)","i"),bool:new RegExp("^(?:"+L+")$","i"),needsContext:new RegExp("^"+R+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+R+"*((?:-\\d)?\\d*)"+R+"*\\)|)(?=[^-]|$)","i")},K=/^(?:input|select|textarea|button)$/i,J=/^h\d$/i,G=/^[^{]+\{\s*\[native \w/,Q=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,Y=/[+~]/,Z=new RegExp("\\\\([\\da-f]{1,6}"+R+"?|("+R+")|.)","ig"),tt=function(t,e,n){var r="0x"+e-65536;return r!=r||n?e:r<0?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320)},et=/([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g,nt=function(t,e){return e?"\0"===t?"�":t.slice(0,-1)+"\\"+t.charCodeAt(t.length-1).toString(16)+" ":"\\"+t},rt=function(){p()},it=yt(function(t){return!0===t.disabled&&("form"in t||"label"in t)},{dir:"parentNode",next:"legend"});try{N.apply(E=D.call(w.childNodes),w.childNodes),E[w.childNodes.length].nodeType}catch(t){N={apply:E.length?function(t,e){j.apply(t,D.call(e))}:function(t,e){for(var n=t.length,r=0;t[n++]=e[r++];);t.length=n-1}}}function ot(t,e,r,i){var o,s,c,l,f,h,g,y=e&&e.ownerDocument,x=e?e.nodeType:9;if(r=r||[],"string"!=typeof t||!t||1!==x&&9!==x&&11!==x)return r;if(!i&&((e?e.ownerDocument||e:w)!==d&&p(e),e=e||d,v)){if(11!==x&&(f=Q.exec(t)))if(o=f[1]){if(9===x){if(!(c=e.getElementById(o)))return r;if(c.id===o)return r.push(c),r}else if(y&&(c=y.getElementById(o))&&b(e,c)&&c.id===o)return r.push(c),r}else{if(f[2])return N.apply(r,e.getElementsByTagName(t)),r;if((o=f[3])&&n.getElementsByClassName&&e.getElementsByClassName)return N.apply(r,e.getElementsByClassName(o)),r}if(n.qsa&&!$[t+" "]&&(!m||!m.test(t))){if(1!==x)y=e,g=t;else if("object"!==e.nodeName.toLowerCase()){for((l=e.getAttribute("id"))?l=l.replace(et,nt):e.setAttribute("id",l=_),s=(h=a(t)).length;s--;)h[s]="#"+l+" "+gt(h[s]);g=h.join(","),y=Y.test(t)&&vt(e.parentNode)||e}if(g)try{return N.apply(r,y.querySelectorAll(g)),r}catch(t){}finally{l===_&&e.removeAttribute("id")}}}return u(t.replace(U,"$1"),e,r,i)}function at(){var t=[];return function e(n,i){return t.push(n+" ")>r.cacheLength&&delete e[t.shift()],e[n+" "]=i}}function st(t){return t[_]=!0,t}function ut(t){var e=d.createElement("fieldset");try{return!!t(e)}catch(t){return!1}finally{e.parentNode&&e.parentNode.removeChild(e),e=null}}function ct(t,e){for(var n=t.split("|"),i=n.length;i--;)r.attrHandle[n[i]]=e}function lt(t,e){var n=e&&t,r=n&&1===t.nodeType&&1===e.nodeType&&t.sourceIndex-e.sourceIndex;if(r)return r;if(n)for(;n=n.nextSibling;)if(n===e)return-1;return t?1:-1}function ft(t){return function(e){return"input"===e.nodeName.toLowerCase()&&e.type===t}}function pt(t){return function(e){var n=e.nodeName.toLowerCase();return("input"===n||"button"===n)&&e.type===t}}function dt(t){return function(e){return"form"in e?e.parentNode&&!1===e.disabled?"label"in e?"label"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&it(e)===t:e.disabled===t:"label"in e&&e.disabled===t}}function ht(t){return st(function(e){return e=+e,st(function(n,r){for(var i,o=t([],n.length,e),a=o.length;a--;)n[i=o[a]]&&(n[i]=!(r[i]=n[i]))})})}function vt(t){return t&&void 0!==t.getElementsByTagName&&t}for(e in n=ot.support={},o=ot.isXML=function(t){var e=t&&(t.ownerDocument||t).documentElement;return!!e&&"HTML"!==e.nodeName},p=ot.setDocument=function(t){var e,i,a=t?t.ownerDocument||t:w;return a!==d&&9===a.nodeType&&a.documentElement?(h=(d=a).documentElement,v=!o(d),w!==d&&(i=d.defaultView)&&i.top!==i&&(i.addEventListener?i.addEventListener("unload",rt,!1):i.attachEvent&&i.attachEvent("onunload",rt)),n.attributes=ut(function(t){return t.className="i",!t.getAttribute("className")}),n.getElementsByTagName=ut(function(t){return t.appendChild(d.createComment("")),!t.getElementsByTagName("*").length}),n.getElementsByClassName=G.test(d.getElementsByClassName),n.getById=ut(function(t){return h.appendChild(t).id=_,!d.getElementsByName||!d.getElementsByName(_).length}),n.getById?(r.filter.ID=function(t){var e=t.replace(Z,tt);return function(t){return t.getAttribute("id")===e}},r.find.ID=function(t,e){if(void 0!==e.getElementById&&v){var n=e.getElementById(t);return n?[n]:[]}}):(r.filter.ID=function(t){var e=t.replace(Z,tt);return function(t){var n=void 0!==t.getAttributeNode&&t.getAttributeNode("id");return n&&n.value===e}},r.find.ID=function(t,e){if(void 0!==e.getElementById&&v){var n,r,i,o=e.getElementById(t);if(o){if((n=o.getAttributeNode("id"))&&n.value===t)return[o];for(i=e.getElementsByName(t),r=0;o=i[r++];)if((n=o.getAttributeNode("id"))&&n.value===t)return[o]}return[]}}),r.find.TAG=n.getElementsByTagName?function(t,e){return void 0!==e.getElementsByTagName?e.getElementsByTagName(t):n.qsa?e.querySelectorAll(t):void 0}:function(t,e){var n,r=[],i=0,o=e.getElementsByTagName(t);if("*"===t){for(;n=o[i++];)1===n.nodeType&&r.push(n);return r}return o},r.find.CLASS=n.getElementsByClassName&&function(t,e){if(void 0!==e.getElementsByClassName&&v)return e.getElementsByClassName(t)},g=[],m=[],(n.qsa=G.test(d.querySelectorAll))&&(ut(function(t){h.appendChild(t).innerHTML="",t.querySelectorAll("[msallowcapture^='']").length&&m.push("[*^$]="+R+"*(?:''|\"\")"),t.querySelectorAll("[selected]").length||m.push("\\["+R+"*(?:value|"+L+")"),t.querySelectorAll("[id~="+_+"-]").length||m.push("~="),t.querySelectorAll(":checked").length||m.push(":checked"),t.querySelectorAll("a#"+_+"+*").length||m.push(".#.+[+~]")}),ut(function(t){t.innerHTML="";var e=d.createElement("input");e.setAttribute("type","hidden"),t.appendChild(e).setAttribute("name","D"),t.querySelectorAll("[name=d]").length&&m.push("name"+R+"*[*^$|!~]?="),2!==t.querySelectorAll(":enabled").length&&m.push(":enabled",":disabled"),h.appendChild(t).disabled=!0,2!==t.querySelectorAll(":disabled").length&&m.push(":enabled",":disabled"),t.querySelectorAll("*,:x"),m.push(",.*:")})),(n.matchesSelector=G.test(y=h.matches||h.webkitMatchesSelector||h.mozMatchesSelector||h.oMatchesSelector||h.msMatchesSelector))&&ut(function(t){n.disconnectedMatch=y.call(t,"*"),y.call(t,"[s!='']:x"),g.push("!=",M)}),m=m.length&&new RegExp(m.join("|")),g=g.length&&new RegExp(g.join("|")),e=G.test(h.compareDocumentPosition),b=e||G.test(h.contains)?function(t,e){var n=9===t.nodeType?t.documentElement:t,r=e&&e.parentNode;return t===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):t.compareDocumentPosition&&16&t.compareDocumentPosition(r)))}:function(t,e){if(e)for(;e=e.parentNode;)if(e===t)return!0;return!1},S=e?function(t,e){if(t===e)return f=!0,0;var r=!t.compareDocumentPosition-!e.compareDocumentPosition;return r||(1&(r=(t.ownerDocument||t)===(e.ownerDocument||e)?t.compareDocumentPosition(e):1)||!n.sortDetached&&e.compareDocumentPosition(t)===r?t===d||t.ownerDocument===w&&b(w,t)?-1:e===d||e.ownerDocument===w&&b(w,e)?1:l?I(l,t)-I(l,e):0:4&r?-1:1)}:function(t,e){if(t===e)return f=!0,0;var n,r=0,i=t.parentNode,o=e.parentNode,a=[t],s=[e];if(!i||!o)return t===d?-1:e===d?1:i?-1:o?1:l?I(l,t)-I(l,e):0;if(i===o)return lt(t,e);for(n=t;n=n.parentNode;)a.unshift(n);for(n=e;n=n.parentNode;)s.unshift(n);for(;a[r]===s[r];)r++;return r?lt(a[r],s[r]):a[r]===w?-1:s[r]===w?1:0},d):d},ot.matches=function(t,e){return ot(t,null,null,e)},ot.matchesSelector=function(t,e){if((t.ownerDocument||t)!==d&&p(t),e=e.replace(W,"='$1']"),n.matchesSelector&&v&&!$[e+" "]&&(!g||!g.test(e))&&(!m||!m.test(e)))try{var r=y.call(t,e);if(r||n.disconnectedMatch||t.document&&11!==t.document.nodeType)return r}catch(t){}return ot(e,d,null,[t]).length>0},ot.contains=function(t,e){return(t.ownerDocument||t)!==d&&p(t),b(t,e)},ot.attr=function(t,e){(t.ownerDocument||t)!==d&&p(t);var i=r.attrHandle[e.toLowerCase()],o=i&&A.call(r.attrHandle,e.toLowerCase())?i(t,e,!v):void 0;return void 0!==o?o:n.attributes||!v?t.getAttribute(e):(o=t.getAttributeNode(e))&&o.specified?o.value:null},ot.escape=function(t){return(t+"").replace(et,nt)},ot.error=function(t){throw new Error("Syntax error, unrecognized expression: "+t)},ot.uniqueSort=function(t){var e,r=[],i=0,o=0;if(f=!n.detectDuplicates,l=!n.sortStable&&t.slice(0),t.sort(S),f){for(;e=t[o++];)e===t[o]&&(i=r.push(o));for(;i--;)t.splice(r[i],1)}return l=null,t},i=ot.getText=function(t){var e,n="",r=0,o=t.nodeType;if(o){if(1===o||9===o||11===o){if("string"==typeof t.textContent)return t.textContent;for(t=t.firstChild;t;t=t.nextSibling)n+=i(t)}else if(3===o||4===o)return t.nodeValue}else for(;e=t[r++];)n+=i(e);return n},(r=ot.selectors={cacheLength:50,createPseudo:st,match:X,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(t){return t[1]=t[1].replace(Z,tt),t[3]=(t[3]||t[4]||t[5]||"").replace(Z,tt),"~="===t[2]&&(t[3]=" "+t[3]+" "),t.slice(0,4)},CHILD:function(t){return t[1]=t[1].toLowerCase(),"nth"===t[1].slice(0,3)?(t[3]||ot.error(t[0]),t[4]=+(t[4]?t[5]+(t[6]||1):2*("even"===t[3]||"odd"===t[3])),t[5]=+(t[7]+t[8]||"odd"===t[3])):t[3]&&ot.error(t[0]),t},PSEUDO:function(t){var e,n=!t[6]&&t[2];return X.CHILD.test(t[0])?null:(t[3]?t[2]=t[4]||t[5]||"":n&&z.test(n)&&(e=a(n,!0))&&(e=n.indexOf(")",n.length-e)-n.length)&&(t[0]=t[0].slice(0,e),t[2]=n.slice(0,e)),t.slice(0,3))}},filter:{TAG:function(t){var e=t.replace(Z,tt).toLowerCase();return"*"===t?function(){return!0}:function(t){return t.nodeName&&t.nodeName.toLowerCase()===e}},CLASS:function(t){var e=T[t+" "];return e||(e=new RegExp("(^|"+R+")"+t+"("+R+"|$)"))&&T(t,function(t){return e.test("string"==typeof t.className&&t.className||void 0!==t.getAttribute&&t.getAttribute("class")||"")})},ATTR:function(t,e,n){return function(r){var i=ot.attr(r,t);return null==i?"!="===e:!e||(i+="","="===e?i===n:"!="===e?i!==n:"^="===e?n&&0===i.indexOf(n):"*="===e?n&&i.indexOf(n)>-1:"$="===e?n&&i.slice(-n.length)===n:"~="===e?(" "+i.replace(q," ")+" ").indexOf(n)>-1:"|="===e&&(i===n||i.slice(0,n.length+1)===n+"-"))}},CHILD:function(t,e,n,r,i){var o="nth"!==t.slice(0,3),a="last"!==t.slice(-4),s="of-type"===e;return 1===r&&0===i?function(t){return!!t.parentNode}:function(e,n,u){var c,l,f,p,d,h,v=o!==a?"nextSibling":"previousSibling",m=e.parentNode,g=s&&e.nodeName.toLowerCase(),y=!u&&!s,b=!1;if(m){if(o){for(;v;){for(p=e;p=p[v];)if(s?p.nodeName.toLowerCase()===g:1===p.nodeType)return!1;h=v="only"===t&&!h&&"nextSibling"}return!0}if(h=[a?m.firstChild:m.lastChild],a&&y){for(b=(d=(c=(l=(f=(p=m)[_]||(p[_]={}))[p.uniqueID]||(f[p.uniqueID]={}))[t]||[])[0]===x&&c[1])&&c[2],p=d&&m.childNodes[d];p=++d&&p&&p[v]||(b=d=0)||h.pop();)if(1===p.nodeType&&++b&&p===e){l[t]=[x,d,b];break}}else if(y&&(b=d=(c=(l=(f=(p=e)[_]||(p[_]={}))[p.uniqueID]||(f[p.uniqueID]={}))[t]||[])[0]===x&&c[1]),!1===b)for(;(p=++d&&p&&p[v]||(b=d=0)||h.pop())&&((s?p.nodeName.toLowerCase()!==g:1!==p.nodeType)||!++b||(y&&((l=(f=p[_]||(p[_]={}))[p.uniqueID]||(f[p.uniqueID]={}))[t]=[x,b]),p!==e)););return(b-=i)===r||b%r==0&&b/r>=0}}},PSEUDO:function(t,e){var n,i=r.pseudos[t]||r.setFilters[t.toLowerCase()]||ot.error("unsupported pseudo: "+t);return i[_]?i(e):i.length>1?(n=[t,t,"",e],r.setFilters.hasOwnProperty(t.toLowerCase())?st(function(t,n){for(var r,o=i(t,e),a=o.length;a--;)t[r=I(t,o[a])]=!(n[r]=o[a])}):function(t){return i(t,0,n)}):i}},pseudos:{not:st(function(t){var e=[],n=[],r=s(t.replace(U,"$1"));return r[_]?st(function(t,e,n,i){for(var o,a=r(t,null,i,[]),s=t.length;s--;)(o=a[s])&&(t[s]=!(e[s]=o))}):function(t,i,o){return e[0]=t,r(e,null,o,n),e[0]=null,!n.pop()}}),has:st(function(t){return function(e){return ot(t,e).length>0}}),contains:st(function(t){return t=t.replace(Z,tt),function(e){return(e.textContent||e.innerText||i(e)).indexOf(t)>-1}}),lang:st(function(t){return V.test(t||"")||ot.error("unsupported lang: "+t),t=t.replace(Z,tt).toLowerCase(),function(e){var n;do{if(n=v?e.lang:e.getAttribute("xml:lang")||e.getAttribute("lang"))return(n=n.toLowerCase())===t||0===n.indexOf(t+"-")}while((e=e.parentNode)&&1===e.nodeType);return!1}}),target:function(e){var n=t.location&&t.location.hash;return n&&n.slice(1)===e.id},root:function(t){return t===h},focus:function(t){return t===d.activeElement&&(!d.hasFocus||d.hasFocus())&&!!(t.type||t.href||~t.tabIndex)},enabled:dt(!1),disabled:dt(!0),checked:function(t){var e=t.nodeName.toLowerCase();return"input"===e&&!!t.checked||"option"===e&&!!t.selected},selected:function(t){return t.parentNode&&t.parentNode.selectedIndex,!0===t.selected},empty:function(t){for(t=t.firstChild;t;t=t.nextSibling)if(t.nodeType<6)return!1;return!0},parent:function(t){return!r.pseudos.empty(t)},header:function(t){return J.test(t.nodeName)},input:function(t){return K.test(t.nodeName)},button:function(t){var e=t.nodeName.toLowerCase();return"input"===e&&"button"===t.type||"button"===e},text:function(t){var e;return"input"===t.nodeName.toLowerCase()&&"text"===t.type&&(null==(e=t.getAttribute("type"))||"text"===e.toLowerCase())},first:ht(function(){return[0]}),last:ht(function(t,e){return[e-1]}),eq:ht(function(t,e,n){return[n<0?n+e:n]}),even:ht(function(t,e){for(var n=0;n=0;)t.push(r);return t}),gt:ht(function(t,e,n){for(var r=n<0?n+e:n;++r1?function(e,n,r){for(var i=t.length;i--;)if(!t[i](e,n,r))return!1;return!0}:t[0]}function _t(t,e,n,r,i){for(var o,a=[],s=0,u=t.length,c=null!=e;s-1&&(o[c]=!(a[c]=f))}}else g=_t(g===a?g.splice(h,g.length):g),i?i(null,a,g,u):N.apply(a,g)})}function xt(t){for(var e,n,i,o=t.length,a=r.relative[t[0].type],s=a||r.relative[" "],u=a?1:0,l=yt(function(t){return t===e},s,!0),f=yt(function(t){return I(e,t)>-1},s,!0),p=[function(t,n,r){var i=!a&&(r||n!==c)||((e=n).nodeType?l(t,n,r):f(t,n,r));return e=null,i}];u1&&bt(p),u>1&>(t.slice(0,u-1).concat({value:" "===t[u-2].type?"*":""})).replace(U,"$1"),n,u0,i=t.length>0,o=function(o,a,s,u,l){var f,h,m,g=0,y="0",b=o&&[],_=[],w=c,C=o||i&&r.find.TAG("*",l),T=x+=null==w?1:Math.random()||.1,k=C.length;for(l&&(c=a===d||a||l);y!==k&&null!=(f=C[y]);y++){if(i&&f){for(h=0,a||f.ownerDocument===d||(p(f),s=!v);m=t[h++];)if(m(f,a||d,s)){u.push(f);break}l&&(x=T)}n&&((f=!m&&f)&&g--,o&&b.push(f))}if(g+=y,n&&y!==g){for(h=0;m=e[h++];)m(b,_,a,s);if(o){if(g>0)for(;y--;)b[y]||_[y]||(_[y]=O.call(u));_=_t(_)}N.apply(u,_),l&&!o&&_.length>0&&g+e.length>1&&ot.uniqueSort(u)}return l&&(x=T,c=w),b};return n?st(o):o}(o,i))).selector=t}return s},u=ot.select=function(t,e,n,i){var o,u,c,l,f,p="function"==typeof t&&t,d=!i&&a(t=p.selector||t);if(n=n||[],1===d.length){if((u=d[0]=d[0].slice(0)).length>2&&"ID"===(c=u[0]).type&&9===e.nodeType&&v&&r.relative[u[1].type]){if(!(e=(r.find.ID(c.matches[0].replace(Z,tt),e)||[])[0]))return n;p&&(e=e.parentNode),t=t.slice(u.shift().value.length)}for(o=X.needsContext.test(t)?0:u.length;o--&&(c=u[o],!r.relative[l=c.type]);)if((f=r.find[l])&&(i=f(c.matches[0].replace(Z,tt),Y.test(u[0].type)&&vt(e.parentNode)||e))){if(u.splice(o,1),!(t=i.length&>(u)))return N.apply(n,i),n;break}}return(p||s(t,d))(i,e,!v,n,!e||Y.test(t)&&vt(e.parentNode)||e),n},n.sortStable=_.split("").sort(S).join("")===_,n.detectDuplicates=!!f,p(),n.sortDetached=ut(function(t){return 1&t.compareDocumentPosition(d.createElement("fieldset"))}),ut(function(t){return t.innerHTML="","#"===t.firstChild.getAttribute("href")})||ct("type|href|height|width",function(t,e,n){if(!n)return t.getAttribute(e,"type"===e.toLowerCase()?1:2)}),n.attributes&&ut(function(t){return t.innerHTML="",t.firstChild.setAttribute("value",""),""===t.firstChild.getAttribute("value")})||ct("value",function(t,e,n){if(!n&&"input"===t.nodeName.toLowerCase())return t.defaultValue}),ut(function(t){return null==t.getAttribute("disabled")})||ct(L,function(t,e,n){var r;if(!n)return!0===t[e]?e.toLowerCase():(r=t.getAttributeNode(e))&&r.specified?r.value:null}),ot}(n);C.find=$,C.expr=$.selectors,C.expr[":"]=C.expr.pseudos,C.uniqueSort=C.unique=$.uniqueSort,C.text=$.getText,C.isXMLDoc=$.isXML,C.contains=$.contains,C.escapeSelector=$.escape;var S=function(t,e,n){for(var r=[],i=void 0!==n;(t=t[e])&&9!==t.nodeType;)if(1===t.nodeType){if(i&&C(t).is(n))break;r.push(t)}return r},A=function(t,e){for(var n=[];t;t=t.nextSibling)1===t.nodeType&&t!==e&&n.push(t);return n},E=C.expr.match.needsContext;function O(t,e){return t.nodeName&&t.nodeName.toLowerCase()===e.toLowerCase()}var j=/^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i;function N(t,e,n){return y(e)?C.grep(t,function(t,r){return!!e.call(t,r,t)!==n}):e.nodeType?C.grep(t,function(t){return t===e!==n}):"string"!=typeof e?C.grep(t,function(t){return f.call(e,t)>-1!==n}):C.filter(e,t,n)}C.filter=function(t,e,n){var r=e[0];return n&&(t=":not("+t+")"),1===e.length&&1===r.nodeType?C.find.matchesSelector(r,t)?[r]:[]:C.find.matches(t,C.grep(e,function(t){return 1===t.nodeType}))},C.fn.extend({find:function(t){var e,n,r=this.length,i=this;if("string"!=typeof t)return this.pushStack(C(t).filter(function(){for(e=0;e1?C.uniqueSort(n):n},filter:function(t){return this.pushStack(N(this,t||[],!1))},not:function(t){return this.pushStack(N(this,t||[],!0))},is:function(t){return!!N(this,"string"==typeof t&&E.test(t)?C(t):t||[],!1).length}});var D,I=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/;(C.fn.init=function(t,e,n){var r,i;if(!t)return this;if(n=n||D,"string"==typeof t){if(!(r="<"===t[0]&&">"===t[t.length-1]&&t.length>=3?[null,t,null]:I.exec(t))||!r[1]&&e)return!e||e.jquery?(e||n).find(t):this.constructor(e).find(t);if(r[1]){if(e=e instanceof C?e[0]:e,C.merge(this,C.parseHTML(r[1],e&&e.nodeType?e.ownerDocument||e:a,!0)),j.test(r[1])&&C.isPlainObject(e))for(r in e)y(this[r])?this[r](e[r]):this.attr(r,e[r]);return this}return(i=a.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return t.nodeType?(this[0]=t,this.length=1,this):y(t)?void 0!==n.ready?n.ready(t):t(C):C.makeArray(t,this)}).prototype=C.fn,D=C(a);var L=/^(?:parents|prev(?:Until|All))/,R={children:!0,contents:!0,next:!0,prev:!0};function P(t,e){for(;(t=t[e])&&1!==t.nodeType;);return t}C.fn.extend({has:function(t){var e=C(t,this),n=e.length;return this.filter(function(){for(var t=0;t-1:1===n.nodeType&&C.find.matchesSelector(n,t))){o.push(n);break}return this.pushStack(o.length>1?C.uniqueSort(o):o)},index:function(t){return t?"string"==typeof t?f.call(C(t),this[0]):f.call(this,t.jquery?t[0]:t):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(t,e){return this.pushStack(C.uniqueSort(C.merge(this.get(),C(t,e))))},addBack:function(t){return this.add(null==t?this.prevObject:this.prevObject.filter(t))}}),C.each({parent:function(t){var e=t.parentNode;return e&&11!==e.nodeType?e:null},parents:function(t){return S(t,"parentNode")},parentsUntil:function(t,e,n){return S(t,"parentNode",n)},next:function(t){return P(t,"nextSibling")},prev:function(t){return P(t,"previousSibling")},nextAll:function(t){return S(t,"nextSibling")},prevAll:function(t){return S(t,"previousSibling")},nextUntil:function(t,e,n){return S(t,"nextSibling",n)},prevUntil:function(t,e,n){return S(t,"previousSibling",n)},siblings:function(t){return A((t.parentNode||{}).firstChild,t)},children:function(t){return A(t.firstChild)},contents:function(t){return O(t,"iframe")?t.contentDocument:(O(t,"template")&&(t=t.content||t),C.merge([],t.childNodes))}},function(t,e){C.fn[t]=function(n,r){var i=C.map(this,e,n);return"Until"!==t.slice(-5)&&(r=n),r&&"string"==typeof r&&(i=C.filter(r,i)),this.length>1&&(R[t]||C.uniqueSort(i),L.test(t)&&i.reverse()),this.pushStack(i)}});var F=/[^\x20\t\r\n\f]+/g;function M(t){return t}function q(t){throw t}function U(t,e,n,r){var i;try{t&&y(i=t.promise)?i.call(t).done(e).fail(n):t&&y(i=t.then)?i.call(t,e,n):e.apply(void 0,[t].slice(r))}catch(t){n.apply(void 0,[t])}}C.Callbacks=function(t){t="string"==typeof t?function(t){var e={};return C.each(t.match(F)||[],function(t,n){e[n]=!0}),e}(t):C.extend({},t);var e,n,r,i,o=[],a=[],s=-1,u=function(){for(i=i||t.once,r=e=!0;a.length;s=-1)for(n=a.shift();++s-1;)o.splice(n,1),n<=s&&s--}),this},has:function(t){return t?C.inArray(t,o)>-1:o.length>0},empty:function(){return o&&(o=[]),this},disable:function(){return i=a=[],o=n="",this},disabled:function(){return!o},lock:function(){return i=a=[],n||e||(o=n=""),this},locked:function(){return!!i},fireWith:function(t,n){return i||(n=[t,(n=n||[]).slice?n.slice():n],a.push(n),e||u()),this},fire:function(){return c.fireWith(this,arguments),this},fired:function(){return!!r}};return c},C.extend({Deferred:function(t){var e=[["notify","progress",C.Callbacks("memory"),C.Callbacks("memory"),2],["resolve","done",C.Callbacks("once memory"),C.Callbacks("once memory"),0,"resolved"],["reject","fail",C.Callbacks("once memory"),C.Callbacks("once memory"),1,"rejected"]],r="pending",i={state:function(){return r},always:function(){return o.done(arguments).fail(arguments),this},catch:function(t){return i.then(null,t)},pipe:function(){var t=arguments;return C.Deferred(function(n){C.each(e,function(e,r){var i=y(t[r[4]])&&t[r[4]];o[r[1]](function(){var t=i&&i.apply(this,arguments);t&&y(t.promise)?t.promise().progress(n.notify).done(n.resolve).fail(n.reject):n[r[0]+"With"](this,i?[t]:arguments)})}),t=null}).promise()},then:function(t,r,i){var o=0;function a(t,e,r,i){return function(){var s=this,u=arguments,c=function(){var n,c;if(!(t=o&&(r!==q&&(s=void 0,u=[n]),e.rejectWith(s,u))}};t?l():(C.Deferred.getStackHook&&(l.stackTrace=C.Deferred.getStackHook()),n.setTimeout(l))}}return C.Deferred(function(n){e[0][3].add(a(0,n,y(i)?i:M,n.notifyWith)),e[1][3].add(a(0,n,y(t)?t:M)),e[2][3].add(a(0,n,y(r)?r:q))}).promise()},promise:function(t){return null!=t?C.extend(t,i):i}},o={};return C.each(e,function(t,n){var a=n[2],s=n[5];i[n[1]]=a.add,s&&a.add(function(){r=s},e[3-t][2].disable,e[3-t][3].disable,e[0][2].lock,e[0][3].lock),a.add(n[3].fire),o[n[0]]=function(){return o[n[0]+"With"](this===o?void 0:this,arguments),this},o[n[0]+"With"]=a.fireWith}),i.promise(o),t&&t.call(o,o),o},when:function(t){var e=arguments.length,n=e,r=Array(n),i=u.call(arguments),o=C.Deferred(),a=function(t){return function(n){r[t]=this,i[t]=arguments.length>1?u.call(arguments):n,--e||o.resolveWith(r,i)}};if(e<=1&&(U(t,o.done(a(n)).resolve,o.reject,!e),"pending"===o.state()||y(i[n]&&i[n].then)))return o.then();for(;n--;)U(i[n],a(n),o.reject);return o.promise()}});var B=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;C.Deferred.exceptionHook=function(t,e){n.console&&n.console.warn&&t&&B.test(t.name)&&n.console.warn("jQuery.Deferred exception: "+t.message,t.stack,e)},C.readyException=function(t){n.setTimeout(function(){throw t})};var H=C.Deferred();function W(){a.removeEventListener("DOMContentLoaded",W),n.removeEventListener("load",W),C.ready()}C.fn.ready=function(t){return H.then(t).catch(function(t){C.readyException(t)}),this},C.extend({isReady:!1,readyWait:1,ready:function(t){(!0===t?--C.readyWait:C.isReady)||(C.isReady=!0,!0!==t&&--C.readyWait>0||H.resolveWith(a,[C]))}}),C.ready.then=H.then,"complete"===a.readyState||"loading"!==a.readyState&&!a.documentElement.doScroll?n.setTimeout(C.ready):(a.addEventListener("DOMContentLoaded",W),n.addEventListener("load",W));var z=function(t,e,n,r,i,o,a){var s=0,u=t.length,c=null==n;if("object"===x(n))for(s in i=!0,n)z(t,e,s,n[s],!0,o,a);else if(void 0!==r&&(i=!0,y(r)||(a=!0),c&&(a?(e.call(t,r),e=null):(c=e,e=function(t,e,n){return c.call(C(t),n)})),e))for(;s1,null,!0)},removeData:function(t){return this.each(function(){Z.remove(this,t)})}}),C.extend({queue:function(t,e,n){var r;if(t)return e=(e||"fx")+"queue",r=Y.get(t,e),n&&(!r||Array.isArray(n)?r=Y.access(t,e,C.makeArray(n)):r.push(n)),r||[]},dequeue:function(t,e){e=e||"fx";var n=C.queue(t,e),r=n.length,i=n.shift(),o=C._queueHooks(t,e);"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===e&&n.unshift("inprogress"),delete o.stop,i.call(t,function(){C.dequeue(t,e)},o)),!r&&o&&o.empty.fire()},_queueHooks:function(t,e){var n=e+"queueHooks";return Y.get(t,n)||Y.access(t,n,{empty:C.Callbacks("once memory").add(function(){Y.remove(t,[e+"queue",n])})})}}),C.fn.extend({queue:function(t,e){var n=2;return"string"!=typeof t&&(e=t,t="fx",n--),arguments.length\x20\t\r\n\f]+)/i,ht=/^$|^module$|\/(?:java|ecma)script/i,vt={option:[1,""],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};function mt(t,e){var n;return n=void 0!==t.getElementsByTagName?t.getElementsByTagName(e||"*"):void 0!==t.querySelectorAll?t.querySelectorAll(e||"*"):[],void 0===e||e&&O(t,e)?C.merge([t],n):n}function gt(t,e){for(var n=0,r=t.length;n-1)i&&i.push(o);else if(c=C.contains(o.ownerDocument,o),a=mt(f.appendChild(o),"script"),c&>(a),n)for(l=0;o=a[l++];)ht.test(o.type||"")&&n.push(o);return f}yt=a.createDocumentFragment().appendChild(a.createElement("div")),(bt=a.createElement("input")).setAttribute("type","radio"),bt.setAttribute("checked","checked"),bt.setAttribute("name","t"),yt.appendChild(bt),g.checkClone=yt.cloneNode(!0).cloneNode(!0).lastChild.checked,yt.innerHTML="",g.noCloneChecked=!!yt.cloneNode(!0).lastChild.defaultValue;var xt=a.documentElement,Ct=/^key/,Tt=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,kt=/^([^.]*)(?:\.(.+)|)/;function $t(){return!0}function St(){return!1}function At(){try{return a.activeElement}catch(t){}}function Et(t,e,n,r,i,o){var a,s;if("object"==typeof e){for(s in"string"!=typeof n&&(r=r||n,n=void 0),e)Et(t,s,n,r,e[s],o);return t}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&("string"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=St;else if(!i)return t;return 1===o&&(a=i,(i=function(t){return C().off(t),a.apply(this,arguments)}).guid=a.guid||(a.guid=C.guid++)),t.each(function(){C.event.add(this,e,i,r,n)})}C.event={global:{},add:function(t,e,n,r,i){var o,a,s,u,c,l,f,p,d,h,v,m=Y.get(t);if(m)for(n.handler&&(n=(o=n).handler,i=o.selector),i&&C.find.matchesSelector(xt,i),n.guid||(n.guid=C.guid++),(u=m.events)||(u=m.events={}),(a=m.handle)||(a=m.handle=function(e){return void 0!==C&&C.event.triggered!==e.type?C.event.dispatch.apply(t,arguments):void 0}),c=(e=(e||"").match(F)||[""]).length;c--;)d=v=(s=kt.exec(e[c])||[])[1],h=(s[2]||"").split(".").sort(),d&&(f=C.event.special[d]||{},d=(i?f.delegateType:f.bindType)||d,f=C.event.special[d]||{},l=C.extend({type:d,origType:v,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&C.expr.match.needsContext.test(i),namespace:h.join(".")},o),(p=u[d])||((p=u[d]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(t,r,h,a)||t.addEventListener&&t.addEventListener(d,a)),f.add&&(f.add.call(t,l),l.handler.guid||(l.handler.guid=n.guid)),i?p.splice(p.delegateCount++,0,l):p.push(l),C.event.global[d]=!0)},remove:function(t,e,n,r,i){var o,a,s,u,c,l,f,p,d,h,v,m=Y.hasData(t)&&Y.get(t);if(m&&(u=m.events)){for(c=(e=(e||"").match(F)||[""]).length;c--;)if(d=v=(s=kt.exec(e[c])||[])[1],h=(s[2]||"").split(".").sort(),d){for(f=C.event.special[d]||{},p=u[d=(r?f.delegateType:f.bindType)||d]||[],s=s[2]&&new RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),a=o=p.length;o--;)l=p[o],!i&&v!==l.origType||n&&n.guid!==l.guid||s&&!s.test(l.namespace)||r&&r!==l.selector&&("**"!==r||!l.selector)||(p.splice(o,1),l.selector&&p.delegateCount--,f.remove&&f.remove.call(t,l));a&&!p.length&&(f.teardown&&!1!==f.teardown.call(t,h,m.handle)||C.removeEvent(t,d,m.handle),delete u[d])}else for(d in u)C.event.remove(t,d+e[c],n,r,!0);C.isEmptyObject(u)&&Y.remove(t,"handle events")}},dispatch:function(t){var e,n,r,i,o,a,s=C.event.fix(t),u=new Array(arguments.length),c=(Y.get(this,"events")||{})[s.type]||[],l=C.event.special[s.type]||{};for(u[0]=s,e=1;e=1))for(;c!==this;c=c.parentNode||this)if(1===c.nodeType&&("click"!==t.type||!0!==c.disabled)){for(o=[],a={},n=0;n-1:C.find(i,this,null,[c]).length),a[i]&&o.push(r);o.length&&s.push({elem:c,handlers:o})}return c=this,u\x20\t\r\n\f]*)[^>]*)\/>/gi,jt=/\s*$/g;function It(t,e){return O(t,"table")&&O(11!==e.nodeType?e:e.firstChild,"tr")&&C(t).children("tbody")[0]||t}function Lt(t){return t.type=(null!==t.getAttribute("type"))+"/"+t.type,t}function Rt(t){return"true/"===(t.type||"").slice(0,5)?t.type=t.type.slice(5):t.removeAttribute("type"),t}function Pt(t,e){var n,r,i,o,a,s,u,c;if(1===e.nodeType){if(Y.hasData(t)&&(o=Y.access(t),a=Y.set(e,o),c=o.events))for(i in delete a.handle,a.events={},c)for(n=0,r=c[i].length;n1&&"string"==typeof h&&!g.checkClone&&Nt.test(h))return t.each(function(i){var o=t.eq(i);v&&(e[0]=h.call(this,i,o.html())),Ft(o,e,n,r)});if(p&&(o=(i=wt(e,t[0].ownerDocument,!1,t,r)).firstChild,1===i.childNodes.length&&(i=o),o||r)){for(s=(a=C.map(mt(i,"script"),Lt)).length;f")},clone:function(t,e,n){var r,i,o,a,s,u,c,l=t.cloneNode(!0),f=C.contains(t.ownerDocument,t);if(!(g.noCloneChecked||1!==t.nodeType&&11!==t.nodeType||C.isXMLDoc(t)))for(a=mt(l),r=0,i=(o=mt(t)).length;r0&>(a,!f&&mt(t,"script")),l},cleanData:function(t){for(var e,n,r,i=C.event.special,o=0;void 0!==(n=t[o]);o++)if(G(n)){if(e=n[Y.expando]){if(e.events)for(r in e.events)i[r]?C.event.remove(n,r):C.removeEvent(n,r,e.handle);n[Y.expando]=void 0}n[Z.expando]&&(n[Z.expando]=void 0)}}}),C.fn.extend({detach:function(t){return Mt(this,t,!0)},remove:function(t){return Mt(this,t)},text:function(t){return z(this,function(t){return void 0===t?C.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=t)})},null,t,arguments.length)},append:function(){return Ft(this,arguments,function(t){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||It(this,t).appendChild(t)})},prepend:function(){return Ft(this,arguments,function(t){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var e=It(this,t);e.insertBefore(t,e.firstChild)}})},before:function(){return Ft(this,arguments,function(t){this.parentNode&&this.parentNode.insertBefore(t,this)})},after:function(){return Ft(this,arguments,function(t){this.parentNode&&this.parentNode.insertBefore(t,this.nextSibling)})},empty:function(){for(var t,e=0;null!=(t=this[e]);e++)1===t.nodeType&&(C.cleanData(mt(t,!1)),t.textContent="");return this},clone:function(t,e){return t=null!=t&&t,e=null==e?t:e,this.map(function(){return C.clone(this,t,e)})},html:function(t){return z(this,function(t){var e=this[0]||{},n=0,r=this.length;if(void 0===t&&1===e.nodeType)return e.innerHTML;if("string"==typeof t&&!jt.test(t)&&!vt[(dt.exec(t)||["",""])[1].toLowerCase()]){t=C.htmlPrefilter(t);try{for(;n=0&&(u+=Math.max(0,Math.ceil(t["offset"+e[0].toUpperCase()+e.slice(1)]-o-u-s-.5))),u}function te(t,e,n){var r=Ut(t),i=Ht(t,e,r),o="border-box"===C.css(t,"boxSizing",!1,r),a=o;if(qt.test(i)){if(!n)return i;i="auto"}return a=a&&(g.boxSizingReliable()||i===t.style[e]),("auto"===i||!parseFloat(i)&&"inline"===C.css(t,"display",!1,r))&&(i=t["offset"+e[0].toUpperCase()+e.slice(1)],a=!0),(i=parseFloat(i)||0)+Zt(t,e,n||(o?"border":"content"),a,r,i)+"px"}function ee(t,e,n,r,i){return new ee.prototype.init(t,e,n,r,i)}C.extend({cssHooks:{opacity:{get:function(t,e){if(e){var n=Ht(t,"opacity");return""===n?"1":n}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{},style:function(t,e,n,r){if(t&&3!==t.nodeType&&8!==t.nodeType&&t.style){var i,o,a,s=J(e),u=Vt.test(e),c=t.style;if(u||(e=Qt(s)),a=C.cssHooks[e]||C.cssHooks[s],void 0===n)return a&&"get"in a&&void 0!==(i=a.get(t,!1,r))?i:c[e];"string"===(o=typeof n)&&(i=it.exec(n))&&i[1]&&(n=ut(t,e,i),o="number"),null!=n&&n==n&&("number"===o&&(n+=i&&i[3]||(C.cssNumber[s]?"":"px")),g.clearCloneStyle||""!==n||0!==e.indexOf("background")||(c[e]="inherit"),a&&"set"in a&&void 0===(n=a.set(t,n,r))||(u?c.setProperty(e,n):c[e]=n))}},css:function(t,e,n,r){var i,o,a,s=J(e);return Vt.test(e)||(e=Qt(s)),(a=C.cssHooks[e]||C.cssHooks[s])&&"get"in a&&(i=a.get(t,!0,n)),void 0===i&&(i=Ht(t,e,r)),"normal"===i&&e in Kt&&(i=Kt[e]),""===n||n?(o=parseFloat(i),!0===n||isFinite(o)?o||0:i):i}}),C.each(["height","width"],function(t,e){C.cssHooks[e]={get:function(t,n,r){if(n)return!zt.test(C.css(t,"display"))||t.getClientRects().length&&t.getBoundingClientRect().width?te(t,e,r):st(t,Xt,function(){return te(t,e,r)})},set:function(t,n,r){var i,o=Ut(t),a="border-box"===C.css(t,"boxSizing",!1,o),s=r&&Zt(t,e,r,a,o);return a&&g.scrollboxSize()===o.position&&(s-=Math.ceil(t["offset"+e[0].toUpperCase()+e.slice(1)]-parseFloat(o[e])-Zt(t,e,"border",!1,o)-.5)),s&&(i=it.exec(n))&&"px"!==(i[3]||"px")&&(t.style[e]=n,n=C.css(t,e)),Yt(0,n,s)}}}),C.cssHooks.marginLeft=Wt(g.reliableMarginLeft,function(t,e){if(e)return(parseFloat(Ht(t,"marginLeft"))||t.getBoundingClientRect().left-st(t,{marginLeft:0},function(){return t.getBoundingClientRect().left}))+"px"}),C.each({margin:"",padding:"",border:"Width"},function(t,e){C.cssHooks[t+e]={expand:function(n){for(var r=0,i={},o="string"==typeof n?n.split(" "):[n];r<4;r++)i[t+ot[r]+e]=o[r]||o[r-2]||o[0];return i}},"margin"!==t&&(C.cssHooks[t+e].set=Yt)}),C.fn.extend({css:function(t,e){return z(this,function(t,e,n){var r,i,o={},a=0;if(Array.isArray(e)){for(r=Ut(t),i=e.length;a1)}}),C.Tween=ee,ee.prototype={constructor:ee,init:function(t,e,n,r,i,o){this.elem=t,this.prop=n,this.easing=i||C.easing._default,this.options=e,this.start=this.now=this.cur(),this.end=r,this.unit=o||(C.cssNumber[n]?"":"px")},cur:function(){var t=ee.propHooks[this.prop];return t&&t.get?t.get(this):ee.propHooks._default.get(this)},run:function(t){var e,n=ee.propHooks[this.prop];return this.options.duration?this.pos=e=C.easing[this.easing](t,this.options.duration*t,0,1,this.options.duration):this.pos=e=t,this.now=(this.end-this.start)*e+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):ee.propHooks._default.set(this),this}},ee.prototype.init.prototype=ee.prototype,ee.propHooks={_default:{get:function(t){var e;return 1!==t.elem.nodeType||null!=t.elem[t.prop]&&null==t.elem.style[t.prop]?t.elem[t.prop]:(e=C.css(t.elem,t.prop,""))&&"auto"!==e?e:0},set:function(t){C.fx.step[t.prop]?C.fx.step[t.prop](t):1!==t.elem.nodeType||null==t.elem.style[C.cssProps[t.prop]]&&!C.cssHooks[t.prop]?t.elem[t.prop]=t.now:C.style(t.elem,t.prop,t.now+t.unit)}}},ee.propHooks.scrollTop=ee.propHooks.scrollLeft={set:function(t){t.elem.nodeType&&t.elem.parentNode&&(t.elem[t.prop]=t.now)}},C.easing={linear:function(t){return t},swing:function(t){return.5-Math.cos(t*Math.PI)/2},_default:"swing"},C.fx=ee.prototype.init,C.fx.step={};var ne,re,ie=/^(?:toggle|show|hide)$/,oe=/queueHooks$/;function ae(){re&&(!1===a.hidden&&n.requestAnimationFrame?n.requestAnimationFrame(ae):n.setTimeout(ae,C.fx.interval),C.fx.tick())}function se(){return n.setTimeout(function(){ne=void 0}),ne=Date.now()}function ue(t,e){var n,r=0,i={height:t};for(e=e?1:0;r<4;r+=2-e)i["margin"+(n=ot[r])]=i["padding"+n]=t;return e&&(i.opacity=i.width=t),i}function ce(t,e,n){for(var r,i=(le.tweeners[e]||[]).concat(le.tweeners["*"]),o=0,a=i.length;o1)},removeAttr:function(t){return this.each(function(){C.removeAttr(this,t)})}}),C.extend({attr:function(t,e,n){var r,i,o=t.nodeType;if(3!==o&&8!==o&&2!==o)return void 0===t.getAttribute?C.prop(t,e,n):(1===o&&C.isXMLDoc(t)||(i=C.attrHooks[e.toLowerCase()]||(C.expr.match.bool.test(e)?fe:void 0)),void 0!==n?null===n?void C.removeAttr(t,e):i&&"set"in i&&void 0!==(r=i.set(t,n,e))?r:(t.setAttribute(e,n+""),n):i&&"get"in i&&null!==(r=i.get(t,e))?r:null==(r=C.find.attr(t,e))?void 0:r)},attrHooks:{type:{set:function(t,e){if(!g.radioValue&&"radio"===e&&O(t,"input")){var n=t.value;return t.setAttribute("type",e),n&&(t.value=n),e}}}},removeAttr:function(t,e){var n,r=0,i=e&&e.match(F);if(i&&1===t.nodeType)for(;n=i[r++];)t.removeAttribute(n)}}),fe={set:function(t,e,n){return!1===e?C.removeAttr(t,n):t.setAttribute(n,n),n}},C.each(C.expr.match.bool.source.match(/\w+/g),function(t,e){var n=pe[e]||C.find.attr;pe[e]=function(t,e,r){var i,o,a=e.toLowerCase();return r||(o=pe[a],pe[a]=i,i=null!=n(t,e,r)?a:null,pe[a]=o),i}});var de=/^(?:input|select|textarea|button)$/i,he=/^(?:a|area)$/i;function ve(t){return(t.match(F)||[]).join(" ")}function me(t){return t.getAttribute&&t.getAttribute("class")||""}function ge(t){return Array.isArray(t)?t:"string"==typeof t&&t.match(F)||[]}C.fn.extend({prop:function(t,e){return z(this,C.prop,t,e,arguments.length>1)},removeProp:function(t){return this.each(function(){delete this[C.propFix[t]||t]})}}),C.extend({prop:function(t,e,n){var r,i,o=t.nodeType;if(3!==o&&8!==o&&2!==o)return 1===o&&C.isXMLDoc(t)||(e=C.propFix[e]||e,i=C.propHooks[e]),void 0!==n?i&&"set"in i&&void 0!==(r=i.set(t,n,e))?r:t[e]=n:i&&"get"in i&&null!==(r=i.get(t,e))?r:t[e]},propHooks:{tabIndex:{get:function(t){var e=C.find.attr(t,"tabindex");return e?parseInt(e,10):de.test(t.nodeName)||he.test(t.nodeName)&&t.href?0:-1}}},propFix:{for:"htmlFor",class:"className"}}),g.optSelected||(C.propHooks.selected={get:function(t){var e=t.parentNode;return e&&e.parentNode&&e.parentNode.selectedIndex,null},set:function(t){var e=t.parentNode;e&&(e.selectedIndex,e.parentNode&&e.parentNode.selectedIndex)}}),C.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){C.propFix[this.toLowerCase()]=this}),C.fn.extend({addClass:function(t){var e,n,r,i,o,a,s,u=0;if(y(t))return this.each(function(e){C(this).addClass(t.call(this,e,me(this)))});if((e=ge(t)).length)for(;n=this[u++];)if(i=me(n),r=1===n.nodeType&&" "+ve(i)+" "){for(a=0;o=e[a++];)r.indexOf(" "+o+" ")<0&&(r+=o+" ");i!==(s=ve(r))&&n.setAttribute("class",s)}return this},removeClass:function(t){var e,n,r,i,o,a,s,u=0;if(y(t))return this.each(function(e){C(this).removeClass(t.call(this,e,me(this)))});if(!arguments.length)return this.attr("class","");if((e=ge(t)).length)for(;n=this[u++];)if(i=me(n),r=1===n.nodeType&&" "+ve(i)+" "){for(a=0;o=e[a++];)for(;r.indexOf(" "+o+" ")>-1;)r=r.replace(" "+o+" "," ");i!==(s=ve(r))&&n.setAttribute("class",s)}return this},toggleClass:function(t,e){var n=typeof t,r="string"===n||Array.isArray(t);return"boolean"==typeof e&&r?e?this.addClass(t):this.removeClass(t):y(t)?this.each(function(n){C(this).toggleClass(t.call(this,n,me(this),e),e)}):this.each(function(){var e,i,o,a;if(r)for(i=0,o=C(this),a=ge(t);e=a[i++];)o.hasClass(e)?o.removeClass(e):o.addClass(e);else void 0!==t&&"boolean"!==n||((e=me(this))&&Y.set(this,"__className__",e),this.setAttribute&&this.setAttribute("class",e||!1===t?"":Y.get(this,"__className__")||""))})},hasClass:function(t){var e,n,r=0;for(e=" "+t+" ";n=this[r++];)if(1===n.nodeType&&(" "+ve(me(n))+" ").indexOf(e)>-1)return!0;return!1}});var ye=/\r/g;C.fn.extend({val:function(t){var e,n,r,i=this[0];return arguments.length?(r=y(t),this.each(function(n){var i;1===this.nodeType&&(null==(i=r?t.call(this,n,C(this).val()):t)?i="":"number"==typeof i?i+="":Array.isArray(i)&&(i=C.map(i,function(t){return null==t?"":t+""})),(e=C.valHooks[this.type]||C.valHooks[this.nodeName.toLowerCase()])&&"set"in e&&void 0!==e.set(this,i,"value")||(this.value=i))})):i?(e=C.valHooks[i.type]||C.valHooks[i.nodeName.toLowerCase()])&&"get"in e&&void 0!==(n=e.get(i,"value"))?n:"string"==typeof(n=i.value)?n.replace(ye,""):null==n?"":n:void 0}}),C.extend({valHooks:{option:{get:function(t){var e=C.find.attr(t,"value");return null!=e?e:ve(C.text(t))}},select:{get:function(t){var e,n,r,i=t.options,o=t.selectedIndex,a="select-one"===t.type,s=a?null:[],u=a?o+1:i.length;for(r=o<0?u:a?o:0;r-1)&&(n=!0);return n||(t.selectedIndex=-1),o}}}}),C.each(["radio","checkbox"],function(){C.valHooks[this]={set:function(t,e){if(Array.isArray(e))return t.checked=C.inArray(C(t).val(),e)>-1}},g.checkOn||(C.valHooks[this].get=function(t){return null===t.getAttribute("value")?"on":t.value})}),g.focusin="onfocusin"in n;var be=/^(?:focusinfocus|focusoutblur)$/,_e=function(t){t.stopPropagation()};C.extend(C.event,{trigger:function(t,e,r,i){var o,s,u,c,l,f,p,d,v=[r||a],m=h.call(t,"type")?t.type:t,g=h.call(t,"namespace")?t.namespace.split("."):[];if(s=d=u=r=r||a,3!==r.nodeType&&8!==r.nodeType&&!be.test(m+C.event.triggered)&&(m.indexOf(".")>-1&&(m=(g=m.split(".")).shift(),g.sort()),l=m.indexOf(":")<0&&"on"+m,(t=t[C.expando]?t:new C.Event(m,"object"==typeof t&&t)).isTrigger=i?2:3,t.namespace=g.join("."),t.rnamespace=t.namespace?new RegExp("(^|\\.)"+g.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,t.result=void 0,t.target||(t.target=r),e=null==e?[t]:C.makeArray(e,[t]),p=C.event.special[m]||{},i||!p.trigger||!1!==p.trigger.apply(r,e))){if(!i&&!p.noBubble&&!b(r)){for(c=p.delegateType||m,be.test(c+m)||(s=s.parentNode);s;s=s.parentNode)v.push(s),u=s;u===(r.ownerDocument||a)&&v.push(u.defaultView||u.parentWindow||n)}for(o=0;(s=v[o++])&&!t.isPropagationStopped();)d=s,t.type=o>1?c:p.bindType||m,(f=(Y.get(s,"events")||{})[t.type]&&Y.get(s,"handle"))&&f.apply(s,e),(f=l&&s[l])&&f.apply&&G(s)&&(t.result=f.apply(s,e),!1===t.result&&t.preventDefault());return t.type=m,i||t.isDefaultPrevented()||p._default&&!1!==p._default.apply(v.pop(),e)||!G(r)||l&&y(r[m])&&!b(r)&&((u=r[l])&&(r[l]=null),C.event.triggered=m,t.isPropagationStopped()&&d.addEventListener(m,_e),r[m](),t.isPropagationStopped()&&d.removeEventListener(m,_e),C.event.triggered=void 0,u&&(r[l]=u)),t.result}},simulate:function(t,e,n){var r=C.extend(new C.Event,n,{type:t,isSimulated:!0});C.event.trigger(r,null,e)}}),C.fn.extend({trigger:function(t,e){return this.each(function(){C.event.trigger(t,e,this)})},triggerHandler:function(t,e){var n=this[0];if(n)return C.event.trigger(t,e,n,!0)}}),g.focusin||C.each({focus:"focusin",blur:"focusout"},function(t,e){var n=function(t){C.event.simulate(e,t.target,C.event.fix(t))};C.event.special[e]={setup:function(){var r=this.ownerDocument||this,i=Y.access(r,e);i||r.addEventListener(t,n,!0),Y.access(r,e,(i||0)+1)},teardown:function(){var r=this.ownerDocument||this,i=Y.access(r,e)-1;i?Y.access(r,e,i):(r.removeEventListener(t,n,!0),Y.remove(r,e))}}});var we=n.location,xe=Date.now(),Ce=/\?/;C.parseXML=function(t){var e;if(!t||"string"!=typeof t)return null;try{e=(new n.DOMParser).parseFromString(t,"text/xml")}catch(t){e=void 0}return e&&!e.getElementsByTagName("parsererror").length||C.error("Invalid XML: "+t),e};var Te=/\[\]$/,ke=/\r?\n/g,$e=/^(?:submit|button|image|reset|file)$/i,Se=/^(?:input|select|textarea|keygen)/i;function Ae(t,e,n,r){var i;if(Array.isArray(e))C.each(e,function(e,i){n||Te.test(t)?r(t,i):Ae(t+"["+("object"==typeof i&&null!=i?e:"")+"]",i,n,r)});else if(n||"object"!==x(e))r(t,e);else for(i in e)Ae(t+"["+i+"]",e[i],n,r)}C.param=function(t,e){var n,r=[],i=function(t,e){var n=y(e)?e():e;r[r.length]=encodeURIComponent(t)+"="+encodeURIComponent(null==n?"":n)};if(Array.isArray(t)||t.jquery&&!C.isPlainObject(t))C.each(t,function(){i(this.name,this.value)});else for(n in t)Ae(n,t[n],e,i);return r.join("&")},C.fn.extend({serialize:function(){return C.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var t=C.prop(this,"elements");return t?C.makeArray(t):this}).filter(function(){var t=this.type;return this.name&&!C(this).is(":disabled")&&Se.test(this.nodeName)&&!$e.test(t)&&(this.checked||!pt.test(t))}).map(function(t,e){var n=C(this).val();return null==n?null:Array.isArray(n)?C.map(n,function(t){return{name:e.name,value:t.replace(ke,"\r\n")}}):{name:e.name,value:n.replace(ke,"\r\n")}}).get()}});var Ee=/%20/g,Oe=/#.*$/,je=/([?&])_=[^&]*/,Ne=/^(.*?):[ \t]*([^\r\n]*)$/gm,De=/^(?:GET|HEAD)$/,Ie=/^\/\//,Le={},Re={},Pe="*/".concat("*"),Fe=a.createElement("a");function Me(t){return function(e,n){"string"!=typeof e&&(n=e,e="*");var r,i=0,o=e.toLowerCase().match(F)||[];if(y(n))for(;r=o[i++];)"+"===r[0]?(r=r.slice(1)||"*",(t[r]=t[r]||[]).unshift(n)):(t[r]=t[r]||[]).push(n)}}function qe(t,e,n,r){var i={},o=t===Re;function a(s){var u;return i[s]=!0,C.each(t[s]||[],function(t,s){var c=s(e,n,r);return"string"!=typeof c||o||i[c]?o?!(u=c):void 0:(e.dataTypes.unshift(c),a(c),!1)}),u}return a(e.dataTypes[0])||!i["*"]&&a("*")}function Ue(t,e){var n,r,i=C.ajaxSettings.flatOptions||{};for(n in e)void 0!==e[n]&&((i[n]?t:r||(r={}))[n]=e[n]);return r&&C.extend(!0,t,r),t}Fe.href=we.href,C.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:we.href,type:"GET",isLocal:/^(?:about|app|app-storage|.+-extension|file|res|widget):$/.test(we.protocol),global:!0,processData:!0,async:!0,contentType:"application/x-www-form-urlencoded; charset=UTF-8",accepts:{"*":Pe,text:"text/plain",html:"text/html",xml:"application/xml, text/xml",json:"application/json, text/javascript"},contents:{xml:/\bxml\b/,html:/\bhtml/,json:/\bjson\b/},responseFields:{xml:"responseXML",text:"responseText",json:"responseJSON"},converters:{"* text":String,"text html":!0,"text json":JSON.parse,"text xml":C.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(t,e){return e?Ue(Ue(t,C.ajaxSettings),e):Ue(C.ajaxSettings,t)},ajaxPrefilter:Me(Le),ajaxTransport:Me(Re),ajax:function(t,e){"object"==typeof t&&(e=t,t=void 0),e=e||{};var r,i,o,s,u,c,l,f,p,d,h=C.ajaxSetup({},e),v=h.context||h,m=h.context&&(v.nodeType||v.jquery)?C(v):C.event,g=C.Deferred(),y=C.Callbacks("once memory"),b=h.statusCode||{},_={},w={},x="canceled",T={readyState:0,getResponseHeader:function(t){var e;if(l){if(!s)for(s={};e=Ne.exec(o);)s[e[1].toLowerCase()]=e[2];e=s[t.toLowerCase()]}return null==e?null:e},getAllResponseHeaders:function(){return l?o:null},setRequestHeader:function(t,e){return null==l&&(t=w[t.toLowerCase()]=w[t.toLowerCase()]||t,_[t]=e),this},overrideMimeType:function(t){return null==l&&(h.mimeType=t),this},statusCode:function(t){var e;if(t)if(l)T.always(t[T.status]);else for(e in t)b[e]=[b[e],t[e]];return this},abort:function(t){var e=t||x;return r&&r.abort(e),k(0,e),this}};if(g.promise(T),h.url=((t||h.url||we.href)+"").replace(Ie,we.protocol+"//"),h.type=e.method||e.type||h.method||h.type,h.dataTypes=(h.dataType||"*").toLowerCase().match(F)||[""],null==h.crossDomain){c=a.createElement("a");try{c.href=h.url,c.href=c.href,h.crossDomain=Fe.protocol+"//"+Fe.host!=c.protocol+"//"+c.host}catch(t){h.crossDomain=!0}}if(h.data&&h.processData&&"string"!=typeof h.data&&(h.data=C.param(h.data,h.traditional)),qe(Le,h,e,T),l)return T;for(p in(f=C.event&&h.global)&&0==C.active++&&C.event.trigger("ajaxStart"),h.type=h.type.toUpperCase(),h.hasContent=!De.test(h.type),i=h.url.replace(Oe,""),h.hasContent?h.data&&h.processData&&0===(h.contentType||"").indexOf("application/x-www-form-urlencoded")&&(h.data=h.data.replace(Ee,"+")):(d=h.url.slice(i.length),h.data&&(h.processData||"string"==typeof h.data)&&(i+=(Ce.test(i)?"&":"?")+h.data,delete h.data),!1===h.cache&&(i=i.replace(je,"$1"),d=(Ce.test(i)?"&":"?")+"_="+xe+++d),h.url=i+d),h.ifModified&&(C.lastModified[i]&&T.setRequestHeader("If-Modified-Since",C.lastModified[i]),C.etag[i]&&T.setRequestHeader("If-None-Match",C.etag[i])),(h.data&&h.hasContent&&!1!==h.contentType||e.contentType)&&T.setRequestHeader("Content-Type",h.contentType),T.setRequestHeader("Accept",h.dataTypes[0]&&h.accepts[h.dataTypes[0]]?h.accepts[h.dataTypes[0]]+("*"!==h.dataTypes[0]?", "+Pe+"; q=0.01":""):h.accepts["*"]),h.headers)T.setRequestHeader(p,h.headers[p]);if(h.beforeSend&&(!1===h.beforeSend.call(v,T,h)||l))return T.abort();if(x="abort",y.add(h.complete),T.done(h.success),T.fail(h.error),r=qe(Re,h,e,T)){if(T.readyState=1,f&&m.trigger("ajaxSend",[T,h]),l)return T;h.async&&h.timeout>0&&(u=n.setTimeout(function(){T.abort("timeout")},h.timeout));try{l=!1,r.send(_,k)}catch(t){if(l)throw t;k(-1,t)}}else k(-1,"No Transport");function k(t,e,a,s){var c,p,d,_,w,x=e;l||(l=!0,u&&n.clearTimeout(u),r=void 0,o=s||"",T.readyState=t>0?4:0,c=t>=200&&t<300||304===t,a&&(_=function(t,e,n){for(var r,i,o,a,s=t.contents,u=t.dataTypes;"*"===u[0];)u.shift(),void 0===r&&(r=t.mimeType||e.getResponseHeader("Content-Type"));if(r)for(i in s)if(s[i]&&s[i].test(r)){u.unshift(i);break}if(u[0]in n)o=u[0];else{for(i in n){if(!u[0]||t.converters[i+" "+u[0]]){o=i;break}a||(a=i)}o=o||a}if(o)return o!==u[0]&&u.unshift(o),n[o]}(h,T,a)),_=function(t,e,n,r){var i,o,a,s,u,c={},l=t.dataTypes.slice();if(l[1])for(a in t.converters)c[a.toLowerCase()]=t.converters[a];for(o=l.shift();o;)if(t.responseFields[o]&&(n[t.responseFields[o]]=e),!u&&r&&t.dataFilter&&(e=t.dataFilter(e,t.dataType)),u=o,o=l.shift())if("*"===o)o=u;else if("*"!==u&&u!==o){if(!(a=c[u+" "+o]||c["* "+o]))for(i in c)if((s=i.split(" "))[1]===o&&(a=c[u+" "+s[0]]||c["* "+s[0]])){!0===a?a=c[i]:!0!==c[i]&&(o=s[0],l.unshift(s[1]));break}if(!0!==a)if(a&&t.throws)e=a(e);else try{e=a(e)}catch(t){return{state:"parsererror",error:a?t:"No conversion from "+u+" to "+o}}}return{state:"success",data:e}}(h,_,T,c),c?(h.ifModified&&((w=T.getResponseHeader("Last-Modified"))&&(C.lastModified[i]=w),(w=T.getResponseHeader("etag"))&&(C.etag[i]=w)),204===t||"HEAD"===h.type?x="nocontent":304===t?x="notmodified":(x=_.state,p=_.data,c=!(d=_.error))):(d=x,!t&&x||(x="error",t<0&&(t=0))),T.status=t,T.statusText=(e||x)+"",c?g.resolveWith(v,[p,x,T]):g.rejectWith(v,[T,x,d]),T.statusCode(b),b=void 0,f&&m.trigger(c?"ajaxSuccess":"ajaxError",[T,h,c?p:d]),y.fireWith(v,[T,x]),f&&(m.trigger("ajaxComplete",[T,h]),--C.active||C.event.trigger("ajaxStop")))}return T},getJSON:function(t,e,n){return C.get(t,e,n,"json")},getScript:function(t,e){return C.get(t,void 0,e,"script")}}),C.each(["get","post"],function(t,e){C[e]=function(t,n,r,i){return y(n)&&(i=i||r,r=n,n=void 0),C.ajax(C.extend({url:t,type:e,dataType:i,data:n,success:r},C.isPlainObject(t)&&t))}}),C._evalUrl=function(t){return C.ajax({url:t,type:"GET",dataType:"script",cache:!0,async:!1,global:!1,throws:!0})},C.fn.extend({wrapAll:function(t){var e;return this[0]&&(y(t)&&(t=t.call(this[0])),e=C(t,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&e.insertBefore(this[0]),e.map(function(){for(var t=this;t.firstElementChild;)t=t.firstElementChild;return t}).append(this)),this},wrapInner:function(t){return y(t)?this.each(function(e){C(this).wrapInner(t.call(this,e))}):this.each(function(){var e=C(this),n=e.contents();n.length?n.wrapAll(t):e.append(t)})},wrap:function(t){var e=y(t);return this.each(function(n){C(this).wrapAll(e?t.call(this,n):t)})},unwrap:function(t){return this.parent(t).not("body").each(function(){C(this).replaceWith(this.childNodes)}),this}}),C.expr.pseudos.hidden=function(t){return!C.expr.pseudos.visible(t)},C.expr.pseudos.visible=function(t){return!!(t.offsetWidth||t.offsetHeight||t.getClientRects().length)},C.ajaxSettings.xhr=function(){try{return new n.XMLHttpRequest}catch(t){}};var Be={0:200,1223:204},He=C.ajaxSettings.xhr();g.cors=!!He&&"withCredentials"in He,g.ajax=He=!!He,C.ajaxTransport(function(t){var e,r;if(g.cors||He&&!t.crossDomain)return{send:function(i,o){var a,s=t.xhr();if(s.open(t.type,t.url,t.async,t.username,t.password),t.xhrFields)for(a in t.xhrFields)s[a]=t.xhrFields[a];for(a in t.mimeType&&s.overrideMimeType&&s.overrideMimeType(t.mimeType),t.crossDomain||i["X-Requested-With"]||(i["X-Requested-With"]="XMLHttpRequest"),i)s.setRequestHeader(a,i[a]);e=function(t){return function(){e&&(e=r=s.onload=s.onerror=s.onabort=s.ontimeout=s.onreadystatechange=null,"abort"===t?s.abort():"error"===t?"number"!=typeof s.status?o(0,"error"):o(s.status,s.statusText):o(Be[s.status]||s.status,s.statusText,"text"!==(s.responseType||"text")||"string"!=typeof s.responseText?{binary:s.response}:{text:s.responseText},s.getAllResponseHeaders()))}},s.onload=e(),r=s.onerror=s.ontimeout=e("error"),void 0!==s.onabort?s.onabort=r:s.onreadystatechange=function(){4===s.readyState&&n.setTimeout(function(){e&&r()})},e=e("abort");try{s.send(t.hasContent&&t.data||null)}catch(t){if(e)throw t}},abort:function(){e&&e()}}}),C.ajaxPrefilter(function(t){t.crossDomain&&(t.contents.script=!1)}),C.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/\b(?:java|ecma)script\b/},converters:{"text script":function(t){return C.globalEval(t),t}}}),C.ajaxPrefilter("script",function(t){void 0===t.cache&&(t.cache=!1),t.crossDomain&&(t.type="GET")}),C.ajaxTransport("script",function(t){var e,n;if(t.crossDomain)return{send:function(r,i){e=C(" + + \ No newline at end of file diff --git a/resources/assets/js/components/transactions/Amount.vue b/resources/assets/js/components/transactions/Amount.vue new file mode 100644 index 0000000000..34dbcd6e6c --- /dev/null +++ b/resources/assets/js/components/transactions/Amount.vue @@ -0,0 +1,104 @@ + + + + + + + \ No newline at end of file diff --git a/resources/assets/js/components/transactions/Budget.vue b/resources/assets/js/components/transactions/Budget.vue new file mode 100644 index 0000000000..0871d558c4 --- /dev/null +++ b/resources/assets/js/components/transactions/Budget.vue @@ -0,0 +1,79 @@ + + + + + + + + \ No newline at end of file diff --git a/resources/assets/js/components/transactions/Category.vue b/resources/assets/js/components/transactions/Category.vue new file mode 100644 index 0000000000..f3e4879c43 --- /dev/null +++ b/resources/assets/js/components/transactions/Category.vue @@ -0,0 +1,131 @@ + + + + + + + \ No newline at end of file diff --git a/resources/assets/js/components/transactions/CreateTransaction.vue b/resources/assets/js/components/transactions/CreateTransaction.vue new file mode 100644 index 0000000000..bb8af9ce91 --- /dev/null +++ b/resources/assets/js/components/transactions/CreateTransaction.vue @@ -0,0 +1,821 @@ + + + + + + + \ No newline at end of file diff --git a/resources/assets/js/components/transactions/CustomAttachments.vue b/resources/assets/js/components/transactions/CustomAttachments.vue new file mode 100644 index 0000000000..f56dced163 --- /dev/null +++ b/resources/assets/js/components/transactions/CustomAttachments.vue @@ -0,0 +1,59 @@ + + + + + + + \ No newline at end of file diff --git a/resources/assets/js/components/transactions/CustomDate.vue b/resources/assets/js/components/transactions/CustomDate.vue new file mode 100644 index 0000000000..46afc83516 --- /dev/null +++ b/resources/assets/js/components/transactions/CustomDate.vue @@ -0,0 +1,63 @@ + + + + + + + \ No newline at end of file diff --git a/resources/assets/js/components/transactions/CustomString.vue b/resources/assets/js/components/transactions/CustomString.vue new file mode 100644 index 0000000000..de9ca97892 --- /dev/null +++ b/resources/assets/js/components/transactions/CustomString.vue @@ -0,0 +1,63 @@ + + + + + + + \ No newline at end of file diff --git a/resources/assets/js/components/transactions/CustomTextarea.vue b/resources/assets/js/components/transactions/CustomTextarea.vue new file mode 100644 index 0000000000..de965f2abf --- /dev/null +++ b/resources/assets/js/components/transactions/CustomTextarea.vue @@ -0,0 +1,63 @@ + + + + + + + \ No newline at end of file diff --git a/resources/assets/js/components/transactions/CustomTransactionFields.vue b/resources/assets/js/components/transactions/CustomTransactionFields.vue new file mode 100644 index 0000000000..301452896d --- /dev/null +++ b/resources/assets/js/components/transactions/CustomTransactionFields.vue @@ -0,0 +1,117 @@ + + + + + + + \ No newline at end of file diff --git a/resources/assets/js/components/transactions/EditTransaction.vue b/resources/assets/js/components/transactions/EditTransaction.vue new file mode 100644 index 0000000000..eb93a816dd --- /dev/null +++ b/resources/assets/js/components/transactions/EditTransaction.vue @@ -0,0 +1,907 @@ + + + + + + + \ No newline at end of file diff --git a/resources/assets/js/components/transactions/ForeignAmountSelect.vue b/resources/assets/js/components/transactions/ForeignAmountSelect.vue new file mode 100644 index 0000000000..d2d9cbff82 --- /dev/null +++ b/resources/assets/js/components/transactions/ForeignAmountSelect.vue @@ -0,0 +1,151 @@ + + + + + + + \ No newline at end of file diff --git a/resources/assets/js/components/transactions/GroupDescription.vue b/resources/assets/js/components/transactions/GroupDescription.vue new file mode 100644 index 0000000000..5efe4f3223 --- /dev/null +++ b/resources/assets/js/components/transactions/GroupDescription.vue @@ -0,0 +1,62 @@ + + + + + + + \ No newline at end of file diff --git a/resources/assets/js/components/transactions/PiggyBank.vue b/resources/assets/js/components/transactions/PiggyBank.vue new file mode 100644 index 0000000000..3f0fabdd02 --- /dev/null +++ b/resources/assets/js/components/transactions/PiggyBank.vue @@ -0,0 +1,77 @@ + + + + + + + \ No newline at end of file diff --git a/resources/assets/js/components/transactions/StandardDate.vue b/resources/assets/js/components/transactions/StandardDate.vue new file mode 100644 index 0000000000..d5d59bd929 --- /dev/null +++ b/resources/assets/js/components/transactions/StandardDate.vue @@ -0,0 +1,59 @@ + + + + + + + \ No newline at end of file diff --git a/resources/assets/js/components/transactions/Tags.vue b/resources/assets/js/components/transactions/Tags.vue new file mode 100644 index 0000000000..f946de6bac --- /dev/null +++ b/resources/assets/js/components/transactions/Tags.vue @@ -0,0 +1,93 @@ + + + + + + + \ No newline at end of file diff --git a/resources/assets/js/components/transactions/TransactionDescription.vue b/resources/assets/js/components/transactions/TransactionDescription.vue new file mode 100644 index 0000000000..dc4c41b29e --- /dev/null +++ b/resources/assets/js/components/transactions/TransactionDescription.vue @@ -0,0 +1,58 @@ + + + + + + + \ No newline at end of file diff --git a/resources/assets/js/components/transactions/TransactionType.vue b/resources/assets/js/components/transactions/TransactionType.vue new file mode 100644 index 0000000000..94815b1051 --- /dev/null +++ b/resources/assets/js/components/transactions/TransactionType.vue @@ -0,0 +1,90 @@ + + + + + + + \ No newline at end of file diff --git a/resources/assets/sass/_variables.scss b/resources/assets/sass/_variables.scss index 5226b8edfc..785809ea0c 100644 --- a/resources/assets/sass/_variables.scss +++ b/resources/assets/sass/_variables.scss @@ -1,3 +1,23 @@ +/*! + * _variables.scss + * Copyright (c) 2019 thegrumpydictator@gmail.com + * + * This file is part of Firefly III. + * + * Firefly III is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Firefly III is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Firefly III. If not, see . + */ + /* TODO REMOVE ME */ // Body $body-bg: #f5f8fa; diff --git a/resources/assets/sass/app.scss b/resources/assets/sass/app.scss index b1c7dc4c93..40935f3958 100644 --- a/resources/assets/sass/app.scss +++ b/resources/assets/sass/app.scss @@ -1,3 +1,23 @@ +/*! + * app.scss + * Copyright (c) 2019 thegrumpydictator@gmail.com + * + * This file is part of Firefly III. + * + * Firefly III is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Firefly III is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Firefly III. If not, see . + */ + /* TODO REMOVE ME */ // Variables //@import "variables"; diff --git a/app/Helpers/Filter/FilterInterface.php b/resources/lang/cs_CZ/auth.php similarity index 65% rename from app/Helpers/Filter/FilterInterface.php rename to resources/lang/cs_CZ/auth.php index 4b08036b51..613031af89 100644 --- a/app/Helpers/Filter/FilterInterface.php +++ b/resources/lang/cs_CZ/auth.php @@ -1,7 +1,8 @@ . */ + declare(strict_types=1); -namespace FireflyIII\Helpers\Filter; - -use Illuminate\Support\Collection; - -/** - * Interface FilterInterface - */ -interface FilterInterface -{ - /** - * Apply the filter. - * - * @param Collection $set - * - * @return Collection - */ - public function filter(Collection $set): Collection; -} +return [ + 'failed' => 'Nesprávné přihlašovací údaje.', + 'throttle' => 'Příliš mnoho pokusů o přihlášení. Zkuste to znovu za :seconds sekund.', +]; diff --git a/app/Helpers/Filter/EmptyFilter.php b/resources/lang/cs_CZ/bank.php similarity index 61% rename from app/Helpers/Filter/EmptyFilter.php rename to resources/lang/cs_CZ/bank.php index 716b3d9cdb..5d00b1e685 100644 --- a/app/Helpers/Filter/EmptyFilter.php +++ b/resources/lang/cs_CZ/bank.php @@ -1,7 +1,8 @@ . */ + declare(strict_types=1); -namespace FireflyIII\Helpers\Filter; - -use Illuminate\Support\Collection; - -/** - * Class EmptyFilter. - * - * @codeCoverageIgnore - */ -class EmptyFilter implements FilterInterface -{ - /** - * Simply returns the set. - * - * @param Collection $set - * - * @return Collection - */ - public function filter(Collection $set): Collection - { - return $set; - } -} +return [ +]; diff --git a/resources/lang/cs_CZ/breadcrumbs.php b/resources/lang/cs_CZ/breadcrumbs.php new file mode 100644 index 0000000000..06bcc54eaf --- /dev/null +++ b/resources/lang/cs_CZ/breadcrumbs.php @@ -0,0 +1,59 @@ +. + */ + +declare(strict_types=1); + +return [ + 'home' => 'Domů', + 'edit_currency' => 'Upravit měnu „:name“', + 'delete_currency' => 'Odstranit měnu „:name“', + 'newPiggyBank' => 'Vytvořit novou pokladničku', + 'edit_piggyBank' => 'Upravit pokladničku „:name“', + 'preferences' => 'Předvolby', + 'profile' => 'Profil', + 'changePassword' => 'Změnit heslo', + 'change_email' => 'Změnit e-mailovou adresu', + 'bills' => 'Účtenky a faktury', + 'newBill' => 'Nová účtenka/faktura', + 'edit_bill' => 'Upravit účtenku/fakturu „:name“', + 'delete_bill' => 'Odstranit účtenku/fakturu „:name“', + 'reports' => 'Sestavy', + 'search_result' => 'Výsledky hledání pro „:query“', + 'withdrawal_list' => 'Výdaje', + 'Withdrawal_list' => 'Expenses', + 'deposit_list' => 'Výnosy, příjmy a vklady', + 'transfer_list' => 'Převody', + 'transfers_list' => 'Převody', + 'reconciliation_list' => 'Vyúčtování', + 'create_withdrawal' => 'Vytvořit nový výběr', + 'create_deposit' => 'Vytvořit nový vklad', + 'create_transfer' => 'Vytvořit nový převod', + 'create_new_transaction' => 'Vytvořit novou transakci', + 'edit_journal' => 'Upravit transakci „:description“', + 'edit_reconciliation' => 'Upravit „:description“', + 'delete_journal' => 'Odstranit transakci „:description“', + 'tags' => 'Štítky', + 'createTag' => 'Vytvořit nový štítek', + 'edit_tag' => 'Upravit štítek „:tag“', + 'delete_tag' => 'Odstranit štítek „:tag“', + 'delete_journal_link' => 'Odstranit vazbu mezi transakcemi', +]; diff --git a/resources/lang/cs_CZ/components.php b/resources/lang/cs_CZ/components.php new file mode 100644 index 0000000000..b3f2cf68ec --- /dev/null +++ b/resources/lang/cs_CZ/components.php @@ -0,0 +1,32 @@ +. + */ + +declare(strict_types=1); + +return [ + // profile + 'personal_access_tokens' => 'Osobní přístupový token', + + // bills: + 'not_expected_period' => 'Neočekáváno v tomto období', + 'not_or_not_yet' => 'Zatím ne', +]; diff --git a/resources/lang/cs_CZ/config.php b/resources/lang/cs_CZ/config.php new file mode 100644 index 0000000000..a8a9aef265 --- /dev/null +++ b/resources/lang/cs_CZ/config.php @@ -0,0 +1,51 @@ +. + */ + +declare(strict_types=1); + +return [ + 'html_language' => 'cs', + 'locale' => 'cs, Čeština, cs_CZ, cs_CZ.utf8, cs_CZ.UTF-8', + 'month' => '%B %Y', + 'month_and_day' => '%e %B %Y', + 'month_and_date_day' => '%A %B %e, %Y', + 'month_and_day_no_year' => '%B %e', + 'date_time' => '%e %B %Y, @ %T', + 'specific_day' => '%e %B %Y', + 'week_in_year' => 'Týden %W, %Y', + 'year' => '%Y', + 'half_year' => '%B %Y', + 'month_js' => 'MMMM YYYY', + 'month_and_day_js' => 'D. MMMM YYYY', + 'date_time_js' => 'D. MMMM YYYY, @ HH:mm:ss', + 'specific_day_js' => 'D. MMMM YYYY', + 'week_in_year_js' => '[Week] t, RRRR', + 'year_js' => 'YYYY', + 'half_year_js' => 'Q YYYY', + 'dow_1' => 'Pondělí', + 'dow_2' => 'Úterý', + 'dow_3' => 'Středa', + 'dow_4' => 'Čtvrtek', + 'dow_5' => 'Pátek', + 'dow_6' => 'Sobota', + 'dow_7' => 'Neděle', +]; diff --git a/resources/lang/cs_CZ/csv.php b/resources/lang/cs_CZ/csv.php new file mode 100644 index 0000000000..aae109a40a --- /dev/null +++ b/resources/lang/cs_CZ/csv.php @@ -0,0 +1,26 @@ +. + */ + +declare(strict_types=1); + +return [ +]; diff --git a/resources/lang/cs_CZ/demo.php b/resources/lang/cs_CZ/demo.php new file mode 100644 index 0000000000..cca1d66268 --- /dev/null +++ b/resources/lang/cs_CZ/demo.php @@ -0,0 +1,38 @@ +. + */ + +declare(strict_types=1); + +return [ + 'no_demo_text' => 'Je nám líto, ale pro na této stránce není žádný další text vysvětlující ukázku.', + 'see_help_icon' => 'Nicméně, ikona v pravém horním rohu vám může sdělit více.', + 'index' => 'Vítejte ve Firefly III! Tato stránka je rychlým přehledem vašich financí. Pro další informace se podívejte na Účty → Účty aktiv a samozřejmě Rozpočty a Výkazy. Nebo se prostě po aplikaci porozhlédněte.', + 'accounts-index' => 'Účty aktiv jsou vaše osobní bankovní účty. Výdajové účty jsou ty, na kterých utrácíte peníze, jako například obchody a spol. Příjmové účty jsou ty, ze kterých dostáváte peníze, jako například vaše zaměstnání, stát nebo jiné zdroje příjmu. Závazky jsou dluhy a půjčky, jako například staré dluhy z kreditní karty nebo studentské půjčky. Na této stránce je možné je upravovat nebo odebírat.', + 'budgets-index' => 'Tato stránka je přehledem vašich rozpočtů. Horní lišta zobrazuje částku která je pro rozpočet k dispozici. To může být pro libovolné období upraveno kliknutím na částku vpravo. Částka, kterou jste skutečně utratili je zobrazena v liště níže. Pod tím jsou výdaje v jednotlivých rozpočtech a co jste z nich naplánovali.', + 'reports-index-start' => 'Firefly III podporuje mnoho výkazů. Přečtěte si o nich kliknutím na ikonu - v pravém horním rohu.', + 'reports-index-examples' => 'Nezapomeňte se podívat na tyto ukázky: měsíční finanční přehled, roční finanční přehled a přehled rozpočtu.', + 'currencies-index' => 'Firefly III podporuje vícero měn. Ačkoli výchozí je Euro, je možné ho nastavit na americký dolar a mnoho dalších měn. Jak můžete vidět, malá sbírka měn je obsažena a je možné si přidat svou vlastní. Změna výchozí měny se nedotkne té u existujících transakcí: Firefly III podporuje použití vícero měn současně.', + 'transactions-index' => 'Tyto výdaje, vklady a převody nejsou zvlášť nápadité. Byly vytvořeny automaticky.', + 'piggy-banks-index' => 'Jak je možné vidět, jsou zde tři pokladničky. Pomocí tlačítek plus a mínus ovlivníte částku v každé z pokladniček. Kliknutí na název pokladničky zobrazí správu pro každou z nich.', + 'import-index' => 'Do Firefly III lze importovat libovolný CSV soubor. Také podporuje importování dat z bunq a Specter. Ostatní banky a finanční slučovače budou implementovány v budoucnu. Jako demouživatel můžete v akci vidět pouze podstrčeného poskytovatele. Vytvoří nějaké náhodné transakce pro zobrazení toho, jak proces funguje.', + 'profile-index' => 'Mějte na paměti, že obsah demostránky je každé čtyři hodny smazán. Přístup může být zrušen kdykoli. Toto se děje automaticky a nejedná se o chybu.', +]; diff --git a/resources/lang/cs_CZ/firefly.php b/resources/lang/cs_CZ/firefly.php new file mode 100644 index 0000000000..7e80a31f7f --- /dev/null +++ b/resources/lang/cs_CZ/firefly.php @@ -0,0 +1,1415 @@ +. + */ + +declare(strict_types=1); + +return [ + // general stuff: + 'close' => 'Zavřít', + 'actions' => 'Akce', + 'edit' => 'Upravit', + 'delete' => 'Odstranit', + 'split' => 'Rozdělit', + 'clone' => 'Klonovat', + 'last_seven_days' => 'Uplynulých 7 dnů', + 'last_thirty_days' => 'Uplynulých 30 dní', + 'welcomeBack' => 'Co hraje?', + 'welcome_back' => 'Co hraje?', + 'everything' => 'Vše', + 'today' => 'dnes', + 'customRange' => 'Vlastní rozsah', + 'apply' => 'Použít', + 'select_date' => 'Vyberte datum…', + 'cancel' => 'Storno', + 'from' => 'Od', + 'to' => 'Komu', + 'structure' => 'Struktura', + 'help_translating' => 'Text této nápovědy ještě není k dispozici ve vašem jazyce. Pomůžete s překladem?', + 'showEverything' => 'Zobrazit vše', + 'never' => 'Nikdy', + 'no_results_for_empty_search' => 'Nezadali jste žádné parametry vyhledávání, takže nebylo co hledat.', + 'removed_amount' => 'Odebráno :amount', + 'added_amount' => 'Přidáno :amount', + 'asset_account_role_help' => 'Jakékoliv další možnosti, vyplývající z vaší volby lze nastavit později.', + 'Opening balance' => 'Počáteční zůstatek', + 'create_new_stuff' => 'Vytvořit novou věc', + 'new_withdrawal' => 'Nový výběr', + 'create_new_transaction' => 'Přidat novou transakci', + 'new_transaction' => 'Nová transakce', + 'go_to_asset_accounts' => 'Zobrazit účty s aktivy', + 'go_to_budgets' => 'Přejít k rozpočtům', + 'go_to_categories' => 'Přejít ke kategoriím', + 'go_to_bills' => 'Přejít k účtům', + 'go_to_expense_accounts' => 'Zobrazit výdajové účty', + 'go_to_revenue_accounts' => 'Zobrazit výnosové účty', + 'go_to_piggies' => 'Přejít k pokladničkám', + 'new_deposit' => 'Nový vklad', + 'new_transfer' => 'Nový převod', + 'new_transfers' => 'Nový převod', + 'new_asset_account' => 'Nový účet s aktivy', + 'new_expense_account' => 'Nový výdajový účet', + 'new_revenue_account' => 'Nový výnosový účet', + 'new_liabilities_account' => 'Nový závazek', + 'new_budget' => 'Nový rozpočet', + 'new_bill' => 'Nová platba', + 'block_account_logout' => 'Byli jste odhlášeni. Zablokované účty nemohou tuto stránku používat. Zaregistrovali jste se pomocí platné e-mailové adresy?', + 'flash_success' => 'Úspěšně dokončeno!', + 'flash_info' => 'Zpráva', + 'flash_warning' => 'Varování!', + 'flash_error' => 'Chyba!', + 'flash_info_multiple' => 'Jedna zpráva | :count zpráv', + 'flash_error_multiple' => 'Jedna chyba | :count chyb', + 'net_worth' => 'Čisté jmění', + 'route_has_no_help' => 'Pro tento směr není k dispozici nápověda.', + 'help_for_this_page' => 'Nápověda pro tuto stránku', + 'no_help_could_be_found' => 'Nepodařilo se nalézt žádný nápovědný text.', + 'no_help_title' => 'Omlouváme se, došlo k chybě.', + 'two_factor_welcome' => 'Hello!', + 'two_factor_enter_code' => 'Pro pokračování zadejte kód pro dvoufázové ověření. Vaše aplikace ho pro vás může vytvořit.', + 'two_factor_code_here' => 'Sem zadejte kód', + 'two_factor_title' => 'Dvoufázové ověření', + 'authenticate' => 'Ověřit', + 'two_factor_forgot_title' => 'Ztratil(a) jsem dvoufázové ověřování', + 'two_factor_forgot' => 'Zapomněl(a) jsem si nástroj pro dvoufázové ověření.', + 'two_factor_lost_header' => 'Ztratili jste své dvoufázové ověření?', + 'two_factor_lost_intro' => 'If you lost your backup codes as well, you have bad luck. This is not something you can fix from the web interface. You have two choices.', + 'two_factor_lost_fix_self' => 'If you run your own instance of Firefly III, check the logs in storage/logs for instructions, or run docker logs <container_id> to see the instructions (refresh this page).', + 'two_factor_lost_fix_owner' => 'V ostatních případech napište provozovateli, :site_owner a požádejte ho o resetování svého dvoufázového ověřování.', + 'mfa_backup_code' => 'You have used a backup code to login to Firefly III. It can\'t be used again, so cross it from your list.', + 'pref_two_factor_new_backup_codes' => 'Get new backup codes', + 'pref_two_factor_backup_code_count' => 'You have :count valid backup code(s).', + '2fa_i_have_them' => 'I stored them!', + 'warning_much_data' => ':days dnů dat může chvíli trvat načíst.', + 'registered' => 'Úspěšně jste se zaregistrovali!', + 'Default asset account' => 'Výchozí účet s aktivy', + 'no_budget_pointer' => 'Zdá se, že zatím nemáte žádné rozpočty. Na stránce rozpočty byste nějaké měli vytvořit. Rozpočty mohou pomoci udržet si přehled ve výdajích.', + 'Savings account' => 'Spořicí účet', + 'Credit card' => 'Kreditní karta', + 'source_accounts' => 'Zdrojové účty', + 'destination_accounts' => 'Cílový účet(y)', + 'user_id_is' => 'Vaš identifikátor uživatele je :user', + 'field_supports_markdown' => 'Text v této kolonce je možné formátovat pomocí Markdown.', + 'need_more_help' => 'Pokud potřebujete další pomoc s používáním Firefly III, založte požadavek na portálu GitHub.', + 'reenable_intro_text' => 'Můžete si také znovu spustit úvodního průvodce.', + 'intro_boxes_after_refresh' => 'Oblasti s úvodem se znovu objeví po opětovném načtení stránky.', + 'show_all_no_filter' => 'Při jejich seskupení podle data zobrazit veškeré transakce.', + 'expenses_by_category' => 'Výdaje podle kategorie', + 'expenses_by_budget' => 'Výdaje podle rozpočtu', + 'income_by_category' => 'Příjem podle kategorie', + 'expenses_by_asset_account' => 'Výdaje podle účtů aktiv', + 'expenses_by_expense_account' => 'Výdaje podle výdajových účtů', + 'cannot_redirect_to_account' => 'Firefly III se nedaří přesměrovat vás na správnou stránku. Omlouváme se.', + 'sum_of_expenses' => 'Souhrn výdajů', + 'sum_of_income' => 'Souhrn příjmů', + 'liabilities' => 'Závazky', + 'spent_in_specific_budget' => 'Utraceno v rozpočtu „:budget“', + 'sum_of_expenses_in_budget' => 'Celkem utraceno v rozpočtu „:budget“', + 'left_in_budget_limit' => 'Ponecháno k utracení dle rozpočtu', + 'current_period' => 'Stávající období', + 'show_the_current_period_and_overview' => 'Zobrazit stávající období a přehled', + 'pref_languages_locale' => 'Aby správně fungovalo i pro jiné jazyky, než je angličtina je třeba, aby operační systém byl vybaven správnými údaji o místních a jazykových nastaveních. Pokud nejsou přítomné, data měn, datumů a částek mohou být chybně formátované.', + 'budget_in_period' => 'Veškeré transakce pro rozpočet „:name“ mezi :start a :end', + 'chart_budget_in_period' => 'Graf veškerých transakcí pro rozpočet „:name“ mezi :start a :end', + 'chart_account_in_period' => 'Graf veškerých transakcí pro účet „:name“ mezi :start a :end', + 'chart_category_in_period' => 'Graf veškerých transakcí pro kategorii „:name“ mezi :start a :end', + 'chart_category_all' => 'Graf veškerých transakcí pro kategoii „:name“', + 'clone_withdrawal' => 'Klonovat tento výběr', + 'clone_deposit' => 'Klonovat tento vklad', + 'clone_transfer' => 'Klonovat tento převod', + 'multi_select_no_selection' => 'Nic nevybráno', + 'multi_select_select_all' => 'Vybrat vše', + 'multi_select_n_selected' => 'vybráno', + 'multi_select_all_selected' => 'Všechny vybrané', + 'multi_select_filter_placeholder' => 'Najít…', + 'intro_next_label' => 'Následující', + 'intro_prev_label' => 'Předchozí', + 'intro_skip_label' => 'Přeskočit', + 'intro_done_label' => 'Hotovo', + 'between_dates_breadcrumb' => 'Mezi :start a :end', + 'all_journals_without_budget' => 'Všechny transakce bez rozpočtu', + 'journals_without_budget' => 'Transakce bez rozpočtu', + 'all_journals_without_category' => 'Všechny transakce bez kategorie', + 'journals_without_category' => 'Transakce bez kategorie', + 'all_journals_for_account' => 'Všechny transakce pro účet :name', + 'chart_all_journals_for_account' => 'Graf veškerých transakcí pro účet „:name“', + 'journals_in_period_for_account' => 'Veškeré transakce pro účet „:name“ mezi :start a :end', + 'transferred' => 'Přeneseno', + 'all_withdrawal' => 'Všechny výdaje', + 'all_transactions' => 'Všechny transakce', + 'title_withdrawal_between' => 'Všechny výdaje mezi :start a :end', + 'all_deposit' => 'Veškeré výnosy', + 'title_deposit_between' => 'Všechny výnosy mezi :start a :end', + 'all_transfers' => 'Všechny převody', + 'title_transfers_between' => 'Všechny převody mezi :start a :end', + 'all_transfer' => 'Všechny převody', + 'all_journals_for_tag' => 'Všechny transakce pro značku „:tag“', + 'title_transfer_between' => 'Všechny převody mezi :start a :end', + 'all_journals_for_category' => 'Všechny transakce pro kategorii :name', + 'all_journals_for_budget' => 'Všechny transakce pro rozpočet :name', + 'chart_all_journals_for_budget' => 'Graf veškerých transakcí pro rozpočet „:name“', + 'journals_in_period_for_category' => 'Veškeré transakce pro kategorii „:name“ mezi :start a :end', + 'journals_in_period_for_tag' => 'Veškeré transakce pro štítek :tag mezi :start a :end', + 'not_available_demo_user' => 'Funkce, kterou se snažíte použít není uživatelům ukázky k dispozici.', + 'exchange_rate_instructions' => 'Účet aktiv „@name“ přijímá transakce pouze v @native_currency. Pokud chcete namísto toho použít @foreign_currency ověřte, že je známa také částka v @native_currency:', + 'transfer_exchange_rate_instructions' => 'Source asset account "@source_name" only accepts transactions in @source_currency. Destination asset account "@dest_name" only accepts transactions in @dest_currency. You must provide the transferred amount correctly in both currencies.', + 'transaction_data' => 'Data transakce', + 'invalid_server_configuration' => 'Neplatné nastavení serveru', + 'invalid_locale_settings' => 'Firefly III nemůže formátovat peněžní účty protože na vašem serveru chybí potřebné balíčky. Jsou k dispozici pokyny, jak to napravit.', + 'quickswitch' => 'Rychlé přepnutí', + 'sign_in_to_start' => 'Pro zahájení vaší relace se přihlaste', + 'sign_in' => 'Přihlásit', + 'register_new_account' => 'Zaregistrovat nový účet', + 'forgot_my_password' => 'Zapomněl(a) jsem své heslo', + 'problems_with_input' => 'Vyskytly se problémy se vstupními údaji.', + 'reset_password' => 'Resetovat své heslo', + 'button_reset_password' => 'Resetovat heslo', + 'reset_button' => 'Reset', + 'want_to_login' => 'Chci se přihlásit', + 'login_page_title' => 'Přihlášení do Firefly III', + 'register_page_title' => 'Registrace do Firefly III', + 'forgot_pw_page_title' => 'Zapomenuté heslo do Firefly III', + 'reset_pw_page_title' => 'Reset hesla do Firefly III', + 'cannot_reset_demo_user' => 'Heslo uživatele pro ukázku (demo) nelze resetovat.', + 'button_register' => 'Zaregistrovat se', + 'authorization' => 'Pověření', + 'active_bills_only' => 'pouze aktivní účty', + 'average_per_bill' => 'průměr na účet', + 'expected_total' => 'očekávaný celkový součet', + // API access + 'authorization_request' => 'Požadavek na ověření – Firefly III verze :version', + 'authorization_request_intro' => ':client žádá oprávnění pro přístup k vaší finanční správě. Chcete autorizovat :client pro přístup k těmto záznamům?', + 'scopes_will_be_able' => 'Tato aplikace bude moci:', + 'button_authorize' => 'Autorizovat', + 'none_in_select_list' => '(žádné)', + 'name_in_currency' => ':name v :currency', + 'paid_in_currency' => 'Zaplaceno v :currency', + 'unpaid_in_currency' => 'Nezaplaceno v :currency', + + // check for updates: + 'update_check_title' => 'Zjistit dostupnost případných aktualizací', + 'admin_update_check_title' => 'Zjišťovat dostupnost případných aktualizací automaticky', + 'admin_update_check_explain' => 'Firefly III může automaticky zjišťovat dostupnost případných aktualizací. Pokud toto zapnete, obrátí se na portál Github a zjistí, zda je k dispozici nová verze Firefly III. Pokud ano, budete na to upozorněni. Fungování tohoto oznámení je možné si vyzkoušet pomocí tlačítka vpravo. Označte níže, zda si přejete, aby Firefly III zjišťovalo dostupnost případných aktualizací.', + 'check_for_updates_permission' => 'Firefly III umí zjišťovat dostupnost případných aktualizací, ale potřebuje k tomu vaše svolení. Přejděte do správy a označte, zda chcete tuto funkci zapnout.', + 'updates_ask_me_later' => 'Zeptat se znovu později', + 'updates_do_not_check' => 'Nezjišťovat dostupnost případných aktualizací', + 'updates_enable_check' => 'Zjišťovat dostupnost případných aktualizací', + 'admin_update_check_now_title' => 'Zjistit dostupno případných aktualizací nyní', + 'admin_update_check_now_explain' => 'Stisknutím tohoto tlačítka Firefly III ověří, zda používáte nejnovější verzi.', + 'check_for_updates_button' => 'Zkontrolovat nyní!', + 'update_new_version_alert' => 'Je k dispozici nová verze Firefly III. Nyní provozujete verzi :your_version, nejnovější verze je :new_version, která byla vydaná :date.', + 'update_current_version_alert' => 'Provozujete verzi :version, která je nejnovější dostupnou verzí.', + 'update_newer_version_alert' => 'Provozujete verzi :your_version, zatímco nejnovější vydání je verze :new_version.', + 'update_check_error' => 'Došlo k chybě při zjišťování případných aktualizací. Podívejte se do souborů se záznamem událostí.', + + // search + 'search' => 'Hledat', + 'search_query' => 'Dotaz', + 'search_found_transactions' => 'Firefly III nalezlo :count transakcí za :time sekund.', + 'search_for_query' => 'Firefly III is searching for transactions with all of these words in them: :query', + 'search_modifier_amount_is' => 'Částka je přesně :value', + 'search_modifier_amount' => 'Částka je přesně :value', + 'search_modifier_amount_max' => 'Částka je nanejvýše :value', + 'search_modifier_amount_min' => 'Částka je přinejmenším :value', + 'search_modifier_amount_less' => 'Částka je nižší než :value', + 'search_modifier_amount_more' => 'Částka je vyšší než :value', + 'search_modifier_source' => 'Zdrojový účet je :value', + 'search_modifier_from' => 'Source account is :value', + 'search_modifier_destination' => 'Cílový účet je :value', + 'search_modifier_to' => 'Destination account is :value', + 'search_modifier_category' => 'Kategorie je :value', + 'search_modifier_budget' => 'Rozpočet je :value', + 'search_modifier_bill' => 'Účtenka je :value', + 'search_modifier_type' => 'Typ transakce je :value', + 'search_modifier_date' => 'Datum transakce je :value', + 'search_modifier_date_before' => 'Datum transakce je před :value', + 'search_modifier_date_after' => 'Datum transakce je po :value', + 'search_modifier_on' => 'Datum transakce je :value', + 'search_modifier_before' => 'Datum transakce je před :value', + 'search_modifier_after' => 'Datum transakce je po :value', + 'modifiers_applies_are' => 'The following modifiers are applied to the search as well:', + 'general_search_error' => 'Při hledání došlo k chybě. Podrobnosti naleznete v souborech se záznamem událostí.', + 'search_box' => 'Hledat', + 'search_box_intro' => 'Vítejte ve funkci vyhledávání ve Firefly III. Vyhledávací dotaz zadejte do kolonky. Protože vyhledávání poskytuje opravdu mnoho pokročilých funkcí, bude užitečné přečíst si nápovědu.', + 'search_error' => 'Chyba při hledání', + 'search_searching' => 'Hledání…', + 'search_results' => 'Výsledky hledání', + + // repeat frequencies: + 'repeat_freq_yearly' => 'ročně', + 'repeat_freq_half-year' => 'půloročně', + 'repeat_freq_quarterly' => 'čtvrtletně', + 'repeat_freq_monthly' => 'měsíčně', + 'repeat_freq_weekly' => 'týdně', + 'weekly' => 'týdně', + 'quarterly' => 'čtvrtletně', + 'half-year' => 'půlročně', + 'yearly' => 'ročně', + + // rules + 'rules' => 'Pravidla', + 'rule_name' => 'Název pravidla', + 'rule_triggers' => 'Pravidlo se uplatní když', + 'rule_actions' => 'Pravidlo bude', + 'new_rule' => 'Nové pravidlo', + 'new_rule_group' => 'Nová skupina pravidel', + 'rule_priority_up' => 'Zvýšit prioritu pravidla', + 'rule_priority_down' => 'Snížit prioritu pravidla', + 'make_new_rule_group' => 'Vytvořit novou skupinu pravidel', + 'store_new_rule_group' => 'Uložit novou skupinu pravidel', + 'created_new_rule_group' => 'Nová skupina pravidel „:title“ uložena!', + 'updated_rule_group' => 'Skupina pravidel „:title“ úspěšně aktualizována.', + 'edit_rule_group' => 'Upravit skupinu pravidel „:title“', + 'delete_rule_group' => 'Smazat skupinu pravidel „:title“', + 'deleted_rule_group' => 'Skupina pravidel „:title“ smazána', + 'update_rule_group' => 'Aktualizovat skupinu pravidel', + 'no_rules_in_group' => 'Tato skupina neobsahuje žádná pravidla', + 'move_rule_group_up' => 'Posunout skupinu pravidel nahoru', + 'move_rule_group_down' => 'Posunout skupinu pravidel dolů', + 'save_rules_by_moving' => 'Uložit tato pravidla jejich přesunutím do jiné skupiny pravidel:', + 'make_new_rule' => 'Vytvořit nové pravidlo ve skupině pravidel „:title“', + 'make_new_rule_no_group' => 'Vytvořit nové pravidlo', + 'instructions_rule_from_bill' => 'In order to match transactions to your new bill ":name", Firefly III can create a rule that will automatically be checked against any transactions you store. Please verify the details below and store the rule to have Firefly III automatically match transactions to your new bill.', + 'rule_is_strict' => 'striktní pravidlo', + 'rule_is_not_strict' => 'nestriktní pravidlo', + 'rule_help_stop_processing' => 'Pokud toto zaškrtnete, následná pravidla v této skupině nebudou vykonána.', + 'rule_help_strict' => 'In strict rules ALL triggers must fire for the action(s) to be executed. In non-strict rules, ANY trigger is enough for the action(s) to be executed.', + 'rule_help_active' => 'Neaktivní pravidla nebudou nikdy spuštěna.', + 'stored_new_rule' => 'Uloženo nové pravidlo nazvané „:title“', + 'deleted_rule' => 'Smazáno pravidlo nazvané „:title“', + 'store_new_rule' => 'Uložit nové pravidlo', + 'updated_rule' => 'Pravidlo nazvané „:title“ aktualizováno', + 'default_rule_group_name' => 'Výchozí pravidla', + 'default_rule_group_description' => 'Všechna vaše pravidla nenacházející se v konkrétní skupině.', + 'default_rule_name' => 'Vaše první výchozí pravidlo', + 'default_rule_description' => 'Toto pravidlo je ukázkou. Je možné ho bezpečně smazat.', + 'default_rule_trigger_description' => 'Muž, který prodal svět', + 'default_rule_trigger_from_account' => 'David Bowie', + 'default_rule_action_prepend' => 'Bought the world from ', + 'default_rule_action_set_category' => 'Velké výdaje', + 'trigger' => 'Spouštěč', + 'trigger_value' => 'Spouštěč při hodnotě', + 'stop_processing_other_triggers' => 'Zastavit zpracovávání ostatních spouštěčů', + 'add_rule_trigger' => 'Přidat nový spouštěč', + 'action' => 'Akce', + 'action_value' => 'Hodnota akce', + 'stop_executing_other_actions' => 'Zastavit vykonávání ostatních akcí', + 'add_rule_action' => 'Přidat novou akci', + 'edit_rule' => 'Upravit pravidlo „:title“', + 'delete_rule' => 'Smazat pravidlo „:title“', + 'update_rule' => 'Aktualizovat pravidlo', + 'test_rule_triggers' => 'Zobrazit odpovídající transakce', + 'warning_transaction_subset' => 'Z výkonnostních důvodů je tento seznam omezený na nejvýše :max_num_transactions položek a může se tak stát, že nebudou zobrazeny úplně všechny odpovídající transakce', + 'warning_no_matching_transactions' => 'Nebyly nalezeny žádné odpovídající transakce. Mějte ovšem na paměti, že z výkonnostních důvodů, je kontrolováno pouze posledních :num_transactions transakcí.', + 'warning_no_valid_triggers' => 'Nebyly poskytnuty platné spouštěče.', + 'apply_rule_selection' => 'Uplatnit pravidlo „:title“ na vybrané transakce', + 'apply_rule_selection_intro' => 'Rules like ":title" are normally only applied to new or updated transactions, but you can tell Firefly III to run it on a selection of your existing transactions. This can be useful when you have updated a rule and you need the changes to be applied to all of your other transactions.', + 'include_transactions_from_accounts' => 'Zahrnout transakce z těchto účtů', + 'applied_rule_selection' => 'Pravidlo „:title“ bylo uplatněno na váš výběr.', + 'execute' => 'Vykonat', + 'apply_rule_group_selection' => 'Uplatnit skupinu pravidel „:title“ na vybrané transakce', + 'apply_rule_group_selection_intro' => 'Rule groups like ":title" are normally only applied to new or updated transactions, but you can tell Firefly III to run all the rules in this group on a selection of your existing transactions. This can be useful when you have updated a group of rules and you need the changes to be applied to all of your other transactions.', + 'applied_rule_group_selection' => 'Skupina pravidel „:title“ byla uplatněna na váš výběr.', + + // actions and triggers + 'rule_trigger_user_action' => 'Uživatelská akce je „:trigger_value“', + 'rule_trigger_from_account_starts_choice' => 'Zdrojový účet začíná na…', + 'rule_trigger_from_account_starts' => 'Zdrojový účet začíná na „:trigger_value“', + 'rule_trigger_from_account_ends_choice' => 'Zdrojový účet končí na…', + 'rule_trigger_from_account_ends' => 'Zdrojový účet končí na „:trigger_value“', + 'rule_trigger_from_account_is_choice' => 'Zdrojový účet je…', + 'rule_trigger_from_account_is' => 'Zdrojový účet je „:trigger_value“', + 'rule_trigger_from_account_contains_choice' => 'Zdrojový účet obsahuje…', + 'rule_trigger_from_account_contains' => 'Zdrojový účet obsahuje „:trigger_value“', + 'rule_trigger_to_account_starts_choice' => 'Cílový účet začíná na…', + 'rule_trigger_to_account_starts' => 'Cílový účet začíná na „:trigger_value“', + 'rule_trigger_to_account_ends_choice' => 'Cílový účet končí na…', + 'rule_trigger_to_account_ends' => 'Cílový účet končí na „:trigger_value“', + 'rule_trigger_to_account_is_choice' => 'Cílový účet je…', + 'rule_trigger_to_account_is' => 'Cílový účet je „:trigger_value“', + 'rule_trigger_to_account_contains_choice' => 'Cílový účet obsahuje…', + 'rule_trigger_to_account_contains' => 'Cílový účet obsahuje „:trigger_value“', + 'rule_trigger_transaction_type_choice' => 'Transakce je typu…', + 'rule_trigger_transaction_type' => 'Transakce je typu „:trigger_value“', + 'rule_trigger_category_is_choice' => 'Kategorie je…', + 'rule_trigger_category_is' => 'Kategorie je „:trigger_value“', + 'rule_trigger_amount_less_choice' => 'Částka je nižší než…', + 'rule_trigger_amount_less' => 'Částka je nižší než :trigger_value', + 'rule_trigger_amount_exactly_choice' => 'Částka je…', + 'rule_trigger_amount_exactly' => 'Částka je :trigger_value', + 'rule_trigger_amount_more_choice' => 'Částka je vyšší než…', + 'rule_trigger_amount_more' => 'Částka je vyšší než :trigger_value', + 'rule_trigger_description_starts_choice' => 'Popis začíná na…', + 'rule_trigger_description_starts' => 'Popis začíná na „:trigger_value“', + 'rule_trigger_description_ends_choice' => 'Popis končí na…', + 'rule_trigger_description_ends' => 'Popis končí na „:trigger_value“', + 'rule_trigger_description_contains_choice' => 'Popis obsahuje…', + 'rule_trigger_description_contains' => 'Popis obsahuje „:trigger_value“', + 'rule_trigger_description_is_choice' => 'Popis je…', + 'rule_trigger_description_is' => 'Popis je „:trigger_value“', + 'rule_trigger_budget_is_choice' => 'Rozpočet je…', + 'rule_trigger_budget_is' => 'Rozpočet je „:trigger_value“', + 'rule_trigger_tag_is_choice' => 'Štítek je…', + 'rule_trigger_tag_is' => 'Štítek je „:trigger_value“', + 'rule_trigger_currency_is_choice' => 'Měna transakce je…', + 'rule_trigger_currency_is' => 'Měna transakce je „:trigger_value“', + 'rule_trigger_has_attachments_choice' => 'Má alespoň tolik příloh', + 'rule_trigger_has_attachments' => 'Má přinejmenším :trigger_value příloh', + 'rule_trigger_store_journal' => 'Kdy je transakce vytvořena', + 'rule_trigger_update_journal' => 'Kdy je transakce aktualizována', + 'rule_trigger_has_no_category_choice' => 'Nemá žádnou kategorii', + 'rule_trigger_has_no_category' => 'Transakce nemá žádnou kategorii', + 'rule_trigger_has_any_category_choice' => 'Má (libovolnou) kategorii', + 'rule_trigger_has_any_category' => 'Transakce má (libovolnou) kategorii', + 'rule_trigger_has_no_budget_choice' => 'Nemá žádný rozpočet', + 'rule_trigger_has_no_budget' => 'Transakce nemá žádný rozpočet', + 'rule_trigger_has_any_budget_choice' => 'Má (libovolný) rozpočet', + 'rule_trigger_has_any_budget' => 'Transakce má (libovolný) rozpočet', + 'rule_trigger_has_no_tag_choice' => 'Nemá žádné štítky', + 'rule_trigger_has_no_tag' => 'Transakce nemá žádné štítky', + 'rule_trigger_has_any_tag_choice' => 'Má jeden a více štítků', + 'rule_trigger_has_any_tag' => 'Transakce má jeden a více štítků', + 'rule_trigger_any_notes_choice' => 'Má (jakékoli) poznámky', + 'rule_trigger_any_notes' => 'Transakce má (jakékoli) poznámky', + 'rule_trigger_no_notes_choice' => 'Nemá žádné poznámky', + 'rule_trigger_no_notes' => 'Transakce nemá žádné poznámky', + 'rule_trigger_notes_are_choice' => 'Poznámky jsou…', + 'rule_trigger_notes_are' => 'Poznámky jsou „:trigger_value“', + 'rule_trigger_notes_contain_choice' => 'Poznámky obsahují…', + 'rule_trigger_notes_contain' => 'Poznámky obsahují „:trigger_value“', + 'rule_trigger_notes_start_choice' => 'Poznámky začínají na…', + 'rule_trigger_notes_start' => 'Poznámky začínají na „:trigger_value“', + 'rule_trigger_notes_end_choice' => 'Poznámky končí na…', + 'rule_trigger_notes_end' => 'Poznámky končí na „:trigger_value“', + 'rule_action_set_category' => 'Nastavit kategorii na „:action_value“', + 'rule_action_clear_category' => 'Vyčistit kategorii', + 'rule_action_set_budget' => 'Nastavit rozpočet na „:action_value“', + 'rule_action_clear_budget' => 'Vyčistit rozpočet', + 'rule_action_add_tag' => 'Přidat štítek „:action_value“', + 'rule_action_remove_tag' => 'Odebrat štítek „:action_value“', + 'rule_action_remove_all_tags' => 'Odstranit veškeré štítky', + 'rule_action_set_description' => 'Nastavit pospis na „:action_value“', + 'rule_action_append_description' => 'Připojit popis s „:action_value“', + 'rule_action_prepend_description' => 'Před popis přidat „:action_value“', + 'rule_action_set_category_choice' => 'Nastavit kategorii na…', + 'rule_action_clear_category_choice' => 'Vyčistit jakékoli kategorie', + 'rule_action_set_budget_choice' => 'Nastavit rozpočet na…', + 'rule_action_clear_budget_choice' => 'Vyčistit jakýkoli rozpočet', + 'rule_action_add_tag_choice' => 'Přidat štítek…', + 'rule_action_remove_tag_choice' => 'Odebrat štítek…', + 'rule_action_remove_all_tags_choice' => 'Odebrat veškeré štítky', + 'rule_action_set_description_choice' => 'Nastavit popis na…', + 'rule_action_append_description_choice' => 'Připojit k popisu…', + 'rule_action_prepend_description_choice' => 'Přidat před popis…', + 'rule_action_set_source_account_choice' => 'Nastavit zdrojový účet na…', + 'rule_action_set_source_account' => 'Nastavit účet na :action_value', + 'rule_action_set_destination_account_choice' => 'Nastavit cílový účet na…', + 'rule_action_set_destination_account' => 'Nastavit cílový účet na :action_value', + 'rule_action_append_notes_choice' => 'Připojit za poznámky…', + 'rule_action_append_notes' => 'Přidat za poznámky „:action_value“', + 'rule_action_prepend_notes_choice' => 'Přidat před poznámky…', + 'rule_action_prepend_notes' => 'Přidat před poznámky „:action_value“', + 'rule_action_clear_notes_choice' => 'Odstranit všechny poznámky', + 'rule_action_clear_notes' => 'Odstranit všechny poznámky', + 'rule_action_set_notes_choice' => 'Nastavit poznámky na…', + 'rule_action_link_to_bill_choice' => 'Propojit s účtem…', + 'rule_action_link_to_bill' => 'Link to bill ":action_value"', + 'rule_action_set_notes' => 'Nastavit poznámky na „:action_value“', + 'rule_action_convert_deposit_choice' => 'Přeměnit tuto transakci na vklad', + 'rule_action_convert_deposit' => 'Přeměnit tuto transakci z vkladu na „:action_value“', + 'rule_action_convert_withdrawal_choice' => 'Přeměnit transakci na výběr', + 'rule_action_convert_withdrawal' => 'Přeměnit tuto transakci na vklad do „:action_value“', + 'rule_action_convert_transfer_choice' => 'Přeměnit tuto transakci na převod', + 'rule_action_convert_transfer' => 'Přeměnit tuto transakci na převod s „:action_value“', + + 'rules_have_read_warning' => 'Přečetli jste si varování?', + 'apply_rule_warning' => 'Warning: running a rule(group) on a large selection of transactions could take ages, and it could time-out. If it does, the rule(group) will only be applied to an unknown subset of your transactions. This might leave your financial administration in tatters. Please be careful.', + 'rulegroup_for_bills_title' => 'Rule group for bills', + 'rulegroup_for_bills_description' => 'A special rule group for all the rules that involve bills.', + 'rule_for_bill_title' => 'Automaticky vytvořené pravidlo pro účtenku „:name“', + 'rule_for_bill_description' => 'This rule is auto-generated to try to match bill ":name".', + 'create_rule_for_bill' => 'Create a new rule for bill ":name"', + 'create_rule_for_bill_txt' => 'You have just created a new bill called ":name", congratulations! Firefly III can automagically match new withdrawals to this bill. For example, whenever you pay your rent, the bill "rent" will be linked to the expense. This way, Firefly III can accurately show you which bills are due and which ones aren\'t. In order to do so, a new rule must be created. Firefly III has filled in some sensible defaults for you. Please make sure these are correct. If these values are correct, Firefly III will automatically link the correct withdrawal to the correct bill. Please check out the triggers to see if they are correct, and add some if they\'re wrong.', + 'new_rule_for_bill_title' => 'Rule for bill ":name"', + 'new_rule_for_bill_description' => 'This rule marks transactions for bill ":name".', + + // tags + 'store_new_tag' => 'Uložit nový štítek', + 'update_tag' => 'Aktualizovat štítek', + 'no_location_set' => 'Není nastaveno žádné umístění.', + 'meta_data' => 'Metadata', + 'location' => 'Umístění', + 'without_date' => 'Bez data', + 'result' => 'Výsledek', + 'sums_apply_to_range' => 'Všechny součty se vztahují na vybraný rozsah', + 'mapbox_api_key' => 'Pro použití mapy, získejte klíč k aplikačnímu programovému rozhraní Mapbox. Otevřete soubor .env a tento kód zadejte za MAPBOX_API_KEY=.', + 'press_tag_location' => 'Umístění značky nastavíte kliknutím pravým tlačítkem nebo dlouhým stiskem toho levého.', + 'clear_location' => 'Vymazat umístění', + + // preferences + 'pref_home_screen_accounts' => 'Účty na domovské obrazovce', + 'pref_home_screen_accounts_help' => 'Které účty zobrazit na domovské stránce?', + 'pref_view_range' => 'Zobrazit rozsah', + 'pref_view_range_help' => 'Some charts are automatically grouped in periods. Your budgets will also be grouped in periods. What period would you prefer?', + 'pref_1D' => 'Jeden den', + 'pref_1W' => 'Jeden týden', + 'pref_1M' => 'Jeden měsíc', + 'pref_3M' => 'Tři měsíce (čtvrtletí)', + 'pref_6M' => 'Šest měsíců', + 'pref_1Y' => 'Jeden rok', + 'pref_languages' => 'Jazyky', + 'pref_languages_help' => 'Firefly III podporuje několik jazyků – ve kterém ho chcete používat?', + 'pref_custom_fiscal_year' => 'Nastavení fiskálního roku', + 'pref_custom_fiscal_year_label' => 'Zapnuto', + 'pref_custom_fiscal_year_help' => 'Pro země, ve kterých finanční rok nezačíná 1. ledna a tedy ani nekončí 31. prosince, je možné zapnutím tohoto určit den začátku a konce fiskálního roku', + 'pref_fiscal_year_start_label' => 'Fiscal year start date', + 'pref_two_factor_auth' => 'Dvoufázové ověření', + 'pref_two_factor_auth_help' => 'When you enable 2-step verification (also known as two-factor authentication), you add an extra layer of security to your account. You sign in with something you know (your password) and something you have (a verification code). Verification codes are generated by an application on your phone, such as Authy or Google Authenticator.', + 'pref_enable_two_factor_auth' => 'Zapnout dvoufázové ověření', + 'pref_two_factor_auth_disabled' => '2-step verification code removed and disabled', + 'pref_two_factor_auth_remove_it' => 'Nezapomeňte účet odstranit z vaší ověřovací aplikace!', + 'pref_two_factor_auth_code' => 'Ověřit kód', + 'pref_two_factor_auth_code_help' => 'Naskenujte QR kód aplikací na vašem telefonu (jako například Authy nebo Google Authenticator) a zadejte aplikací vytvořený kód.', + 'pref_two_factor_auth_reset_code' => 'Resetovat ověřovací kód', + 'pref_two_factor_auth_disable_2fa' => 'Vypnout dvoufázové ověření', + '2fa_use_secret_instead' => 'If you cannot scan the QR code, feel free to use the secret instead: :secret.', + '2fa_backup_codes' => 'Store these backup codes for access in case you lose your device.', + '2fa_already_enabled' => '2-step verification is already enabled.', + 'wrong_mfa_code' => 'This MFA code is not valid.', + 'pref_save_settings' => 'Uložit nastavení', + 'saved_preferences' => 'Předvolby uloženy!', + 'preferences_general' => 'Obecné', + 'preferences_frontpage' => 'Domovská obrazovka', + 'preferences_security' => 'Zabezpečení', + 'preferences_layout' => 'Rozvržení', + 'pref_home_show_deposits' => 'Zobrazovat vklady na domovské obrazovce', + 'pref_home_show_deposits_info' => 'Domovská obrazovka už zobrazuje vaše výdajové účty. Mají být zobrazovány také ty příjmové?', + 'pref_home_do_show_deposits' => 'Ano, zobrazit je', + 'successful_count' => 'z toho :count úspěšné', + 'list_page_size_title' => 'Velikost stránky', + 'list_page_size_help' => 'Jakýkoli seznam věcí (účty, transakce, atd) zobrazuje nejvýše tolik na stránku.', + 'list_page_size_label' => 'Velikost stránky', + 'between_dates' => '(:start a :end)', + 'pref_optional_fields_transaction' => 'Volitelné kolonky pro transakce', + 'pref_optional_fields_transaction_help' => 'By default not all fields are enabled when creating a new transaction (because of the clutter). Below, you can enable these fields if you think they could be useful for you. Of course, any field that is disabled, but already filled in, will be visible regardless of the setting.', + 'optional_tj_date_fields' => 'Kolonky pro datum', + 'optional_tj_business_fields' => 'Business fields', + 'optional_tj_attachment_fields' => 'Kolonky příloh', + 'pref_optional_tj_interest_date' => 'Úrokové datum', + 'pref_optional_tj_book_date' => 'Book date', + 'pref_optional_tj_process_date' => 'Datum zpracování', + 'pref_optional_tj_due_date' => 'Datum splatnosti', + 'pref_optional_tj_payment_date' => 'Datum zaplacení', + 'pref_optional_tj_invoice_date' => 'Datum vystavení', + 'pref_optional_tj_internal_reference' => 'Interní reference', + 'pref_optional_tj_notes' => 'Poznámky', + 'pref_optional_tj_attachments' => 'Přílohy', + 'optional_field_meta_dates' => 'Datumy', + 'optional_field_meta_business' => 'Business', + 'optional_field_attachments' => 'Přílohy', + 'optional_field_meta_data' => 'Volitelná metadata', + + // profile: + 'change_your_password' => 'Změnit své heslo', + 'delete_account' => 'Smazat účet', + 'current_password' => 'Stávající heslo', + 'new_password' => 'Nové heslo', + 'new_password_again' => 'Nové heslo (zopakování)', + 'delete_your_account' => 'Smazat svůj účet', + 'delete_your_account_help' => 'Smazání vašeho účtu smaže také všechny účty, transakce, vše, co jste ve Firefly III uložili. Bude to PRYČ.', + 'delete_your_account_password' => 'Pokračujte zadáním svého hesla.', + 'password' => 'Heslo', + 'are_you_sure' => 'Opravdu provést? Není možné vzít zpět.', + 'delete_account_button' => 'SMAZAT svůj účet', + 'invalid_current_password' => 'Neplatné stávající heslo!', + 'password_changed' => 'Heslo změněno.', + 'should_change' => 'Myšlenka je změnit si heslo.', + 'invalid_password' => 'Neplatné heslo.', + 'what_is_pw_security' => 'Co je „ověřit odolnost hesla“.', + 'secure_pw_title' => 'Jak zvolit odolné heslo', + 'secure_pw_history' => 'Nemine týden, abychom neviděli zprávy o stránkách, ze kterých unikly hesla jejich uživatelů. Hackeři a zloději tato hesla používají pro pokus o ukradení vašich soukromých informací. Informace jsou cenné.', + 'secure_pw_ff' => 'Používáte stejná hesla napříč webem? Pokud jedna ze stránek ztratí vaše heslo, hackeři mají přístup ke všem vašim datům. Firefly III spoléhá na to, že zvolíte silné a jinde nepoužívané heslo pro ochranu vašich finančních záznamů.', + 'secure_pw_check_box' => 'Jako pomoc Firefly III může zkontrolovat heslo které chcete použít, zda v minulosti nebylo ukradeno (včetně jeho otisku). V takovém případě aplikace NEdoporučí používat takové heslo.', + 'secure_pw_working_title' => 'Jak to funguje?', + 'secure_pw_working' => 'By checking the box, Firefly III will send the first five characters of the SHA1 hash of your password to the website of Troy Hunt to see if it is on the list. This will stop you from using unsafe passwords as is recommended in the latest NIST Special Publication on this subject.', + 'secure_pw_should' => 'Mám tuto kolonku zaškrtnout?', + 'secure_pw_long_password' => 'Ano, vždy ověřit že je heslo bezpečné.', + 'command_line_token' => 'Token pro příkazový řádek', + 'explain_command_line_token' => 'You need this token to perform command line options, such as importing or exporting data. Without it, such sensitive commands will not work. Do not share your command line token. Nobody will ask you for this token, not even me. If you fear you lost this, or when you\'re paranoid, regenerate this token using the button.', + 'regenerate_command_line_token' => 'Regenerate command line token', + 'token_regenerated' => 'Byl vytvořen nový token pro příkazový řádek', + 'change_your_email' => 'Změna e-mailové adresy', + 'email_verification' => 'An email message will be sent to your old AND new email address. For security purposes, you will not be able to login until you verify your new email address. If you are unsure if your Firefly III installation is capable of sending email, please do not use this feature. If you are an administrator, you can test this in the Administration.', + 'email_changed_logout' => 'Dokud neověříte svou emailovou adresu, nemůžete se přihlásit.', + 'login_with_new_email' => 'Nyní se můžete přihlásit pomocí nové e-mailové adresy.', + 'login_with_old_email' => 'Nyní se můžete přihlásit pomocí původní e-mailové adresy.', + 'login_provider_local_only' => 'This action is not available when authenticating through ":login_provider".', + 'delete_local_info_only' => 'Because you authenticate through ":login_provider", this will only delete local Firefly III information.', + + // attachments + 'nr_of_attachments' => 'Jedna příloha|:count příloh', + 'attachments' => 'Přílohy', + 'edit_attachment' => 'Upravit přílohu ":name"', + 'update_attachment' => 'Aktualizovat přílohu', + 'delete_attachment' => 'Smazat přílohu „:name“', + 'attachment_deleted' => 'Příloha „:name“ smazána', + 'liabilities_deleted' => 'Deleted liability ":name"', + 'attachment_updated' => 'Příloha „:name“ smazána', + 'upload_max_file_size' => 'Nejvyšší umožněná velikost souboru: :size', + 'list_all_attachments' => 'Seznam všech příloh', + + // transaction index + 'title_expenses' => 'Výdaje', + 'title_withdrawal' => 'Výdaje', + 'title_revenue' => 'Odměna/příjem', + 'title_deposit' => 'Odměna/příjem', + 'title_transfer' => 'Převody', + 'title_transfers' => 'Převody', + + // convert stuff: + 'convert_is_already_type_Withdrawal' => 'Tato transakce už je výběrem', + 'convert_is_already_type_Deposit' => 'Tato transakce už je vkladem', + 'convert_is_already_type_Transfer' => 'Tato transakce už je převodem', + 'convert_to_Withdrawal' => 'Přeměnit „:description“ na výběr', + 'convert_to_Deposit' => 'Přeměnit „:description“ na vklad', + 'convert_to_Transfer' => 'Přeměnit „:description“ na přenos', + 'convert_options_WithdrawalDeposit' => 'Přeměnit výběr na vklad', + 'convert_options_WithdrawalTransfer' => 'Přeměnit výběr na převod', + 'convert_options_DepositTransfer' => 'Přeměnit výběr na přenos', + 'convert_options_DepositWithdrawal' => 'Přeměnit vklad na výběr', + 'convert_options_TransferWithdrawal' => 'Přeměnit převod na výběr', + 'convert_options_TransferDeposit' => 'Přeměnit převod na vklad', + 'convert_Withdrawal_to_deposit' => 'Přeměnit tento výběr na vklad', + 'convert_Withdrawal_to_transfer' => 'Přeměnit tento výběr na převod', + 'convert_Deposit_to_withdrawal' => 'Přeměnit tento vklad na výběr', + 'convert_Deposit_to_transfer' => 'Přeměnit tento výběr na přenos', + 'convert_Transfer_to_deposit' => 'Přeměnit tento převod na vklad', + 'convert_Transfer_to_withdrawal' => 'Přeměnit tento převod výběr', + 'convert_please_set_revenue_source' => 'Please pick the revenue account where the money will come from.', + 'convert_please_set_asset_destination' => 'Vyberte účet aktiv, na který peníze půjdou.', + 'convert_please_set_expense_destination' => 'Vyberte výdajový účet, na který peníze půjdou.', + 'convert_please_set_asset_source' => 'Vyberte účet aktiv, ze kterého peníze půjdou.', + 'convert_explanation_withdrawal_deposit' => 'Pokud tento výběr přeměníte na vklad, :amount bude na :sourceName vloženo, namísto vybráno.', + 'convert_explanation_withdrawal_transfer' => 'If you convert this withdrawal into a transfer, :amount will be transferred from :sourceName to a new asset account, instead of being paid to :destinationName.', + 'convert_explanation_deposit_withdrawal' => 'If you convert this deposit into a withdrawal, :amount will be removed from :destinationName instead of added to it.', + 'convert_explanation_deposit_transfer' => 'If you convert this deposit into a transfer, :amount will be transferred from an asset account of your choice into :destinationName.', + 'convert_explanation_transfer_withdrawal' => 'If you convert this transfer into a withdrawal, :amount will go from :sourceName to a new destination as an expense, instead of to :destinationName as a transfer.', + 'convert_explanation_transfer_deposit' => 'If you convert this transfer into a deposit, :amount will be deposited into account :destinationName instead of being transferred there.', + 'converted_to_Withdrawal' => 'The transaction has been converted to a withdrawal', + 'converted_to_Deposit' => 'The transaction has been converted to a deposit', + 'converted_to_Transfer' => 'The transaction has been converted to a transfer', + 'invalid_convert_selection' => 'The account you have selected is already used in this transaction or does not exist.', + 'source_or_dest_invalid' => 'Cannot find the correct transaction details. Conversion is not possible.', + 'convert_to_withdrawal' => 'Přeměnit na výběr', + 'convert_to_deposit' => 'Přeměnit na vklad', + 'convert_to_transfer' => 'Přeměnit na převod', + + // create new stuff: + 'create_new_withdrawal' => 'Vytvořit nový výběr', + 'create_new_deposit' => 'Vytvořit nový vklad', + 'create_new_transfer' => 'Vytvořit nový převod', + 'create_new_asset' => 'Vytvořit nový účet aktiv', + 'create_new_expense' => 'Vytvořit výdajový účet', + 'create_new_revenue' => 'Vytvořit nový příjmový účet', + 'create_new_piggy_bank' => 'Vytvořit novou pokladničku', + 'create_new_bill' => 'Vytvořit novou fakturu', + + // currencies: + 'create_currency' => 'Vytvořit novou měnu', + 'store_currency' => 'Uložit novou měnu', + 'update_currency' => 'Aktualizovat měnu', + 'new_default_currency' => ':name je nyní výchozí měna.', + 'cannot_delete_currency' => ':name nelze odstranit, protože je v aplikaci stále pro něco používáno.', + 'cannot_disable_currency' => 'Cannot disable :name because it is still in use.', + 'deleted_currency' => 'Měna :name smazána', + 'created_currency' => 'Měna :name vytvořena', + 'could_not_store_currency' => 'Novou měnu se nepodařilo uložit.', + 'updated_currency' => 'Měna :name aktualizována', + 'ask_site_owner' => 'Please ask :owner to add, remove or edit currencies.', + 'currencies_intro' => 'Firefly III podporuje různé měny, které můžete nastavit a zpřístupnit zde.', + 'make_default_currency' => 'Nastavit jako výchozí', + 'default_currency' => 'výchozí', + 'currency_is_disabled' => 'Vypnuto', + 'enable_currency' => 'Zapnout', + 'disable_currency' => 'Vypnout', + 'currencies_default_disabled' => 'Většina těchto měn je ve výchozím stavu vypnutá. Pro jejich použití, je třeba je nejdříve zapnout.', + 'currency_is_now_enabled' => 'Měna „:name“ byla zapnuta', + 'currency_is_now_disabled' => 'Měna „:name“ byla vypnuta', + + // forms: + 'mandatoryFields' => 'Povinné kolonky', + 'optionalFields' => 'Volitelné kolonky', + 'options' => 'Možnosti', + + // budgets: + 'create_new_budget' => 'Vytvořit nový rozpočet', + 'store_new_budget' => 'Uložit nový rozpočet', + 'stored_new_budget' => 'Uložen novým rozpočet „:name“', + 'available_between' => 'Dostupné mezi :start a :end', + 'transactionsWithoutBudget' => 'Výdaje bez rozpočtu', + 'transactions_no_budget' => 'Výdaje bez rozpočtu mezi :start a :end', + 'spent_between' => 'Utraceno mezi :start a :end', + 'createBudget' => 'Nový rozpočet', + 'inactiveBudgets' => 'Neaktivní rozpočty', + 'without_budget_between' => 'Transakce bez rozpočtu mezi :start a :end', + 'delete_budget' => 'Smazat rozpočet „:name“', + 'deleted_budget' => 'Smazán rozpočet „:name“', + 'edit_budget' => 'Upravit rozpočet „:name“', + 'updated_budget' => 'Aktualizován rozpočet „:name“', + 'update_amount' => 'Aktualizovat částku', + 'update_budget' => 'Aktualizovat rozpočet', + 'update_budget_amount_range' => 'Update (expected) available amount between :start and :end', + 'budget_period_navigator' => 'Period navigator', + 'info_on_available_amount' => 'Co mám(e) k dispozici?', + 'available_amount_indication' => 'Use these amounts to get an indication of what your total budget could be.', + 'suggested' => 'Navrhované', + 'average_between' => 'Průměr mezi :start a :end', + 'over_budget_warn' => ' Usually you budget about :amount per day. This time it\'s :over_amount per day. Are you sure?', + 'transferred_in' => 'Transferred (in)', + 'transferred_away' => 'Transferred (away)', + + // bills: + 'match_between_amounts' => 'Bill matches transactions between :low and :high.', + 'bill_related_rules' => 'Pravidla vztahující se k této účtence/faktuře', + 'repeats' => 'Opakuje se', + 'connected_journals' => 'Propojené transakce', + 'auto_match_on' => 'Automaticky spárováno Firefly III', + 'auto_match_off' => 'Not automatically matched by Firefly III', + 'next_expected_match' => 'Příští očekávaná shoda', + 'delete_bill' => 'Smazat účtenku „:name“', + 'deleted_bill' => 'Deleted bill ":name"', + 'edit_bill' => 'Upravit účtenku „:name“', + 'more' => 'Více', + 'rescan_old' => 'Spustit pravidla znovu, na všechny transakce', + 'update_bill' => 'Aktualizovat účtenku', + 'updated_bill' => 'Aktualizovat účtenku „:name“', + 'store_new_bill' => 'Uložit novou účtenku', + 'stored_new_bill' => 'Stored new bill ":name"', + 'cannot_scan_inactive_bill' => 'Inactive bills cannot be scanned.', + 'rescanned_bill' => 'Rescanned everything, and linked :total transaction(s) to the bill.', + 'average_bill_amount_year' => 'Average bill amount (:year)', + 'average_bill_amount_overall' => 'Average bill amount (overall)', + 'bill_is_active' => 'Bill is active', + 'bill_expected_between' => 'Očekáváno mezi :start a :end', + 'bill_will_automatch' => 'Účtenka bude automaticky propojena s odpovídajícími transakcemi', + 'skips_over' => 'přeskočí přes', + 'bill_store_error' => 'Při ukládání nové účtenky došlo k neočekávané chybě. Podívejte se do souborů se záznamem událostí', + 'list_inactive_rule' => 'neaktivní pravidlo', + + // accounts: + 'account_missing_transaction' => 'Account #:id (":name") cannot be viewed directly, but Firefly is missing redirect information.', + 'details_for_asset' => 'Podrobnosti o účtu aktiv „:name“', + 'details_for_expense' => 'Podrobnosti pro výdajový účet „:name“', + 'details_for_revenue' => 'Details for revenue account ":name"', + 'details_for_cash' => 'Podrobnosti o hotovostním účtu „:name“', + 'store_new_asset_account' => 'Uložit nový účet aktiv', + 'store_new_expense_account' => 'Uložit nový výdajový účet', + 'store_new_revenue_account' => 'Uložit nový příjmový účet', + 'edit_asset_account' => 'Upravit účet aktiv „:name“', + 'edit_expense_account' => 'Upravit výdajový účet „:name“', + 'edit_revenue_account' => 'Upravit účet odměn „:name“', + 'delete_asset_account' => 'Smazat účet aktiv „:name“', + 'delete_expense_account' => 'Delete expense account ":name"', + 'delete_revenue_account' => 'Delete revenue account ":name"', + 'delete_liabilities_account' => 'Smazat závazek „:name“', + 'asset_deleted' => 'Successfully deleted asset account ":name"', + 'expense_deleted' => 'Successfully deleted expense account ":name"', + 'revenue_deleted' => 'Successfully deleted revenue account ":name"', + 'update_asset_account' => 'Aktualizovat výdajový účet', + 'update_liabilities_account' => 'Aktualizovat závazek', + 'update_expense_account' => 'Aktualizovat výdajový účet', + 'update_revenue_account' => 'Aktualizovat příjmový účet', + 'make_new_asset_account' => 'Vytvořit nový účet aktiv', + 'make_new_expense_account' => 'Vytvořit výdajový účet', + 'make_new_revenue_account' => 'Vytvořit nový příjmový účet', + 'make_new_liabilities_account' => 'Vytvořit nový závazek', + 'asset_accounts' => 'Účty aktiv', + 'expense_accounts' => 'Výdajové účty', + 'revenue_accounts' => 'Příjmové účty', + 'cash_accounts' => 'Hotovostní účty', + 'Cash account' => 'Hotovostní účet', + 'liabilities_accounts' => 'Závazky', + 'reconcile_account' => 'Vyúčtovat účet ":account"', + 'overview_of_reconcile_modal' => 'Overview of reconciliation', + 'delete_reconciliation' => 'Odstranit vyúčtování', + 'update_reconciliation' => 'Upravit vyúčtování', + 'amount_cannot_be_zero' => 'Částka nemůže být nula', + 'end_of_reconcile_period' => 'Konec vyúčtovacího období: :period', + 'start_of_reconcile_period' => 'Začátek vyúčtovacího období: :period', + 'start_balance' => 'Počáteční zůstatek', + 'end_balance' => 'Konečný zůstatek', + 'update_balance_dates_instruction' => 'Match the amounts and dates above to your bank statement, and press "Start reconciling"', + 'select_transactions_instruction' => 'Select the transactions that appear on your bank statement.', + 'select_range_and_balance' => 'First verify the date-range and balances. Then press "Start reconciling"', + 'date_change_instruction' => 'If you change the date range now, any progress will be lost.', + 'update_selection' => 'Aktualizovat výběr', + 'store_reconcile' => 'Uložit vyúčtování', + 'reconciliation_transaction' => 'Reconciliation transaction', + 'Reconciliation' => 'Vyúčtování', + 'reconciliation' => 'Vyúčtování', + 'reconcile_options' => 'Možnosti vyúčtování', + 'reconcile_range' => 'Rozsah vyúčtování', + 'start_reconcile' => 'Spustit vyúčtování', + 'cash_account_type' => 'Cash', + 'cash' => 'hotovost', + 'account_type' => 'Typ účtu', + 'save_transactions_by_moving' => 'Save these transaction(s) by moving them to another account:', + 'stored_new_account' => 'Nový účet „:name“ uložen!', + 'updated_account' => 'Aktualizován účet „:name“', + 'credit_card_options' => 'Předvolby kreditní karty', + 'no_transactions_account' => 'There are no transactions (in this period) for asset account ":name".', + 'no_transactions_period' => 'Neexistují žádné transakce (v tomto období).', + 'no_data_for_chart' => 'Pro vytvoření tohoto grafu není dostatek informací (zatím).', + 'select_at_least_one_account' => 'Vyberte alespoň jeden účet aktiv', + 'select_at_least_one_category' => 'Vyberte alespoň jednu kategorii', + 'select_at_least_one_budget' => 'Vyberte alespoň jeden rozpočet', + 'select_at_least_one_tag' => 'Vyberte alespoň jeden štítek', + 'select_at_least_one_expense' => 'Please select at least one combination of expense/revenue accounts. If you have none (the list is empty) this report is not available.', + 'account_default_currency' => 'This will be the default currency associated with this account.', + 'reconcile_has_more' => 'Your Firefly III ledger has more money in it than your bank claims you should have. There are several options. Please choose what to do. Then, press "Confirm reconciliation".', + 'reconcile_has_less' => 'Your Firefly III ledger has less money in it than your bank claims you should have. There are several options. Please choose what to do. Then, press "Confirm reconciliation".', + 'reconcile_is_equal' => 'Your Firefly III ledger and your bank statements match. There is nothing to do. Please press "Confirm reconciliation" to confirm your input.', + 'create_pos_reconcile_transaction' => 'Clear the selected transactions, and create a correction adding :amount to this asset account.', + 'create_neg_reconcile_transaction' => 'Clear the selected transactions, and create a correction removing :amount from this asset account.', + 'reconcile_do_nothing' => 'Clear the selected transactions, but do not correct.', + 'reconcile_go_back' => 'Opravu můžete kdykoli upravit nebo odstranit.', + 'must_be_asset_account' => 'You can only reconcile asset accounts', + 'reconciliation_stored' => 'Vyúčtování uloženo', + 'reconciliation_error' => 'Due to an error the transactions were marked as reconciled but the correction has not been stored: :error.', + 'reconciliation_transaction_title' => 'Reconciliation (:from to :to)', + 'sum_of_reconciliation' => 'Sum of reconciliation', + 'reconcile_this_account' => 'Vyúčtovat tento účet', + 'confirm_reconciliation' => 'Potvrdit vyúčtování', + 'submitted_start_balance' => 'Předložený počáteční zůstatek', + 'selected_transactions' => 'Vybrané transakce (:count)', + 'already_cleared_transactions' => 'Already cleared transactions (:count)', + 'submitted_end_balance' => 'Předložený konečný zůstatek', + 'initial_balance_description' => 'Počáteční zůstatek pro „:account“', + 'interest_calc_' => 'neznámé', + 'interest_calc_daily' => 'Za den', + 'interest_calc_monthly' => 'Za měsíc', + 'interest_calc_yearly' => 'Za rok', + 'initial_balance_account' => 'Initial balance account of :account', + + // categories: + 'new_category' => 'Nová kategorie', + 'create_new_category' => 'Vytvořit novou kategorii', + 'without_category' => 'Bez kategorie', + 'update_category' => 'Aktualizovat kategorii', + 'updated_category' => 'Aktualizována kategorie „:name“', + 'categories' => 'Kategorie', + 'edit_category' => 'Upravit kategorii „:name“', + 'no_category' => '(žádná kategorie)', + 'category' => 'Kategorie', + 'delete_category' => 'Smazat kategorii „:name“', + 'deleted_category' => 'Kategorie „:name“ smazána', + 'store_category' => 'Uložit novou kategori', + 'stored_category' => 'Uložena nová kategorie „:name“', + 'without_category_between' => 'Bez kategorie mezi :start a :end', + + // transactions: + 'update_withdrawal' => 'Aktualizovat výběr', + 'update_deposit' => 'Aktualizovat vklad', + 'update_transfer' => 'Aktualizovat převod', + 'updated_withdrawal' => 'Aktualizován výběr „:description“', + 'updated_deposit' => 'Aktualizován vklad „:description“', + 'updated_transfer' => 'Aktualizován převod „:description“', + 'delete_withdrawal' => 'Smazat výběr „:description“', + 'delete_deposit' => 'Smazat vklad „:description“', + 'delete_transfer' => 'Smazat převod „:description“', + 'deleted_withdrawal' => 'Úspěšně smazán výběr „:description“', + 'deleted_deposit' => 'Úspěšně smazán vklad „:description“', + 'deleted_transfer' => 'Úspěšně smazán převod „:description“', + 'stored_journal' => 'Úspěšně vytvořena nová transakce „:description“', + 'stored_journal_no_descr' => 'Successfully created your new transaction', + 'updated_journal_no_descr' => 'Successfully updated your transaction', + 'select_transactions' => 'Vybrat transakce', + 'rule_group_select_transactions' => 'Apply ":title" to transactions', + 'rule_select_transactions' => 'Apply ":title" to transactions', + 'stop_selection' => 'Zastavit označování transakcí', + 'reconcile_selected' => 'Reconcile', + 'mass_delete_journals' => 'Smazat počet transakcí', + 'mass_edit_journals' => 'Upravit počet transakcí', + 'mass_bulk_journals' => 'Hromadná úprava počtu transakcí', + 'mass_bulk_journals_explain' => 'This form allows you to change properties of the transactions listed below in one sweeping update. All the transactions in the table will be updated when you change the parameters you see here.', + 'part_of_split' => 'This transaction is part of a split transaction. If you have not selected all the splits, you may end up with changing only half the transaction.', + 'bulk_set_new_values' => 'Nové hodnoty nastavíte pomocí vstupních kolonek níže. Pokud je nevyplníte, budou u všeho vyprázdněny. Také si všimněte, že pouze výběry dostanou rozpočet.', + 'no_bulk_category' => 'Neaktualizovat kategorii', + 'no_bulk_budget' => 'Neaktualizovat rozpočet', + 'no_bulk_tags' => 'Neaktualizovat štítky', + 'mass_edit' => 'Edit selected individually', + 'bulk_edit' => 'Edit selected in bulk', + 'mass_delete' => 'Delete selected', + 'cannot_edit_other_fields' => 'You cannot mass-edit other fields than the ones here, because there is no room to show them. Please follow the link and edit them by one-by-one, if you need to edit these fields.', + 'cannot_change_amount_reconciled' => 'You can\'t change the amount of reconciled transactions.', + 'no_budget' => '(žádný rozpočet)', + 'no_budget_squared' => '(žádný rozpočet)', + 'perm-delete-many' => 'Deleting many items in one go can be very disruptive. Please be cautious. You can delete part of a split transaction from this page, so take care.', + 'mass_deleted_transactions_success' => 'Smazáno :amount transakcí.', + 'mass_edited_transactions_success' => 'Aktualizováno :amount transakcí', + 'opt_group_' => '(žádný typ účtu)', + 'opt_group_no_account_type' => '(no account type)', + 'opt_group_defaultAsset' => 'Výchozí majetkový účet', + 'opt_group_savingAsset' => 'Spořicí účty', + 'opt_group_sharedAsset' => 'Sdílené účty aktiv', + 'opt_group_ccAsset' => 'Kreditní karty', + 'opt_group_cashWalletAsset' => 'Peněženky', + 'opt_group_expense_account' => 'Expense accounts', + 'opt_group_revenue_account' => 'Revenue accounts', + 'opt_group_l_Loan' => 'Závazek: Půjčka', + 'opt_group_cash_account' => 'Cash account', + 'opt_group_l_Debt' => 'Závazek: Dluh', + 'opt_group_l_Mortgage' => 'Závazek: hypotéka', + 'opt_group_l_Credit card' => 'Závazek: kreditní karta', + 'notes' => 'Poznámky', + 'unknown_journal_error' => 'Transakci se nedaří uložit. Podívejte se do souborů se záznamy událostí.', + 'attachment_not_found' => 'Tuto přílohu se nepodařilo najít.', + 'journal_link_bill' => 'This transaction is linked to bill :name. To remove the connection, uncheck the checkbox. Use rules to connect it to another bill.', + + // new user: + 'welcome' => 'Vítejte ve Firefly III!', + 'submit' => 'Odeslat', + 'submit_yes_really' => 'Odeslat (vím, co dělám)', + 'getting_started' => 'Začínáme', + 'to_get_started' => 'It is good to see you have successfully installed Firefly III. To get started with this tool please enter your bank\'s name and the balance of your main checking account. Do not worry yet if you have multiple accounts. You can add those later. It\'s just that Firefly III needs something to start with.', + 'savings_balance_text' => 'Firefly III will automatically create a savings account for you. By default, there will be no money in your savings account, but if you tell Firefly III the balance it will be stored as such.', + 'finish_up_new_user' => 'That\'s it! You can continue by pressing Submit. You will be taken to the index of Firefly III.', + 'stored_new_accounts_new_user' => 'Yay! Your new accounts have been stored.', + 'set_preferred_language' => 'If you prefer to use Firefly III in another language, please indicate so here.', + 'language' => 'Jazyk', + 'new_savings_account' => ':bank_name spořící účet', + 'cash_wallet' => 'Peněženka', + 'currency_not_present' => 'If the currency you normally use is not listed do not worry. You can create your own currencies under Options > Currencies.', + + // home page: + 'yourAccounts' => 'Vaše účty', + 'your_accounts' => 'Přehled vašeho účtu', + 'category_overview' => 'Přehled kategorie', + 'expense_overview' => 'Expense account overview', + 'revenue_overview' => 'Revenue account overview', + 'budgetsAndSpending' => 'Rozpočty a útraty', + 'budgets_and_spending' => 'Rozpočty a útraty', + 'go_to_budget' => 'Přejít na rozpočet „{budget}“', + 'savings' => 'Úspory', + 'newWithdrawal' => 'Nový výdaj', + 'newDeposit' => 'Nový vklad', + 'newTransfer' => 'Nový převod', + 'bills_to_pay' => 'Bills to pay', + 'per_day' => 'Za den', + 'left_to_spend_per_day' => 'Left to spend per day', + 'bills_paid' => 'Bills paid', + + // menu and titles, should be recycled as often as possible: + 'currency' => 'Měna', + 'preferences' => 'Předvolby', + 'logout' => 'Odhlásit se', + 'toggleNavigation' => 'Vyp/zap. navigaci', + 'searchPlaceholder' => 'Hledat…', + 'version' => 'Verze', + 'dashboard' => 'Přehled', + 'available_budget' => 'Rozpočet k dispozici ({currency})', + 'currencies' => 'Měny', + 'activity' => 'Aktivita', + 'usage' => 'Použití', + 'accounts' => 'Účty', + 'Asset account' => 'Asset account', + 'Default account' => 'Asset account', + 'Expense account' => 'Výdajový účet', + 'Revenue account' => 'Příjmový účet', + 'Initial balance account' => 'Účet počátečního zůstatku', + 'account_type_Debt' => 'Dluh', + 'account_type_Loan' => 'Půjčka', + 'account_type_Mortgage' => 'Hypotéka', + 'account_type_Credit card' => 'Kreditní karta', + 'budgets' => 'Rozpočty', + 'tags' => 'Štítky', + 'reports' => 'Přehledy', + 'transactions' => 'Transakce', + 'expenses' => 'Výdaje', + 'income' => 'Odměna/příjem', + 'transfers' => 'Převody', + 'moneyManagement' => 'Správa peněz', + 'money_management' => 'Správa peněz', + 'tools' => 'Nástroje', + 'piggyBanks' => 'Pokladničky', + 'piggy_banks' => 'Pokladničky', + 'amount_x_of_y' => '{current} z {total}', + 'bills' => 'Účty', + 'withdrawal' => 'Výběr', + 'opening_balance' => 'Počáteční zůstatek', + 'deposit' => 'Vklad', + 'account' => 'Účet', + 'transfer' => 'Převod', + 'Withdrawal' => 'Výběr', + 'Deposit' => 'Vklad', + 'Transfer' => 'Převod', + 'bill' => 'Účet', + 'yes' => 'Ano', + 'no' => 'Ne', + 'amount' => 'Částka', + 'overview' => 'Přehled', + 'saveOnAccount' => 'Uložit na účet', + 'unknown' => 'Neznámé', + 'daily' => 'Denně', + 'monthly' => 'Měsíčně', + 'profile' => 'Profil', + 'errors' => 'Chyby', + 'debt_start_date' => 'Datum začátku dluhu', + 'debt_start_amount' => 'Počáteční výše dluhu', + 'debt_start_amount_help' => 'If you owe an amount its best to enter a negative amount, because it influences your net worth. If you\'re owed an amount the same applies. Check out the help pages for more information.', + 'store_new_liabilities_account' => 'Uložit nový závazek', + 'edit_liabilities_account' => 'Upravit závazek „:name“', + + // reports: + 'report_default' => 'Výchozí finanční výkaz v období :start a :end', + 'report_audit' => 'Přehled historie transakcí mezi :start a :end', + 'report_category' => 'Category report between :start and :end', + 'report_account' => 'Expense/revenue account report between :start and :end', + 'report_budget' => 'Budget report between :start and :end', + 'report_tag' => 'Tag report between :start and :end', + 'quick_link_reports' => 'Rychlé odkazy', + 'quick_link_examples' => 'These are just some example links to get you started. Check out the help pages under the (?)-button for information on all reports and the magic words you can use.', + 'quick_link_default_report' => 'Výchozí výkaz o financích', + 'quick_link_audit_report' => 'Přehled historie transakcí', + 'report_this_month_quick' => 'Stávající měsíc, všechny účty', + 'report_last_month_quick' => 'Minulý měsíc, všechny účty', + 'report_this_year_quick' => 'Stávající rok, všechny účty', + 'report_this_fiscal_year_quick' => 'Stávající fiskální rok, všechny účty', + 'report_all_time_quick' => 'Kdykoli, všechny účty', + 'reports_can_bookmark' => 'Pamatujte, že výkazy je možné si ukládat do záložek.', + 'incomeVsExpenses' => 'Příjmy vůči nákladům', + 'accountBalances' => 'Zůstatky účtů', + 'balanceStart' => 'Zůstatek na začátku období', + 'balanceEnd' => 'Zůstatek na konci období', + 'splitByAccount' => 'Rozúčtování podle účtů', + 'coveredWithTags' => 'Pokryto štítky', + 'leftInBudget' => 'Zbývá v rozpočtu', + 'sumOfSums' => 'Souhrn součtů', + 'noCategory' => '(bez kategorie)', + 'notCharged' => 'Not charged (yet)', + 'inactive' => 'Neaktivní', + 'active' => 'Aktivní', + 'difference' => 'Rozdíl', + 'money_flowing_in' => 'In', + 'money_flowing_out' => 'Out', + 'topX' => 'nej :number', + 'show_full_list' => 'Zobrazit celý seznam', + 'show_only_top' => 'Zobrazit pouze :number nej', + 'report_type' => 'Typ přehledu', + 'report_type_default' => 'Výchozí výkaz o financích', + 'report_type_audit' => 'Přehled historie transakcí (audit)', + 'report_type_category' => 'Výkaz o kategorii', + 'report_type_budget' => 'Sestava rozpočtu', + 'report_type_tag' => 'Tag report', + 'report_type_account' => 'Expense/revenue account report', + 'more_info_help' => 'More information about these types of reports can be found in the help pages. Press the (?) icon in the top right corner.', + 'report_included_accounts' => 'Zahrnuté účty', + 'report_date_range' => 'Date range', + 'report_preset_ranges' => 'Přednastavené rozsahy', + 'shared' => 'Sdíleno', + 'fiscal_year' => 'Fiskální rok', + 'income_entry' => 'Příjmy z účtu „:name“ mezi :start a :end', + 'expense_entry' => 'Expenses to account ":name" between :start and :end', + 'category_entry' => 'Expenses in category ":name" between :start and :end', + 'budget_spent_amount' => 'Expenses in budget ":budget" between :start and :end', + 'balance_amount' => 'Expenses in budget ":budget" paid from account ":account" between :start and :end', + 'no_audit_activity' => 'No activity was recorded on account :account_name between :start and :end.', + 'audit_end_balance' => 'Account balance of :account_name at the end of :end was: :balance', + 'reports_extra_options' => 'Další volby', + 'report_has_no_extra_options' => 'Tento výkaz nemá žádné další volby', + 'reports_submit' => 'Zobrazit výkaz', + 'end_after_start_date' => 'Je třeba, aby konečné datum výkazu bylo později, než to počáteční.', + 'select_category' => 'Vybrat kategorie', + 'select_budget' => 'Vybrat rozpočty', + 'select_tag' => 'Vybrat štítky', + 'income_per_category' => 'Příjem podle kategorie', + 'expense_per_category' => 'Výdaje podle kategorie', + 'expense_per_budget' => 'Výdaje podle rozpočtu', + 'income_per_account' => 'Příjem podle účtu', + 'expense_per_account' => 'Výdaj podle účtu', + 'expense_per_tag' => 'Výdaje podle štítku', + 'income_per_tag' => 'Příjmy podle štítku', + 'include_expense_not_in_budget' => 'Included expenses not in the selected budget(s)', + 'include_expense_not_in_account' => 'Included expenses not in the selected account(s)', + 'include_expense_not_in_category' => 'Included expenses not in the selected category(ies)', + 'include_income_not_in_category' => 'Included income not in the selected category(ies)', + 'include_income_not_in_account' => 'Included income not in the selected account(s)', + 'include_income_not_in_tags' => 'Included income not in the selected tag(s)', + 'include_expense_not_in_tags' => 'Included expenses not in the selected tag(s)', + 'everything_else' => 'Všechno ostatní', + 'income_and_expenses' => 'Příjmy a výdaje', + 'spent_average' => 'Utraceno (průměrně)', + 'income_average' => 'Získáno (průměrně)', + 'transaction_count' => 'Počet transakcí', + 'average_spending_per_account' => 'Průměrné výdaje na účet', + 'average_income_per_account' => 'Průměrný příjem v jednotlivých účtech', + 'total' => 'Celkem', + 'description' => 'Popis', + 'sum_of_period' => 'Souhrn období', + 'average_in_period' => 'Průměr období', + 'account_role_defaultAsset' => 'Výchozí účet aktiv', + 'account_role_sharedAsset' => 'Sdílený účet aktiv', + 'account_role_savingAsset' => 'Spořicí účet', + 'account_role_ccAsset' => 'Kreditní karta', + 'account_role_cashWalletAsset' => 'Peněženka', + 'budget_chart_click' => 'Graf zobrazíte kliknutím na název rozpočtu ve výše uvedené tabulce.', + 'category_chart_click' => 'Graf zobrazíte kliknutím na název kategorie ve výše uvedené tabulce.', + 'in_out_accounts' => 'Earned and spent per combination', + 'in_out_per_category' => 'Získáno a vydáno v jednotlivých kategoriích', + 'out_per_budget' => 'Vydáno v jednotlivých rozpočtech', + 'select_expense_revenue' => 'Vybrat účet výdajů/odměn', + 'multi_currency_report_sum' => 'Protože tento seznam obsahuje účty v různých měnách, nedávají zobrazované součty smysl. Výkaz vždy náhradně použije měnu, kterou máte nastavenou jako výchozí.', + 'sum_in_default_currency' => 'Souhrn vždy bude v měně, kterou jste nastavili jako výchozí.', + 'net_filtered_prefs' => 'This chart will never include accounts that have the "Include in net worth"-option unchecked.', + + // charts: + 'chart' => 'Graf', + 'month' => 'Měsíc', + 'budget' => 'Rozpočet', + 'spent' => 'Utraceno', + 'spent_in_budget' => 'Utraceno v rozpočtu', + 'left_to_spend' => 'Zbývá k utracení', + 'earned' => 'Vyděláno', + 'overspent' => 'Překročeny výdaje', + 'left' => 'Zbývá', + 'max-amount' => 'Maximální částka', + 'min-amount' => 'Minimální částka', + 'journal-amount' => 'Current bill entry', + 'name' => 'Název', + 'date' => 'Datum', + 'paid' => 'Zaplaceno', + 'unpaid' => 'Nezaplaceno', + 'day' => 'Den', + 'budgeted' => 'Rozpočet', + 'period' => 'Období', + 'balance' => 'Zůstatek', + 'sum' => 'Součet', + 'summary' => 'Souhrn', + 'average' => 'Průměr', + 'balanceFor' => 'Zůstatek na :name', + 'no_tags_for_cloud' => 'Nejsou zadané žádné štítky, ze kterých by bylo možné vytvořit oblak', + 'tag_cloud' => 'Oblak štítků', + + // piggy banks: + 'add_money_to_piggy' => 'Vložit peníze do pokladničky ":name"', + 'piggy_bank' => 'Pokladnička', + 'new_piggy_bank' => 'Nová pokladnička', + 'store_piggy_bank' => 'Uložit novou pokladničku', + 'stored_piggy_bank' => 'Uložit novou pokladničku „:name“', + 'account_status' => 'Stav účtu', + 'left_for_piggy_banks' => 'Left for piggy banks', + 'sum_of_piggy_banks' => 'Součet pokladniček', + 'saved_so_far' => 'Doposud naspořeno', + 'left_to_save' => 'Left to save', + 'suggested_amount' => 'Doporučené měsíční částka do úspor', + 'add_money_to_piggy_title' => 'Vložit peníze do pokladničky ":name"', + 'remove_money_from_piggy_title' => 'Vybrat peníze z pokladničky ":name"', + 'add' => 'Přidat', + 'no_money_for_piggy' => 'Nejsou žádné peníze, které by se daly vložit do této pokladničky.', + 'suggested_savings_per_month' => 'Doporučeno na měsíc', + + 'remove' => 'Odebrat', + 'max_amount_add' => 'Nejvyšší částka, kterou je možné přidat je', + 'max_amount_remove' => 'Nejvyšší částka, kterou je možné odebrat je', + 'update_piggy_button' => 'Aktualizovat pokladničku', + 'update_piggy_title' => 'Aktualizovat pokladničku ":name"', + 'updated_piggy_bank' => 'Aktualizována pokladnička „:name“', + 'details' => 'Podrobnosti', + 'events' => 'Události', + 'target_amount' => 'Cílová částka', + 'start_date' => 'Datum zahájení', + 'no_start_date' => 'Žádné datum začátku', + 'target_date' => 'Cílové datum', + 'no_target_date' => 'Žádné cílové datum', + 'table' => 'Tabulka', + 'delete_piggy_bank' => 'Smazat pokladničku ":name"', + 'cannot_add_amount_piggy' => 'Nedaří se přidat :amount do „:name“.', + 'cannot_remove_from_piggy' => 'Could not remove :amount from ":name".', + 'deleted_piggy_bank' => 'Pokladnička ":name" smazána', + 'added_amount_to_piggy' => ':amount přidáno do „:name“', + 'removed_amount_from_piggy' => ':amount odebráno z „:name“', + 'piggy_events' => 'Související pokladničky', + + // tags + 'delete_tag' => 'Smazat štítek „:tag“', + 'deleted_tag' => 'Smazán štítek „:tag“', + 'new_tag' => 'Vytvořit nový štítek', + 'edit_tag' => 'Upravit štítek „:tag“', + 'updated_tag' => 'Aktualizován štítek „:tag“', + 'created_tag' => 'Štítek „:tag“ byl vytvořen.', + + 'transaction_journal_information' => 'Informace o transakci', + 'transaction_journal_meta' => 'Meta informace', + 'transaction_journal_more' => 'Další informace', + 'att_part_of_journal' => 'Uloženo pod „:journal“', + 'total_amount' => 'Celková částka', + 'number_of_decimals' => 'Počet desetinných míst', + + // administration + 'administration' => 'Správa', + 'user_administration' => 'Správa uživatelů', + 'list_all_users' => 'Všichni uživatelé', + 'all_users' => 'Všichni uživatelé', + 'instance_configuration' => 'Nastavení', + 'firefly_instance_configuration' => 'Možnosti nastavení Firefly III', + 'setting_single_user_mode' => 'Režim pro jediného uživatele', + 'setting_single_user_mode_explain' => 'Ve výchozím stavu, Firefly III přijme pouze jednu registraci – Vás. Toto je bezpečnostní opatření, bránící ostatním použít vaši instanci, dokud jim to nepovolíte. Další registrace jsou blokovány. Pokud zrušíte zaškrtnutí tohoto, ostatní mohou vaši instanci také použít za předpokladu, že je jim dostupná (když je připojena k Internetu).', + 'store_configuration' => 'Uložit nastavení', + 'single_user_administration' => 'Správa uživatele pro :email', + 'edit_user' => 'Upravit uživatele :email', + 'hidden_fields_preferences' => 'You can enable more transaction options in your settings.', + 'user_data_information' => 'Uživatelská data', + 'user_information' => 'Informace o uživateli', + 'total_size' => 'celková velikost', + 'budget_or_budgets' => 'rozpočty', + 'budgets_with_limits' => 'Rozpočty s nastavenou částkou', + 'nr_of_rules_in_total_groups' => ':count_rules rule(s) in :count_groups rule group(s)', + 'tag_or_tags' => 'štítky', + 'configuration_updated' => 'Nastavení bylo aktualizováno', + 'setting_is_demo_site' => 'Demostránka', + 'setting_is_demo_site_explain' => 'If you check this box, this installation will behave as if it is the demo site, which can have weird side effects.', + 'block_code_bounced' => 'Email message(s) bounced', + 'block_code_expired' => 'Platnost demoúčtu skončila', + 'no_block_code' => 'Není důvod pro blokování nebo uživatel není blokován', + 'block_code_email_changed' => 'Uživatel ještě nepotvrdil svou e-mailovou adresu', + 'admin_update_email' => 'Narozdíl od profilové stránky uživatel NEBUDE upozorněn na změnu své e-mailové adresy!', + 'update_user' => 'Aktualizovat uživatele', + 'updated_user' => 'Data uživatele byla změněna.', + 'delete_user' => 'Smazat uživatele :email', + 'user_deleted' => 'Uživatel byl smazán', + 'send_test_email' => 'Poslat zkušební e-mail', + 'send_test_email_text' => 'To see if your installation is capable of sending email, please press this button. You will not see an error here (if any), the log files will reflect any errors. You can press this button as many times as you like. There is no spam control. The message will be sent to :email and should arrive shortly.', + 'send_message' => 'Poslat zprávu', + 'send_test_triggered' => 'Test was triggered. Check your inbox and the log files.', + + 'split_transaction_title' => 'Description of the split transaction', + 'split_title_help' => 'If you create a split transaction, there must be a global description for all splits of the transaction.', + 'transaction_information' => 'Informace o transakci', + 'you_create_transfer' => 'Vytváříte převod.', + 'you_create_withdrawal' => 'Vytváříte výběr.', + 'you_create_deposit' => 'Vytváříte vklad.', + + + // links + 'journal_link_configuration' => 'Transaction links configuration', + 'create_new_link_type' => 'Vytvořit nový typ odkazu', + 'store_new_link_type' => 'Store new link type', + 'update_link_type' => 'Aktualizovat typ propojení', + 'edit_link_type' => 'Upravit odkaz typu „:name“', + 'updated_link_type' => 'Updated link type ":name"', + 'delete_link_type' => 'Delete link type ":name"', + 'deleted_link_type' => 'Deleted link type ":name"', + 'stored_new_link_type' => 'Store new link type ":name"', + 'cannot_edit_link_type' => 'Cannot edit link type ":name"', + 'link_type_help_name' => 'Ie. "Duplicates"', + 'link_type_help_inward' => 'tj. „duplicity“', + 'link_type_help_outward' => 'Ie. "is duplicated by"', + 'save_connections_by_moving' => 'Save the link between these transaction(s) by moving them to another link type:', + 'do_not_save_connection' => '(neukládat spojení)', + 'link_transaction' => 'Link transaction', + 'link_to_other_transaction' => 'Propojit tuto transakci s jinou', + 'select_transaction_to_link' => 'Select a transaction to link this transaction to. The links are currently unused in Firefly III (apart from being shown), but I plan to change this in the future. Use the search box to select a transaction either by title or by ID. If you want to add custom link types, check out the administration section.', + 'this_transaction' => 'Tato transakce', + 'transaction' => 'Transakce', + 'comments' => 'Komentáře', + 'link_notes' => 'Any notes you wish to store with the link.', + 'invalid_link_selection' => 'Tyto transakce nelze propojit', + 'selected_transaction' => 'Selected transaction', + 'journals_linked' => 'Transakce jsou propojeny.', + 'journals_error_linked' => 'Tyto transakce už jsou propojené.', + 'journals_link_to_self' => 'Není možné propojit transakci s ní samotnou', + 'journal_links' => 'Transaction links', + 'this_withdrawal' => 'Tento výběr', + 'this_deposit' => 'Tento vklad', + 'this_transfer' => 'Tento převod', + 'overview_for_link' => 'Overview for link type ":name"', + 'source_transaction' => 'Zdrojová transakce', + 'link_description' => 'Popis odkazu', + 'destination_transaction' => 'Cílová transakce', + 'delete_journal_link' => 'Delete the link between :source and :destination', + 'deleted_link' => 'Smazaný odkaz', + + // link translations: + 'Paid_name' => 'Zaplaceno', + 'Refund_name' => 'Refund', + 'Reimbursement_name' => 'Reimbursement', + 'Related_name' => 'Související', + 'relates to_inward' => 'souvisí s', + 'is (partially) refunded by_inward' => 'is (partially) refunded by', + 'is (partially) paid for by_inward' => 'is (partially) paid for by', + 'is (partially) reimbursed by_inward' => 'is (partially) reimbursed by', + 'inward_transaction' => 'Inward transaction', + 'outward_transaction' => 'Outward transaction', + 'relates to_outward' => 'souvisí s', + '(partially) refunds_outward' => '(partially) refunds', + '(partially) pays for_outward' => '(partially) pays for', + '(partially) reimburses_outward' => '(partially) reimburses', + + // split a transaction: + 'splits' => 'Rozúčtování', + 'add_another_split' => 'Přidat další rozúčtování', + 'split-transactions' => 'Rozúčtovat transakce', + 'do_split' => 'Rozúčtovat', + 'split_this_withdrawal' => 'Rozúčtovat tento výběr', + 'split_this_deposit' => 'Rozúčtovat tento vklad', + 'split_this_transfer' => 'Rozúčtovat tento převod', + 'cannot_edit_opening_balance' => 'You cannot edit the opening balance of an account.', + 'no_edit_multiple_left' => 'You have selected no valid transactions to edit.', + 'breadcrumb_convert_group' => 'Convert transaction', + 'convert_invalid_source' => 'Source information is invalid for transaction #%d.', + 'convert_invalid_destination' => 'Destination information is invalid for transaction #%d.', + + // Import page (general strings only) + 'import_index_title' => 'Naimportovat transakce do Firefly III', + 'import_data' => 'Importovat data', + 'import_transactions' => 'Importovat transakce', + + // sandstorm.io errors and messages: + 'sandstorm_not_available' => 'This function is not available when you are using Firefly III within a Sandstorm.io environment.', + + // empty lists? no objects? instructions: + 'no_accounts_title_asset' => 'Let\'s create an asset account!', + 'no_accounts_intro_asset' => 'You have no asset accounts yet. Asset accounts are your main accounts: your checking account, savings account, shared account or even your credit card.', + 'no_accounts_imperative_asset' => 'To start using Firefly III you must create at least one asset account. Let\'s do so now:', + 'no_accounts_create_asset' => 'Vytvořit nový účet', + 'no_accounts_title_expense' => 'Vytvořme výdajový účet!', + 'no_accounts_intro_expense' => 'You have no expense accounts yet. Expense accounts are the places where you spend money, such as shops and supermarkets.', + 'no_accounts_imperative_expense' => 'Expense accounts are created automatically when you create transactions, but you can create one manually too, if you want. Let\'s create one now:', + 'no_accounts_create_expense' => 'Vytvořit výdajový účet', + 'no_accounts_title_revenue' => 'Vytvořme příjmový účet!', + 'no_accounts_intro_revenue' => 'Doposud jste ještě nevytvořili žádné příjmové účty. Příjmové účty jsou místa, ze kterých dostáváte peníze – například váš zaměstnavatel.', + 'no_accounts_imperative_revenue' => 'Revenue accounts are created automatically when you create transactions, but you can create one manually too, if you want. Let\'s create one now:', + 'no_accounts_create_revenue' => 'Vytvořit příjmový účet', + 'no_accounts_title_liabilities' => 'Pojďme vytvořit závazek!', + 'no_accounts_intro_liabilities' => 'Zatím nemáte žádné závazky. Ty jsou účty které registrují vaše půjčky a ostatní dluhy.', + 'no_accounts_imperative_liabilities' => 'Tuto funkci nemusíte používat, ale hodí se pokud si chcete držet přehled v těchto věcech.', + 'no_accounts_create_liabilities' => 'Vytvořit závazek', + 'no_budgets_title_default' => 'Pojďme vytvořit rozpočet', + 'no_budgets_intro_default' => 'Zatím nemáte žádné rozpočty. Ty slouží k uspořádávání vašich výdajů do logických skupin, kterým je možné dávat překročitelné limity výdajů.', + 'no_budgets_imperative_default' => 'Rozpočty jsou základní nástroje správy financí. Pojďme takový vytvořit:', + 'no_budgets_create_default' => 'Vytvořit rozpočet', + 'no_categories_title_default' => 'Pojďme vytvořit kategorii!', + 'no_categories_intro_default' => 'You have no categories yet. Categories are used to fine tune your transactions and label them with their designated category.', + 'no_categories_imperative_default' => 'Categories are created automatically when you create transactions, but you can create one manually too. Let\'s create one now:', + 'no_categories_create_default' => 'Vytvořit kategorii', + 'no_tags_title_default' => 'Pojďme vytvořit štítek!', + 'no_tags_intro_default' => 'You have no tags yet. Tags are used to fine tune your transactions and label them with specific keywords.', + 'no_tags_imperative_default' => 'Tags are created automatically when you create transactions, but you can create one manually too. Let\'s create one now:', + 'no_tags_create_default' => 'Vytvořit štítek', + 'no_transactions_title_withdrawal' => 'Pojďme vytvořit výdaj!', + 'no_transactions_intro_withdrawal' => 'You have no expenses yet. You should create expenses to start managing your finances.', + 'no_transactions_imperative_withdrawal' => 'Have you spent some money? Then you should write it down:', + 'no_transactions_create_withdrawal' => 'Vytvořit výdaj', + 'no_transactions_title_deposit' => 'Pojďme vytvořit nějaký příjem!', + 'no_transactions_intro_deposit' => 'You have no recorded income yet. You should create income entries to start managing your finances.', + 'no_transactions_imperative_deposit' => 'Have you received some money? Then you should write it down:', + 'no_transactions_create_deposit' => 'Vytvořit vklad', + 'no_transactions_title_transfers' => 'Pojďme vytvořit převod!', + 'no_transactions_intro_transfers' => 'You have no transfers yet. When you move money between asset accounts, it is recorded as a transfer.', + 'no_transactions_imperative_transfers' => 'Have you moved some money around? Then you should write it down:', + 'no_transactions_create_transfers' => 'Vytvořit převod', + 'no_piggies_title_default' => 'Vytvořit pokladničku!', + 'no_piggies_intro_default' => 'You have no piggy banks yet. You can create piggy banks to divide your savings and keep track of what you\'re saving up for.', + 'no_piggies_imperative_default' => 'Do you have things you\'re saving money for? Create a piggy bank and keep track:', + 'no_piggies_create_default' => 'Vytvořit novou pokladničku', + 'no_bills_title_default' => 'Let\'s create a bill!', + 'no_bills_intro_default' => 'You have no bills yet. You can create bills to keep track of regular expenses, like your rent or insurance.', + 'no_bills_imperative_default' => 'Do you have such regular bills? Create a bill and keep track of your payments:', + 'no_bills_create_default' => 'Create a bill', + + // recurring transactions + 'recurrences' => 'Opakované transakce', + 'recurring_calendar_view' => 'Kalendář', + 'no_recurring_title_default' => 'Let\'s create a recurring transaction!', + 'no_recurring_intro_default' => 'You have no recurring transactions yet. You can use these to make Firefly III automatically create transactions for you.', + 'no_recurring_imperative_default' => 'This is a pretty advanced feature but it can be extremely useful. Make sure you read the documentation (?)-icon in the top right corner) before you continue.', + 'no_recurring_create_default' => 'Vytvořit opakující se transakci', + 'make_new_recurring' => 'Vytvořit opakující se transakci', + 'recurring_daily' => 'Každý den', + 'recurring_weekly' => 'Každý týden v :weekday', + 'recurring_monthly' => 'Every month on the :dayOfMonth(st/nd/rd/th) day', + 'recurring_ndom' => 'Every month on the :dayOfMonth(st/nd/rd/th) :weekday', + 'recurring_yearly' => 'Každý rok v :date', + 'overview_for_recurrence' => 'Overview for recurring transaction ":title"', + 'warning_duplicates_repetitions' => 'In rare instances, dates appear twice in this list. This can happen when multiple repetitions collide. Firefly III will always generate one transaction per day.', + 'created_transactions' => 'Související transakce', + 'expected_withdrawals' => 'Očekávané výběry', + 'expected_deposits' => 'Očekávané vklady', + 'expected_transfers' => 'Očekávané převody', + 'created_withdrawals' => 'Vytvořené výběry', + 'created_deposits' => 'Vytvořené vklady', + 'created_transfers' => 'Vytvořené převody', + 'created_from_recurrence' => 'Created from recurring transaction ":title" (#:id)', + 'recurring_never_cron' => 'It seems the cron job that is necessary to support recurring transactions has never run. This is of course normal when you have just installed Firefly III, but this should be something to set up as soon as possible. Please check out the help-pages using the (?)-icon in the top right corner of the page.', + 'recurring_cron_long_ago' => 'It looks like it has been more than 36 hours since the cron job to support recurring transactions has fired for the last time. Are you sure it has been set up correctly? Please check out the help-pages using the (?)-icon in the top right corner of the page.', + + 'recurring_meta_field_tags' => 'Štítky', + 'recurring_meta_field_notes' => 'Poznámky', + 'recurring_meta_field_bill_id' => 'Účet', + 'recurring_meta_field_piggy_bank_id' => 'Pokladnička', + 'create_new_recurrence' => 'Create new recurring transaction', + 'help_first_date' => 'Indicate the first expected recurrence. This must be in the future.', + 'help_first_date_no_past' => 'Indicate the first expected recurrence. Firefly III will not create transactions in the past.', + 'no_currency' => '(žádná měna)', + 'mandatory_for_recurring' => 'Mandatory recurrence information', + 'mandatory_for_transaction' => 'Povinné informace o transakci', + 'optional_for_recurring' => 'Optional recurrence information', + 'optional_for_transaction' => 'Volitelné informace o transakci', + 'change_date_other_options' => 'Change the "first date" to see more options.', + 'mandatory_fields_for_tranaction' => 'The values here will end up in the transaction(s) being created', + 'click_for_calendar' => 'Click here for a calendar that shows you when the transaction would repeat.', + 'repeat_forever' => 'Opakovat donekonečna', + 'repeat_until_date' => 'Opakovat do data', + 'repeat_times' => 'Zopakovat tolikrát', + 'recurring_skips_one' => 'Every other', + 'recurring_skips_more' => 'Skips :count occurrences', + 'store_new_recurrence' => 'Uložit opakovanou transakci', + 'stored_new_recurrence' => 'Recurring transaction ":title" stored successfully.', + 'edit_recurrence' => 'Edit recurring transaction ":title"', + 'recurring_repeats_until' => 'Opakuje se do :date', + 'recurring_repeats_forever' => 'Opakuje navždy', + 'recurring_repeats_x_times' => 'Opakuje se :count krát', + 'update_recurrence' => 'Aktualizovat opakující se transakci', + 'updated_recurrence' => 'Updated recurring transaction ":title"', + 'recurrence_is_inactive' => 'This recurring transaction is not active and will not generate new transactions.', + 'delete_recurring' => 'Delete recurring transaction ":title"', + 'new_recurring_transaction' => 'Nová opakující se transakce', + 'help_weekend' => 'What should Firefly III do when the recurring transaction falls on a Saturday or Sunday?', + 'do_nothing' => 'Jen vytvořit transakci', + 'skip_transaction' => 'Přeskočit výskyt', + 'jump_to_friday' => 'Create the transaction on the previous Friday instead', + 'jump_to_monday' => 'Create the transaction on the next Monday instead', + 'will_jump_friday' => 'Will be created on Friday instead of the weekends.', + 'will_jump_monday' => 'Will be created on Monday instead of the weekends.', + 'except_weekends' => 'Vyjma výkendů', + 'recurrence_deleted' => 'Recurring transaction ":title" deleted', + + // new lines for summary controller. + 'box_balance_in_currency' => 'Balance (:currency)', + 'box_spent_in_currency' => 'Spent (:currency)', + 'box_earned_in_currency' => 'Earned (:currency)', + 'box_bill_paid_in_currency' => 'Bills paid (:currency)', + 'box_bill_unpaid_in_currency' => 'Bills unpaid (:currency)', + 'box_left_to_spend_in_currency' => 'Left to spend (:currency)', + 'box_net_worth_in_currency' => 'Net worth (:currency)', + 'box_spend_per_day' => 'Left to spend per day: :amount', + +]; diff --git a/resources/lang/cs_CZ/form.php b/resources/lang/cs_CZ/form.php new file mode 100644 index 0000000000..7870f48a0f --- /dev/null +++ b/resources/lang/cs_CZ/form.php @@ -0,0 +1,259 @@ +. + */ + +declare(strict_types=1); + +return [ + // new user: + 'bank_name' => 'Název banky', + 'bank_balance' => 'Zůstatek', + 'savings_balance' => 'Zůstatek úspor', + 'credit_card_limit' => 'Limit kreditní karty', + 'automatch' => 'Hledat shodu automaticky', + 'skip' => 'Přeskočit', + 'enabled' => 'Zapnuto', + 'name' => 'Název', + 'active' => 'Aktivní', + 'amount_min' => 'Minimální částka', + 'amount_max' => 'Maximální částka', + 'match' => 'Matches on', + 'strict' => 'Striktní režim', + 'repeat_freq' => 'Repeats', + 'journal_currency_id' => 'Měna', + 'currency_id' => 'Měna', + 'transaction_currency_id' => 'Měna', + 'external_ip' => 'Externí IP adresa vašeho serveru', + 'attachments' => 'Přílohy', + 'journal_amount' => 'Částka', + 'journal_source_name' => 'Příjmový účet (zdroj)', + 'keep_bill_id' => 'Bill', + 'journal_source_id' => 'Účet aktiv (zdroj)', + 'BIC' => 'BIC', + 'verify_password' => 'Ověřit odolnost hesla', + 'source_account' => 'Zdrojový účet', + 'destination_account' => 'Cílový účet', + 'journal_destination_id' => 'Majetkový účet (cíl)', + 'asset_destination_account' => 'Cílový účet', + 'include_net_worth' => 'Zahrnout do čistého jmění', + 'asset_source_account' => 'Zdrojový účet', + 'journal_description' => 'Popis', + 'note' => 'Poznámky', + 'store_new_transaction' => 'Uložit novou transakci', + 'split_journal' => 'Rozúčtovat transakci', + 'split_journal_explanation' => 'Rozdělit tuto transakci na vícero částí', + 'currency' => 'Měna', + 'account_id' => 'Účet aktiv', + 'budget_id' => 'Rozpočet', + 'opening_balance' => 'Opening balance', + 'tagMode' => 'Režim štítku', + 'tag_position' => 'Umístění štítku', + 'virtual_balance' => 'Virtual balance', + 'targetamount' => 'Cílová částka', + 'account_role' => 'Account role', + 'opening_balance_date' => 'Opening balance date', + 'cc_type' => 'Credit card payment plan', + 'cc_monthly_payment_date' => 'Credit card monthly payment date', + 'piggy_bank_id' => 'Pokladnička', + 'returnHere' => 'Vrátit sem', + 'returnHereExplanation' => 'Po uložení, vrátit se sem pro vytvoření další.', + 'returnHereUpdateExplanation' => 'Po aktualizaci se vrátit sem.', + 'description' => 'Popis', + 'expense_account' => 'Výdajový účet', + 'revenue_account' => 'Příjmový účet', + 'decimal_places' => 'Desetinná místa', + 'exchange_rate_instruction' => 'Zahraniční měny', + 'source_amount' => 'Částka (zdroj)', + 'destination_amount' => 'Částka (cíl)', + 'native_amount' => 'Native amount', + 'new_email_address' => 'Nová e-mailová adresa', + 'verification' => 'Ověření', + 'api_key' => 'Klíč k API', + 'remember_me' => 'Zapamatovat si mě', + 'liability_type_id' => 'Typ závazku', + 'interest' => 'Úrok', + 'interest_period' => 'Úrokové období', + + 'source_account_asset' => 'Zdrojový účet (účet aktiv)', + 'destination_account_expense' => 'Cílový účet (výdajový účet)', + 'destination_account_asset' => 'Cílový účet (účet aktiv)', + 'source_account_revenue' => 'Zdrojový účet (příjmový účet)', + 'type' => 'Typ', + 'convert_Withdrawal' => 'Přeměnit výběr', + 'convert_Deposit' => 'Přeměnit vklad', + 'convert_Transfer' => 'Přeměnit převod', + + 'amount' => 'Částka', + 'foreign_amount' => 'Částka v cizí měně', + 'existing_attachments' => 'Existující přílohy', + 'date' => 'Datum', + 'interest_date' => 'Úrokové datum', + 'book_date' => 'Book date', + 'process_date' => 'Datum zpracování', + 'category' => 'Kategorie', + 'tags' => 'Štítky', + 'deletePermanently' => 'Nadobro smazat', + 'cancel' => 'Storno', + 'targetdate' => 'Cílové datum', + 'startdate' => 'Datum zahájení', + 'tag' => 'Štítek', + 'under' => 'Pod', + 'symbol' => 'Symbol', + 'code' => 'Kód', + 'iban' => 'IBAN', + 'account_number' => 'Account number', + 'creditCardNumber' => 'Číslo kreditní karty', + 'has_headers' => 'Hlavičky', + 'date_format' => 'Formát data', + 'specifix' => 'Opravy pro konkrétní soubor či banku', + 'attachments[]' => 'Přílohy', + 'store_new_withdrawal' => 'Uložit nový výběr', + 'store_new_deposit' => 'Uložit nový vklad', + 'store_new_transfer' => 'Uložit nový převod', + 'add_new_withdrawal' => 'Přidat nový výběr', + 'add_new_deposit' => 'Přidat nový vklad', + 'add_new_transfer' => 'Přidat nový převod', + 'title' => 'Title', + 'notes' => 'Poznámky', + 'filename' => 'Název souboru', + 'mime' => 'Mime typ', + 'size' => 'Velikost', + 'trigger' => 'Spouštěč', + 'stop_processing' => 'Zastavit zpracování', + 'start_date' => 'Začátek rozsahu', + 'end_date' => 'Konec rozsahu', + 'include_attachments' => 'Včetně nahraných příloh', + 'include_old_uploads' => 'Zahrnout importovaná data', + 'delete_account' => 'Smazat účet „:name“', + 'delete_bill' => 'Delete bill ":name"', + 'delete_budget' => 'Smazat rozpočet „:name“', + 'delete_category' => 'Smazat kategorii „:name“', + 'delete_currency' => 'Odstranit měnu „:name“', + 'delete_journal' => 'Smazat transakci, která má popis „:description“', + 'delete_attachment' => 'Smazat přílohu „:name“', + 'delete_rule' => 'Smazat pravidlo „:title“', + 'delete_rule_group' => 'Smazat skupinu pravidel „:title“', + 'delete_link_type' => 'Smazat odkaz typu „:name“', + 'delete_user' => 'Smazat uživatele „:email“', + 'delete_recurring' => 'Smazat opakovanou transakci „:title“', + 'user_areYouSure' => 'If you delete user ":email", everything will be gone. There is no undo, undelete or anything. If you delete yourself, you will lose access to this instance of Firefly III.', + 'attachment_areYouSure' => 'Are you sure you want to delete the attachment named ":name"?', + 'account_areYouSure' => 'Are you sure you want to delete the account named ":name"?', + 'bill_areYouSure' => 'Are you sure you want to delete the bill named ":name"?', + 'rule_areYouSure' => 'Are you sure you want to delete the rule titled ":title"?', + 'ruleGroup_areYouSure' => 'Are you sure you want to delete the rule group titled ":title"?', + 'budget_areYouSure' => 'Are you sure you want to delete the budget named ":name"?', + 'category_areYouSure' => 'Are you sure you want to delete the category named ":name"?', + 'recurring_areYouSure' => 'Are you sure you want to delete the recurring transaction titled ":title"?', + 'currency_areYouSure' => 'Are you sure you want to delete the currency named ":name"?', + 'piggyBank_areYouSure' => 'Opravdu smazat pokladničku se jménem ":name"?', + 'journal_areYouSure' => 'Are you sure you want to delete the transaction described ":description"?', + 'mass_journal_are_you_sure' => 'Are you sure you want to delete these transactions?', + 'tag_areYouSure' => 'Are you sure you want to delete the tag ":tag"?', + 'journal_link_areYouSure' => 'Are you sure you want to delete the link between :source and :destination?', + 'linkType_areYouSure' => 'Are you sure you want to delete the link type ":name" (":inward" / ":outward")?', + 'permDeleteWarning' => 'Deleting stuff from Firefly III is permanent and cannot be undone.', + 'mass_make_selection' => 'You can still prevent items from being deleted by removing the checkbox.', + 'delete_all_permanently' => 'Označené trvale smazat', + 'update_all_journals' => 'Aktualizovat tyto transakce', + 'also_delete_transactions' => 'The only transaction connected to this account will be deleted as well.|All :count transactions connected to this account will be deleted as well.', + 'also_delete_connections' => 'The only transaction linked with this link type will lose this connection.|All :count transactions linked with this link type will lose their connection.', + 'also_delete_rules' => 'The only rule connected to this rule group will be deleted as well.|All :count rules connected to this rule group will be deleted as well.', + 'also_delete_piggyBanks' => 'The only piggy bank connected to this account will be deleted as well.|All :count piggy bank connected to this account will be deleted as well.', + 'bill_keep_transactions' => 'The only transaction connected to this bill will not be deleted.|All :count transactions connected to this bill will be spared deletion.', + 'budget_keep_transactions' => 'The only transaction connected to this budget will not be deleted.|All :count transactions connected to this budget will be spared deletion.', + 'category_keep_transactions' => 'The only transaction connected to this category will not be deleted.|All :count transactions connected to this category will be spared deletion.', + 'recurring_keep_transactions' => 'The only transaction created by this recurring transaction will not be deleted.|All :count transactions created by this recurring transaction will be spared deletion.', + 'tag_keep_transactions' => 'The only transaction connected to this tag will not be deleted.|All :count transactions connected to this tag will be spared deletion.', + 'check_for_updates' => 'Zjistit dostupnost případných aktualizací', + + 'email' => 'E-mailová adresa', + 'password' => 'Heslo', + 'password_confirmation' => 'Heslo (zopakování)', + 'blocked' => 'Je blokován?', + 'blocked_code' => 'Důvod blokování', + 'login_name' => 'Login', + + // import + 'apply_rules' => 'Uplatnit pravidla', + 'artist' => 'Umělec', + 'album' => 'Album', + 'song' => 'Skladba', + + + // admin + 'domain' => 'Doména', + 'single_user_mode' => 'Vypnout možnost registrace uživatelů', + 'is_demo_site' => 'Je demostránka', + + // import + 'import_file' => 'Importovat soubor', + 'configuration_file' => 'Soubor s nastaveními', + 'import_file_type' => 'Typ souboru k importu', + 'csv_comma' => 'Čárka (,)', + 'csv_semicolon' => 'Středník (;)', + 'csv_tab' => 'Tabulátor (neviditelný)', + 'csv_delimiter' => 'Oddělovač kolonek v CSV', + 'csv_import_account' => 'Výchozí účet pro import', + 'csv_config' => 'Nastavení CSV importu', + 'client_id' => 'Identif. klienta', + 'service_secret' => 'Service secret', + 'app_secret' => 'App secret', + 'app_id' => 'Identif. aplikace', + 'secret' => 'Secret', + 'public_key' => 'Veřejná část klíče', + 'country_code' => 'Kód země', + 'provider_code' => 'Banka nebo poskytovatel dat', + 'fints_url' => 'URL adresa FinTS API', + 'fints_port' => 'Port', + 'fints_bank_code' => 'Kód banky', + 'fints_username' => 'Uživatelské jméno', + 'fints_password' => 'PIN kód / heslo', + 'fints_account' => 'FinTS účet', + 'local_account' => 'Účet Firefly III', + 'from_date' => 'Od data', + 'to_date' => 'Do data', + + + 'due_date' => 'Datum splatnosti', + 'payment_date' => 'Datum zaplacení', + 'invoice_date' => 'Datum vystavení', + 'internal_reference' => 'Interní reference', + 'inward' => 'Inward description', + 'outward' => 'Outward description', + 'rule_group_id' => 'Rule group', + 'transaction_description' => 'Popis transakce', + 'first_date' => 'První datum', + 'transaction_type' => 'Typ transakce', + 'repeat_until' => 'Opakovat do data', + 'recurring_description' => 'Recurring transaction description', + 'repetition_type' => 'Typ opakování', + 'foreign_currency_id' => 'Zahraniční měna', + 'repetition_end' => 'Opakování končí', + 'repetitions' => 'Opakování', + 'calendar' => 'Kalendář', + 'weekend' => 'Víkend', + 'client_secret' => 'Client secret', + + 'withdrawal_destination_id' => 'Destination account', + 'deposit_source_id' => 'Source account', + +]; diff --git a/resources/lang/cs_CZ/import.php b/resources/lang/cs_CZ/import.php new file mode 100644 index 0000000000..c4c52b8cfa --- /dev/null +++ b/resources/lang/cs_CZ/import.php @@ -0,0 +1,316 @@ +. + */ + +declare(strict_types=1); + +return [ + // ALL breadcrumbs and subtitles: + 'index_breadcrumb' => 'Importovat data do Firefly III', + 'prerequisites_breadcrumb_fake' => 'Prerequisites for the fake import provider', + 'prerequisites_breadcrumb_spectre' => 'Prerequisites for Spectre', + 'prerequisites_breadcrumb_bunq' => 'Prerequisites for bunq', + 'prerequisites_breadcrumb_ynab' => 'Prerequisites for YNAB', + 'job_configuration_breadcrumb' => 'Nastavení pro „:key“', + 'job_status_breadcrumb' => 'Stav importu pro „:key“', + 'disabled_for_demo_user' => 'v ukázce vypnuté', + + // index page: + 'general_index_intro' => 'Welcome to Firefly III\'s import routine. There are a few ways of importing data into Firefly III, displayed here as buttons.', + + // import provider strings (index): + 'button_fake' => 'Simulovat import', + 'button_file' => 'Importovat soubor', + 'button_bunq' => 'Importovat z bunq', + 'button_spectre' => 'Importovat pomocí Spectre', + 'button_plaid' => 'Importovat pomocí Plaid', + 'button_yodlee' => 'Importovat pomocí Yodlee', + 'button_quovo' => 'Importovat pomocí Quovo', + 'button_ynab' => 'Importovat z You Need A Budget', + 'button_fints' => 'Importovat pomocí FinTS', + + + // prerequisites box (index) + 'need_prereq_title' => 'Import prerequisites', + 'need_prereq_intro' => 'Some import methods need your attention before they can be used. For example, they might require special API keys or application secrets. You can configure them here. The icon indicates if these prerequisites have been met.', + 'do_prereq_fake' => 'Prerequisites for the fake provider', + 'do_prereq_file' => 'Prerequisites for file imports', + 'do_prereq_bunq' => 'Předpoklady pro importy z bunq', + 'do_prereq_spectre' => 'Předpoklady pro importy z Spectre', + 'do_prereq_plaid' => 'Předpoklady pro importy z Plaid', + 'do_prereq_yodlee' => 'Předpoklady pro importy z Yodlee', + 'do_prereq_quovo' => 'Předpoklady pro importy z Quovo', + 'do_prereq_ynab' => 'Předpoklady pro importy z YNAB', + + // prerequisites: + 'prereq_fake_title' => 'Prerequisites for an import from the fake import provider', + 'prereq_fake_text' => 'This fake provider requires a fake API key. It must be 32 characters long. You can use this one: 123456789012345678901234567890AA', + 'prereq_spectre_title' => 'Prerequisites for an import using the Spectre API', + 'prereq_spectre_text' => 'In order to import data using the Spectre API (v4), you must provide Firefly III with two secret values. They can be found on the secrets page.', + 'prereq_spectre_pub' => 'Likewise, the Spectre API needs to know the public key you see below. Without it, it will not recognize you. Please enter this public key on your secrets page.', + 'prereq_bunq_title' => 'Předpoklady pro import z bunq', + 'prereq_bunq_text' => 'In order to import from bunq, you need to obtain an API key. You can do this through the app. Please note that the import function for bunq is in BETA. It has only been tested against the sandbox API.', + 'prereq_bunq_ip' => 'bunq requires your externally facing IP address. Firefly III has tried to fill this in using the ipify service. Make sure this IP address is correct, or the import will fail.', + 'prereq_ynab_title' => 'Předpoklady pro import z YNAB', + 'prereq_ynab_text' => 'In order to be able to download transactions from YNAB, please create a new application on your Developer Settings Page and enter the client ID and secret on this page.', + 'prereq_ynab_redirect' => 'To complete the configuration, enter the following URL at the Developer Settings Page under the "Redirect URI(s)".', + 'callback_not_tls' => 'Firefly III zjistilo následující URI adresu zpětného volání. Zdá se, že váš server není nastaven tak, aby přijímal TLS připojení (https). YNAB tuto URI nepřijme. Můžete pokračovat v importu (protože Firefly III se může mýlit), ale mějte to na paměti.', + // prerequisites success messages: + 'prerequisites_saved_for_fake' => 'Atrapa API klíče úspěšně uložena!', + 'prerequisites_saved_for_spectre' => 'Identif. aplikace a heslo uloženo!', + 'prerequisites_saved_for_bunq' => 'API klíč a IP adresa uložena!', + 'prerequisites_saved_for_ynab' => 'Identifikátor YNAB klienta a heslo uloženo!', + + // job configuration: + 'job_config_apply_rules_title' => 'Nastavení úlohy – uplatnit vaše pravidla?', + 'job_config_apply_rules_text' => 'Po spuštění atrapy poskytovatele je možné na transakce uplatnit pravidla. To ale prodlouží dobu importu.', + 'job_config_input' => 'Vaše zadání', + // job configuration for the fake provider: + 'job_config_fake_artist_title' => 'Zadejte název skupiny', + 'job_config_fake_artist_text' => 'Mnoho importních rutin má několik kroků nastavení, kterými je třeba projít. V případě atrapy poskytovatele importu je třeba odpovědět na některé podivné otázky. V tomto případě pokračujte zadáním „David Bowie“.', + 'job_config_fake_song_title' => 'Zadejte název skladby', + 'job_config_fake_song_text' => 'Pro pokračování v atrapě importu zmiňte skladbu „Golden years2“.', + 'job_config_fake_album_title' => 'Zadejte název alba', + 'job_config_fake_album_text' => 'Some import routines require extra data halfway through the import. In the case of the fake import provider, you must answer some weird questions. Enter "Station to station" to continue.', + // job configuration form the file provider + 'job_config_file_upload_title' => 'Nastavení importu (1/4) – nahrajte svůj soubor', + 'job_config_file_upload_text' => 'Tato rutina vám pomůže importovat soubory z vaší banky do Firefly III. ', + 'job_config_file_upload_help' => 'Vyberte soubor. Ověřte, že obsah souboru je ve znakové sadě UTF-8.', + 'job_config_file_upload_config_help' => 'If you have previously imported data into Firefly III, you may have a configuration file, which will pre-set configuration values for you. For some banks, other users have kindly provided their configuration file', + 'job_config_file_upload_type_help' => 'Vyberte typ souboru, který budete nahrávat', + 'job_config_file_upload_submit' => 'Nahrát soubory', + 'import_file_type_csv' => 'CSV (středníkem oddělované hodnoty)', + 'import_file_type_ofx' => 'OFX', + 'file_not_utf8' => 'The file you have uploaded is not encoded as UTF-8 or ASCII. Firefly III cannot handle such files. Please use Notepad++ or Sublime to convert your file to UTF-8.', + 'job_config_uc_title' => 'Nastavení importu (2/4) – základní nastavení souboru', + 'job_config_uc_text' => 'Aby byl možný správný import, ověřte níže uvedené volby.', + 'job_config_uc_header_help' => 'Check this box if the first row of your CSV file are the column titles.', + 'job_config_uc_date_help' => 'Date time format in your file. Follow the format as this page indicates. The default value will parse dates that look like this: :dateExample.', + 'job_config_uc_delimiter_help' => 'Choose the field delimiter that is used in your input file. If not sure, comma is the safest option.', + 'job_config_uc_account_help' => 'If your file does NOT contain information about your asset account(s), use this dropdown to select to which account the transactions in the file belong to.', + 'job_config_uc_apply_rules_title' => 'Uplatnit pravidla', + 'job_config_uc_apply_rules_text' => 'Applies your rules to every imported transaction. Note that this slows the import significantly.', + 'job_config_uc_specifics_title' => 'Předvolby pro konkrétní banku', + 'job_config_uc_specifics_txt' => 'Some banks deliver badly formatted files. Firefly III can fix those automatically. If your bank delivers such files but it\'s not listed here, please open an issue on GitHub.', + 'job_config_uc_submit' => 'Pokračovat', + 'invalid_import_account' => 'You have selected an invalid account to import into.', + 'import_liability_select' => 'Závazek', + // job configuration for Spectre: + 'job_config_spectre_login_title' => 'Zvolte své přihlášení', + 'job_config_spectre_login_text' => 'Firefly III has found :count existing login(s) in your Spectre account. Which one would you like to use to import from?', + 'spectre_login_status_active' => 'Aktivní', + 'spectre_login_status_inactive' => 'Neaktivní', + 'spectre_login_status_disabled' => 'Vypnuto', + 'spectre_login_new_login' => 'Login with another bank, or one of these banks with different credentials.', + 'job_config_spectre_accounts_title' => 'Vybrat účty ze kterých importovat', + 'job_config_spectre_accounts_text' => 'You have selected ":name" (:country). You have :count account(s) available from this provider. Please select the Firefly III asset account(s) where the transactions from these accounts should be stored. Remember, in order to import data both the Firefly III account and the ":name"-account must have the same currency.', + 'spectre_do_not_import' => '(neimportovat)', + 'spectre_no_mapping' => 'It seems you have not selected any accounts to import from.', + 'imported_from_account' => 'Importováno z „:account“', + 'spectre_account_with_number' => 'Účet :number', + 'job_config_spectre_apply_rules' => 'Uplatnit pravidla', + 'job_config_spectre_apply_rules_text' => 'By default, your rules will be applied to the transactions created during this import routine. If you do not want this to happen, deselect this checkbox.', + + // job configuration for bunq: + 'job_config_bunq_accounts_title' => 'bunq účty', + 'job_config_bunq_accounts_text' => 'These are the accounts associated with your bunq account. Please select the accounts from which you want to import, and in which account the transactions must be imported.', + 'bunq_no_mapping' => 'It seems you have not selected any accounts.', + 'should_download_config' => 'You should download the configuration file for this job. This will make future imports way easier.', + 'share_config_file' => 'If you have imported data from a public bank, you should share your configuration file so it will be easy for other users to import their data. Sharing your configuration file will not expose your financial details.', + 'job_config_bunq_apply_rules' => 'Uplatnit pravidla', + 'job_config_bunq_apply_rules_text' => 'By default, your rules will be applied to the transactions created during this import routine. If you do not want this to happen, deselect this checkbox.', + 'bunq_savings_goal' => 'Savings goal: :amount (:percentage%)', + 'bunq_account_status_CANCELLED' => 'Zrušený bunq účet', + + 'ynab_account_closed' => 'Účet je uzavřen!', + 'ynab_account_deleted' => 'Účet je smazán!', + 'ynab_account_type_savings' => 'spořicí účet', + 'ynab_account_type_checking' => 'checking account', + 'ynab_account_type_cash' => 'hotovostní účet', + 'ynab_account_type_creditCard' => 'kreditní karta', + 'ynab_account_type_lineOfCredit' => 'řádek úvěru', + 'ynab_account_type_otherAsset' => 'other asset account', + 'ynab_account_type_otherLiability' => 'ostatní závazky', + 'ynab_account_type_payPal' => 'Paypal', + 'ynab_account_type_merchantAccount' => 'merchant account', + 'ynab_account_type_investmentAccount' => 'investiční účet', + 'ynab_account_type_mortgage' => 'hypotéka', + 'ynab_do_not_import' => '(neimportovat)', + 'job_config_ynab_apply_rules' => 'Uplatnit pravidla', + 'job_config_ynab_apply_rules_text' => 'By default, your rules will be applied to the transactions created during this import routine. If you do not want this to happen, deselect this checkbox.', + + // job configuration for YNAB: + 'job_config_ynab_select_budgets' => 'Vyberte svůj rozpočet', + 'job_config_ynab_select_budgets_text' => 'You have :count budgets stored at YNAB. Please select the one from which Firefly III will import the transactions.', + 'job_config_ynab_no_budgets' => 'There are no budgets available to be imported from.', + 'ynab_no_mapping' => 'It seems you have not selected any accounts to import from.', + 'job_config_ynab_bad_currency' => 'You cannot import from the following budget(s), because you do not have accounts with the same currency as these budgets.', + 'job_config_ynab_accounts_title' => 'Vyberte účty', + 'job_config_ynab_accounts_text' => 'You have the following accounts available in this budget. Please select from which accounts you want to import, and where the transactions should be stored.', + + + // keys from "extra" array: + 'spectre_extra_key_iban' => 'IBAN', + 'spectre_extra_key_swift' => 'SWIFT', + 'spectre_extra_key_status' => 'Stav', + 'spectre_extra_key_card_type' => 'Typ karty', + 'spectre_extra_key_account_name' => 'Název účtu', + 'spectre_extra_key_client_name' => 'Jméno zákazníka', + 'spectre_extra_key_account_number' => 'Číslo účtu', + 'spectre_extra_key_blocked_amount' => 'Blokovaná částka', + 'spectre_extra_key_available_amount' => 'Částka k dispozici', + 'spectre_extra_key_credit_limit' => 'Credit limit', + 'spectre_extra_key_interest_rate' => 'Úroková sazba', + 'spectre_extra_key_expiry_date' => 'Datum skončení platnosti', + 'spectre_extra_key_open_date' => 'Open date', + 'spectre_extra_key_current_time' => 'Aktuální čas', + 'spectre_extra_key_current_date' => 'Aktuální datum', + 'spectre_extra_key_cards' => 'Karty', + 'spectre_extra_key_units' => 'Jednotky', + 'spectre_extra_key_unit_price' => 'Jednotková cena', + 'spectre_extra_key_transactions_count' => 'Počet transakcí', + + //job configuration for finTS + 'fints_connection_failed' => 'An error occurred while trying to connecting to your bank. Please make sure that all the data you entered is correct. Original error message: :originalError', + + 'job_config_fints_url_help' => 'Např. https://banking-dkb.s-fints-pt-dkb.de/fints30', + 'job_config_fints_username_help' => 'Pro mnohé banky je toto číslo vašeho účtu.', + 'job_config_fints_port_help' => 'Výchozí port je 443.', + 'job_config_fints_account_help' => 'Choose the bank account for which you want to import transactions.', + 'job_config_local_account_help' => 'Choose the Firefly III account corresponding to your bank account chosen above.', + // specifics: + 'specific_ing_name' => 'ING NL', + 'specific_ing_descr' => 'Vytvořit lepší popisy v exportu ING', + 'specific_sns_name' => 'SNS / Volksbank NL', + 'specific_sns_descr' => 'Trim quotes from SNS / Volksbank export files', + 'specific_abn_name' => 'ABN AMRO NL', + 'specific_abn_descr' => 'Fixes potential problems with ABN AMRO files', + 'specific_rabo_name' => 'Rabobank NL', + 'specific_rabo_descr' => 'Fixes potential problems with Rabobank files', + 'specific_pres_name' => 'President\'s Choice Financial CA', + 'specific_pres_descr' => 'Fixes potential problems with PC files', + 'specific_belfius_name' => 'Belfius BE', + 'specific_belfius_descr' => 'Fixes potential problems with Belfius files', + 'specific_ingbelgium_name' => 'ING BE', + 'specific_ingbelgium_descr' => 'Fixes potential problems with ING Belgium files', + // job configuration for file provider (stage: roles) + 'job_config_roles_title' => 'Import setup (3/4) - Define each column\'s role', + 'job_config_roles_text' => 'Each column in your CSV file contains certain data. Please indicate what kind of data the importer should expect. The option to "map" data means that you will link each entry found in the column to a value in your database. An often mapped column is the column that contains the IBAN of the opposing account. That can be easily matched to IBAN\'s present in your database already.', + 'job_config_roles_submit' => 'Pokračovat', + 'job_config_roles_column_name' => 'Název sloupce', + 'job_config_roles_column_example' => 'Column example data', + 'job_config_roles_column_role' => 'Význam dat ve sloupci', + 'job_config_roles_do_map_value' => 'Mapovat tyto hodnoty', + 'job_config_roles_no_example' => 'Nejsou k dispozici žádná ukázková data', + 'job_config_roles_fa_warning' => 'If you mark a column as containing an amount in a foreign currency, you must also set the column that contains which currency it is.', + 'job_config_roles_rwarning' => 'At the very least, mark one column as the amount-column. It is advisable to also select a column for the description, date and the opposing account.', + 'job_config_roles_colum_count' => 'Sloupec', + // job config for the file provider (stage: mapping): + 'job_config_map_title' => 'Import setup (4/4) - Connect import data to Firefly III data', + 'job_config_map_text' => 'In the following tables, the left value shows you information found in your uploaded file. It is your task to map this value, if possible, to a value already present in your database. Firefly will stick to this mapping. If there is no value to map to, or you do not wish to map the specific value, select nothing.', + 'job_config_map_nothing' => 'There is no data present in your file that you can map to existing values. Please press "Start the import" to continue.', + 'job_config_field_value' => 'Hodnota v kolonce', + 'job_config_field_mapped' => 'Mapováno na', + 'map_do_not_map' => '(nemapovat)', + 'job_config_map_submit' => 'Zahájit import', + + + // import status page: + 'import_with_key' => 'Importovat s klíčem „:key“', + 'status_wait_title' => 'Vyčkejte…', + 'status_wait_text' => 'Toto okno za okamžik zmizí.', + 'status_running_title' => 'Import je spuštěn', + 'status_job_running' => 'Čekejte, import probíhá…', + 'status_job_storing' => 'Čekejte, ukládání dat…', + 'status_job_rules' => 'Čekejte, spouštění pravidel…', + 'status_fatal_title' => 'Fatální chyba', + 'status_fatal_text' => 'The import has suffered from an error it could not recover from. Apologies!', + 'status_fatal_more' => 'This (possibly very cryptic) error message is complemented by log files, which you can find on your hard drive, or in the Docker container where you run Firefly III from.', + 'status_finished_title' => 'Import dokončen', + 'status_finished_text' => 'Import byl dokončen.', + 'finished_with_errors' => 'There were some errors during the import. Please review them carefully.', + 'unknown_import_result' => 'Neznámý výsledek importu', + 'result_no_transactions' => 'No transactions have been imported. Perhaps they were all duplicates is simply no transactions where present to be imported. Perhaps the log files can tell you what happened. If you import data regularly, this is normal.', + 'result_one_transaction' => 'Exactly one transaction has been imported. It is stored under tag :tag where you can inspect it further.', + 'result_many_transactions' => 'Firefly III has imported :count transactions. They are stored under tag :tag where you can inspect them further.', + + + // general errors and warnings: + 'bad_job_status' => 'To access this page, your import job cannot have status ":status".', + + // column roles for CSV import: + 'column__ignore' => '(ignorovat tento sloupec)', + 'column_account-iban' => 'Asset account (IBAN)', + 'column_account-id' => 'Asset account ID (matching FF3)', + 'column_account-name' => 'Asset account (name)', + 'column_account-bic' => 'Asset account (BIC)', + 'column_amount' => 'Částka', + 'column_amount_foreign' => 'Amount (in foreign currency)', + 'column_amount_debit' => 'Amount (debit column)', + 'column_amount_credit' => 'Amount (credit column)', + 'column_amount_negated' => 'Amount (negated column)', + 'column_amount-comma-separated' => 'Amount (comma as decimal separator)', + 'column_bill-id' => 'Bill ID (matching FF3)', + 'column_bill-name' => 'Bill name', + 'column_budget-id' => 'Budget ID (matching FF3)', + 'column_budget-name' => 'Název rozpočtu', + 'column_category-id' => 'Category ID (matching FF3)', + 'column_category-name' => 'Název kategorie', + 'column_currency-code' => 'Kód měny (dle normy ISO 4217)', + 'column_foreign-currency-code' => 'Kód cizí měny (dle normy ISO 4217)', + 'column_currency-id' => 'Currency ID (matching FF3)', + 'column_currency-name' => 'Currency name (matching FF3)', + 'column_currency-symbol' => 'Currency symbol (matching FF3)', + 'column_date-interest' => 'Datum výpočtu úroku', + 'column_date-book' => 'Datum zaúčtování transakce', + 'column_date-process' => 'Datum zpracování transakce', + 'column_date-transaction' => 'Datum', + 'column_date-due' => 'Splatnost transakce', + 'column_date-payment' => 'Datum platby transakce', + 'column_date-invoice' => 'Datum vystavení transakce', + 'column_description' => 'Popis', + 'column_opposing-iban' => 'Opposing account (IBAN)', + 'column_opposing-bic' => 'Opposing account (BIC)', + 'column_opposing-id' => 'Opposing account ID (matching FF3)', + 'column_external-id' => 'Externí identif.', + 'column_opposing-name' => 'Účet protistrany (název)', + 'column_rabo-debit-credit' => 'Rabobank specific debit/credit indicator', + 'column_ing-debit-credit' => 'ING specific debit/credit indicator', + 'column_generic-debit-credit' => 'Generic bank debit/credit indicator', + 'column_sepa_ct_id' => 'SEPA end-to-end Identifier', + 'column_sepa_ct_op' => 'SEPA Opposing Account Identifier', + 'column_sepa_db' => 'SEPA Mandate Identifier', + 'column_sepa_cc' => 'SEPA Clearing Code', + 'column_sepa_ci' => 'SEPA Creditor Identifier', + 'column_sepa_ep' => 'SEPA External Purpose', + 'column_sepa_country' => 'SEPA Country Code', + 'column_sepa_batch_id' => 'SEPA Batch ID', + 'column_tags-comma' => 'Štítky (oddělované čárkou)', + 'column_tags-space' => 'Štítky (oddělované mezerou)', + 'column_account-number' => 'Účet aktiv (číslo účtu)', + 'column_opposing-number' => 'Opposing account (account number)', + 'column_note' => 'Poznámky', + 'column_internal-reference' => 'Interní reference', + + // error message + 'duplicate_row' => 'Row #:row (":description") could not be imported. It already exists.', + +]; diff --git a/resources/lang/cs_CZ/intro.php b/resources/lang/cs_CZ/intro.php new file mode 100644 index 0000000000..0ae9255b93 --- /dev/null +++ b/resources/lang/cs_CZ/intro.php @@ -0,0 +1,160 @@ +. + */ + +declare(strict_types=1); + +return [ + // index + 'index_intro' => 'Vítejte na titulní stránce Firefly III. Věnujte čas projití se tímto úvodem, abyste se dozvěděli, jak Firefly III funguje.', + 'index_accounts-chart' => 'Tento graf zobrazuje stávající zůstatky vašich majetkových účtů. Jaké účty se zde mají zobrazovat lze nastavit v předvolbách.', + 'index_box_out_holder' => 'Tato malá oblast a ty další vedle něho podávají rychlý přehled vaší finanční situace.', + 'index_help' => 'Pokud budete potřebovat nápovědu ke stránce nebo formuláři, klikněte na toto tlačítko.', + 'index_outro' => 'Většina stránek Firefly III začíná krátkou prohlídkou, jako je tato. Obraťte se na mně, pokud máte dotazy nebo komentáře. Ať poslouží!', + 'index_sidebar-toggle' => 'Nabídku pod touto ikonou použijte pro vytváření nových transakcí, účtů a ostatní věcí.', + 'index_cash_account' => 'These are the accounts created so far. You can use the cash account to track cash expenses but it\'s not mandatory of course.', + + // transactions (withdrawal) + 'transactions_create_withdrawal_source' => 'Select your favorite asset account or liability from this dropdown.', + 'transactions_create_withdrawal_destination' => 'Select an expense account here. Leave it empty if you want to make a cash expense.', + 'transactions_create_withdrawal_foreign_currency' => 'Use this field to set a foreign currency and amount.', + 'transactions_create_withdrawal_more_meta' => 'Plenty of other meta data you set in these fields.', + 'transactions_create_withdrawal_split_add' => 'If you want to split a transaction, add more splits with this button', + + // transactions (deposit) + 'transactions_create_deposit_source' => 'Select or type the payee in this auto-completing dropdown/textbox. Leave it empty if you want to make a cash deposit.', + 'transactions_create_deposit_destination' => 'Select an asset or liability account here.', + 'transactions_create_deposit_foreign_currency' => 'Use this field to set a foreign currency and amount.', + 'transactions_create_deposit_more_meta' => 'Plenty of other meta data you set in these fields.', + 'transactions_create_deposit_split_add' => 'If you want to split a transaction, add more splits with this button', + + // transactions (transfer) + 'transactions_create_transfer_source' => 'Select the source asset account here.', + 'transactions_create_transfer_destination' => 'Select the destination asset account here.', + 'transactions_create_transfer_foreign_currency' => 'Use this field to set a foreign currency and amount.', + 'transactions_create_transfer_more_meta' => 'Plenty of other meta data you set in these fields.', + 'transactions_create_transfer_split_add' => 'If you want to split a transaction, add more splits with this button', + + // create account: + 'accounts_create_iban' => 'Zadejte u svých účtů platný IBAN identifikátor. To by v budoucnu mohlo velmi ulehčit import dat.', + 'accounts_create_asset_opening_balance' => 'Majetkové účty mohou mít „počáteční zůstatek“, označující začátek historie tohoto účtu ve Firefly III.', + 'accounts_create_asset_currency' => 'Firefly III podporuje vícero měn. Majetkové účty mají jednu hlavní měnu, kterou je třeba nastavit zde.', + 'accounts_create_asset_virtual' => 'Někdy se může hodit dát svému účtu virtuální zůstatek: extra částku, vždy přičítanou nebo odečítanou od stávajícího zůstatku.', + + // budgets index + 'budgets_index_intro' => 'Rozpočty slouží ke správě vašich financí a tvoří jednu z hlavních funkcí Firefly III.', + 'budgets_index_set_budget' => 'Nastavte celkový rozpočet pro každé z období a Firefly III vám sdělí, pokud jste vyčerpali všechny dostupné peníze.', + 'budgets_index_see_expenses_bar' => 'Utracené peníze budou zvolna plnit tento pruh.', + 'budgets_index_navigate_periods' => 'Procházejte obdobími a jednoduše nastavujte rozpočty dopředu.', + 'budgets_index_new_budget' => 'Vytvářejte nové rozpočty, jak uznáte za vhodné.', + 'budgets_index_list_of_budgets' => 'Použijte tuto tabulku k nastavení částek pro každý rozpočet a zjistěte, jak na tom jste.', + 'budgets_index_outro' => 'Pokud se chcete dozvědět více o tvorbě rozpočtů, klikněte na ikonu nápovědy v pravém horním rohu.', + + // reports (index) + 'reports_index_intro' => 'Pomocí těchto přehledů získáte podrobné informace o svých financích.', + 'reports_index_inputReportType' => 'Vyberte typ přehledu. Podívejte se na stránky nápovědy a zjistěte, co vám každý přehled ukazuje.', + 'reports_index_inputAccountsSelect' => 'Můžete vynechávat nebo zahrnovat majetkové účty, jak potřebujete.', + 'reports_index_inputDateRange' => 'Vybrané časové období je zcela na vás: od jednoho dne do deseti let.', + 'reports_index_extra-options-box' => 'Podle toho, jaký výkaz jste vybrali, je zde možné vybrat další filtry a volby. Při změně typu výkazu sledujte tuto oblast.', + + // reports (reports) + 'reports_report_default_intro' => 'Tento výkaz vám podá rychlý a podrobný přehled vašich financí. Pokud chcete vidět něco jiného, neváhejte se na mne obrátit!', + 'reports_report_audit_intro' => 'Tento výkaz vám podá podrobný vhled do vašich majetkových účtů.', + 'reports_report_audit_optionsBox' => 'Pomocí těchto zaškrtávacích kolonek zobrazujte nebo skrývejte sloupce, které vás (ne)zajímají.', + + 'reports_report_category_intro' => 'Tato sestava vám podá vhled do jedné nebo více kategorií.', + 'reports_report_category_pieCharts' => 'Tyto grafy vám podají vhled do výdajů a příjmů pro jednotlivé kategorie nebo účty.', + 'reports_report_category_incomeAndExpensesChart' => 'Tento graf zobrazuje vaše náklady a příjmy v jednotlivých kategoriích.', + + 'reports_report_tag_intro' => 'Tato sestava vám podává vhled do jednoho nebo více štítků.', + 'reports_report_tag_pieCharts' => 'Tyto grafy vám podávají vhled do nákladů a příjmů pro jednotlivé štítky, účty, kategorie nebo rozpočty.', + 'reports_report_tag_incomeAndExpensesChart' => 'Tento graf zobrazuje vaše výdaje a příjmy pro každý štítek.', + + 'reports_report_budget_intro' => 'Tato sestava vám dává vhled do jednoho nebo více rozpočtů.', + 'reports_report_budget_pieCharts' => 'Tyto grafy vám podají vhled do výdajů pro jednotlivé rozpočty nebo účty.', + 'reports_report_budget_incomeAndExpensesChart' => 'Tento graf zobrazuje vaše výdaje v jednotlivých rozpočtech.', + + // create transaction + 'transactions_create_switch_box' => 'Pomocí těchto tlačítek můžete rychle přepínat typ transakce, kterou chcete uložit.', + 'transactions_create_ffInput_category' => 'Do této kolonky si můžete napsat, co chcete. Budou navrhovány dříve vytvořené kategorie.', + 'transactions_create_withdrawal_ffInput_budget' => 'Propojte svůj výběr s rozpočtem a získáte lepší kontrolu nad financemi.', + 'transactions_create_withdrawal_currency_dropdown_amount' => 'Tuto rozbalovací nabídku použijte pokud je váš výběr v jiné měně.', + 'transactions_create_deposit_currency_dropdown_amount' => 'Tuto rozbalovací nabídku použijte, pokud je váš vklad v jiné měně.', + 'transactions_create_transfer_ffInput_piggy_bank_id' => 'Vyberte pokladničku a propojte tento převod se svými úsporami.', + + // piggy banks index: + 'piggy-banks_index_saved' => 'Tato kolonka zobrazuje, kolik jste naspořili v každé z pokladniček.', + 'piggy-banks_index_button' => 'Vedle tohoto ukazatele postupu se nachází dvě tlačítka (+ a -) pro přidání nebo odebrání peněz z každé z pokladniček.', + 'piggy-banks_index_accountStatus' => 'Pro každý majetkový účet s alespoň jednou pokladničkou je v této tabulce vypsán stav.', + + // create piggy + 'piggy-banks_create_name' => 'Co je vašim cílem? Nová pohovka, fotoaparát, rezerva pro nečekané výdaje?', + 'piggy-banks_create_date' => 'Pro pokladničku je možné nastavit cílové datum nebo termín.', + + // show piggy + 'piggy-banks_show_piggyChart' => 'Tento graf bude zobrazovat historii vaší pokladničky.', + 'piggy-banks_show_piggyDetails' => 'Nějaké podrobnosti o vaší pokladničce', + 'piggy-banks_show_piggyEvents' => 'Jsou zde uvedeny také všechny přírůstky i odebrání.', + + // bill index + 'bills_index_rules' => 'Zde vidíte která pravidla budou zkontrolována pokud je tento účet zasažen', + 'bills_index_paid_in_period' => 'Tato kolonka označuje, kdy byla účtenka/faktura naposledy zaplacena.', + 'bills_index_expected_in_period' => 'Tato kolonka u každé faktury označuje zda a kdy je očekávána další.', + + // show bill + 'bills_show_billInfo' => 'Tato tabulka zobrazuje některé obecné informace o této faktuře.', + 'bills_show_billButtons' => 'Toto tlačítko slouží k opětovnému prohledání starých transakcí, takže bude hledána shoda s touto účtenkou.', + 'bills_show_billChart' => 'Tento graf zobrazuje transakce spojené s touto fakturou.', + + // create bill + 'bills_create_intro' => 'Faktury používejte pro sledování částek, které máte v každém z období zaplatit. Jedná se výdaje jako nájem, pojištění nebo splátky hypotéky.', + 'bills_create_name' => 'Zadejte výstižný název, jako „Nájem“ nebo „Životní pojištění“.', + //'bills_create_match' => 'To match transactions, use terms from those transactions or the expense account involved. All words must match.', + 'bills_create_amount_min_holder' => 'Vyberte nejnižší a nejvyšší částku pro tuto fakturu.', + 'bills_create_repeat_freq_holder' => 'Většina plateb se opakuje měsíčně, ale je zde možné nastavit i jinou frekvenci.', + 'bills_create_skip_holder' => 'Pokud se platba opakuje každé dva týdny, kolonka „přeskočit“ by měla být nastavená na „1“, aby byl vynechán každý druhý týden.', + + // rules index + 'rules_index_intro' => 'Firefly III umožňuje spravovat pravidla, která budou automaticky uplatňována na všechny transakce které vytvoříte nebo upravíte.', + 'rules_index_new_rule_group' => 'Pro snazší zprávu je pravidla možné kombinovat ve skupinách.', + 'rules_index_new_rule' => 'Vytvořte tolik pravidel, kolik chcete.', + 'rules_index_prio_buttons' => 'Seřaďte je tak, jak se vám to hodí.', + 'rules_index_test_buttons' => 'Pravidla je možné vyzkoušet nebo uplatnit na existující transakce.', + 'rules_index_rule-triggers' => 'Pravidla mají „spouštěče“ a „akce“. Jejich pořadí je možné měnit jejich přetahováním.', + 'rules_index_outro' => 'Podívejte se do nápovědy (ikona otazníku v pravém horním rohu)!', + + // create rule: + 'rules_create_mandatory' => 'Zvolte výstižný název a nastavte, kdy má být pravidlo spuštěno.', + 'rules_create_ruletriggerholder' => 'Přidejte tolik spouštěčů, kolik potřebujete, ale pamatujte, že aby byla spuštěná jakákoli akce je třeba, aby byly splněny podmínky VŠECH nastavených spouštěčů.', + 'rules_create_test_rule_triggers' => 'Toto tlačítko slouží ke zobrazení transakcí, které odpovídají pravidlu.', + 'rules_create_actions' => 'Nastavte tolik akcí, kolik chcete.', + + // preferences + 'preferences_index_tabs' => 'Další volby jsou k dispozici v kartách.', + + // currencies + 'currencies_index_intro' => 'Firefly III podporuje více měn, které můžete měnit na této stránce.', + 'currencies_index_default' => 'Firefly III má jednu výchozí měnu.', + 'currencies_index_buttons' => 'Tato tlačítka slouží pro změnu výchozí měny nebo pro zapnutí dalších měn.', + + // create currency + 'currencies_create_code' => 'Tento kód by měl být v souladu s normou ISO (vyhledejte si pro novou měnu).', +]; diff --git a/resources/lang/cs_CZ/list.php b/resources/lang/cs_CZ/list.php new file mode 100644 index 0000000000..91eab21ef7 --- /dev/null +++ b/resources/lang/cs_CZ/list.php @@ -0,0 +1,136 @@ +. + */ + +declare(strict_types=1); + +return [ + 'buttons' => 'Tlačítka', + 'icon' => 'Ikona', + 'id' => 'ID', + 'create_date' => 'Vytvořeno', + 'update_date' => 'Aktualizováno', + 'updated_at' => 'Aktualizováno', + 'balance_before' => 'Zůstatek před', + 'balance_after' => 'Zůstatek po', + 'name' => 'Jméno', + 'role' => 'Role', + 'currentBalance' => 'Aktuální zůstatek', + 'linked_to_rules' => 'Příslušná pravidla', + 'active' => 'Aktivní?', + 'transaction_type' => 'Type', + 'lastActivity' => 'Poslední aktivita', + 'balanceDiff' => 'Rozdíl zůstatku', + 'matchesOn' => 'Matched on', + 'account_type' => 'Typ účtu', + 'created_at' => 'Vytvořeno', + 'account' => 'Účet', + 'matchingAmount' => 'Částka', + 'split_number' => '# rozdělení', + 'destination' => 'Cíl', + 'source' => 'Zdroj', + 'next_expected_match' => 'Next expected match', + 'automatch' => 'Automatické hledání shody?', + 'repeat_freq' => 'Repeats', + 'description' => 'Popis', + 'amount' => 'Částka', + 'internal_reference' => 'Internal reference', + 'date' => 'Datum', + 'interest_date' => 'Úrokové datum', + 'book_date' => 'Book date', + 'process_date' => 'Datum zpracování', + 'due_date' => 'Datum splatnosti', + 'payment_date' => 'Datum platby', + 'invoice_date' => 'Datum vystavení', + 'interal_reference' => 'Interní reference', + 'notes' => 'Poznámky', + 'from' => 'Od', + 'piggy_bank' => 'Pokladnička', + 'to' => 'To', + 'budget' => 'Rozpočet', + 'category' => 'Kategorie', + 'bill' => 'Účet', + 'withdrawal' => 'Výběr', + 'deposit' => 'Vklad', + 'transfer' => 'Převod', + 'type' => 'Typ', + 'completed' => 'Dokončeno', + 'iban' => 'IBAN', + 'paid_current_period' => 'Zaplaceno v tomto období', + 'email' => 'Email', + 'registered_at' => 'Registered at', + 'is_blocked' => 'Je blokován', + 'is_admin' => 'Je admin', + 'has_two_factor' => 'Má 2FA', + 'blocked_code' => 'Block code', + 'source_account' => 'Zdrojový účet', + 'destination_account' => 'Cílový účet', + 'accounts_count' => 'Počet účtů', + 'journals_count' => 'Počet transakcí', + 'attachments_count' => 'Počet příloh', + 'bills_count' => 'Počet účtů', + 'categories_count' => 'Počet kategorií', + 'import_jobs_count' => 'Počet úloh importu', + 'budget_count' => 'Počet rozpočtů', + 'rule_and_groups_count' => 'Počet pravidel a skupin pravidel', + 'tags_count' => 'Počet štítků', + 'tags' => 'Štítky', + 'inward' => 'Inward description', + 'outward' => 'Outward description', + 'number_of_transactions' => 'Počet transakcí', + 'total_amount' => 'Celková částka', + 'sum' => 'Součet', + 'sum_excluding_transfers' => 'Součet (bez převodů)', + 'sum_withdrawals' => 'Součet výběrů', + 'sum_deposits' => 'Součet vkladů', + 'sum_transfers' => 'Součet převodů', + 'reconcile' => 'Reconcile', + 'account_on_spectre' => 'Účet (Spectre)', + 'account_on_ynab' => 'Účet (YNAB)', + 'do_import' => 'Importovat z tohoto účtu', + 'sepa_ct_id' => 'SEPA End to End Identifier', + 'sepa_ct_op' => 'SEPA Opposing Account Identifier', + 'sepa_db' => 'SEPA Mandate Identifier', + 'sepa_country' => 'SEPA Country', + 'sepa_cc' => 'SEPA Clearing Code', + 'sepa_ep' => 'SEPA External Purpose', + 'sepa_ci' => 'SEPA Creditor Identifier', + 'sepa_batch_id' => 'SEPA Batch ID', + 'external_id' => 'External ID', + 'account_at_bunq' => 'Account with bunq', + 'file_name' => 'Název souboru', + 'file_size' => 'Velikost souboru', + 'file_type' => 'Typ souboru', + 'attached_to' => 'Připojeno k', + 'file_exists' => 'Soubor existuje', + 'spectre_bank' => 'Banka', + 'spectre_last_use' => 'Minulé přihlášení', + 'spectre_status' => 'Stav', + 'bunq_payment_id' => 'bunq payment ID', + 'repetitions' => 'Opakování', + 'title' => 'Title', + 'transaction_s' => 'Transakce', + 'field' => 'Kolonka', + 'value' => 'Hodnota', + 'interest' => 'Úrok', + 'interest_period' => 'úrokové období', + 'liability_type' => 'Typ závazku', +]; diff --git a/resources/lang/cs_CZ/pagination.php b/resources/lang/cs_CZ/pagination.php new file mode 100644 index 0000000000..65a73fbe48 --- /dev/null +++ b/resources/lang/cs_CZ/pagination.php @@ -0,0 +1,28 @@ +. + */ + +declare(strict_types=1); + +return [ + 'previous' => '« Předchozí', + 'next' => 'Následující »', +]; diff --git a/resources/lang/cs_CZ/passwords.php b/resources/lang/cs_CZ/passwords.php new file mode 100644 index 0000000000..fbe3601a01 --- /dev/null +++ b/resources/lang/cs_CZ/passwords.php @@ -0,0 +1,32 @@ +. + */ + +declare(strict_types=1); + +return [ + 'password' => 'Je třeba, aby délka hesla byla alespoň šest znaků a jeho potvrzení bylo zadáno stejně.', + 'user' => 'Nedaří se najít uživatele, který by měl tuto e-mailovou adresu.', + 'token' => 'Požadavek na obnovení hesla není platný.', + 'sent' => 'Na e-mail vám byl odeslán odkaz na obnovení hesla!', + 'reset' => 'Vaše heslo bylo resetováno!', + 'blocked' => 'Dobrý pokus.', +]; diff --git a/resources/lang/cs_CZ/validation.php b/resources/lang/cs_CZ/validation.php new file mode 100644 index 0000000000..b89e05f164 --- /dev/null +++ b/resources/lang/cs_CZ/validation.php @@ -0,0 +1,195 @@ +. + */ + +declare(strict_types=1); + +return [ + 'iban' => 'Toto není platný IBAN.', + 'zero_or_more' => 'Hodnota nemůže být záporná.', + 'date_or_time' => 'Je třeba, aby hodnota byla platné datum nebo čas (ve formátu dle normy ISO 8601).', + 'source_equals_destination' => 'Zdrojový účet je zároveň i cílový.', + 'unique_account_number_for_user' => 'Zdá se, že toto číslo účtu se již používá.', + 'unique_iban_for_user' => 'Vypadá to, že tento IBAN kód se již používá.', + 'deleted_user' => 'Z bezpečnostních důvodů se nemůžete registrovat pomocí této emailové adresy.', + 'rule_trigger_value' => 'Tato hodnota není platná pro označený spouštěč.', + 'rule_action_value' => 'Tato hodnota je neplatná pro vybranou akci.', + 'file_already_attached' => 'Nahraný soubor ":name" je již připojen k tomuto objektu.', + 'file_attached' => 'Soubor „:name“ úspěšně nahrán.', + 'must_exist' => 'Identifikátor v kolonce :attribute v databázi neexistuje.', + 'all_accounts_equal' => 'Je třeba, aby všechny účty v této kolonce byly stejné.', + 'group_title_mandatory' => 'Pokud je zde více než jedna transakce, je název skupiny třeba vyplnit.', + 'transaction_types_equal' => 'Je třeba, aby všechna rozdělení byla stejného typu.', + 'invalid_transaction_type' => 'Neplatný typ transakce.', + 'invalid_selection' => 'Váš výběr je neplatný.', + 'belongs_user' => 'Tato hodnota není platná pro toto pole.', + 'at_least_one_transaction' => 'Potřebujete alespoň jednu transakci.', + 'at_least_one_repetition' => 'Potřebujete alespoň jedno opakování.', + 'require_repeat_until' => 'Require either a number of repetitions, or an end date (repeat_until). Not both.', + 'require_currency_info' => 'Obsah tohoto pole je neplatný bez informace o měně.', + 'require_currency_amount' => 'The content of this field is invalid without foreign amount information.', + 'equal_description' => 'Popis transakce nesmí být stejný jako globální popis.', + 'file_invalid_mime' => 'Soubor ":name" je typu ":mime", který není schválen pro nahrání.', + 'file_too_large' => 'Soubor ":name" je příliš velký.', + 'belongs_to_user' => 'Hodnota :attribute není známa.', + 'accepted' => 'Je potřeba potvrdit :attribute.', + 'bic' => 'Toto není platný BIC.', + 'at_least_one_trigger' => 'Je třeba, aby pravidlo mělo alespoň jeden spouštěč.', + 'at_least_one_action' => 'Pravidlo musí obsahovat alespoň jednu akci.', + 'base64' => 'Data nejsou v platném base64 kódování.', + 'model_id_invalid' => 'Zdá se, že dané ID je neplatné pro tento model.', + 'more' => ':attribute musí být větší než nula.', + 'less' => ':attribute musí být menší než 10.000.000', + 'active_url' => ':attribute není platná adresa URL.', + 'after' => ':attribute nemůže být dříve než :date.', + 'alpha' => ':attribute může obsahovat pouze písmena.', + 'alpha_dash' => ':attribute může obsahovat pouze písmena, čísla a pomlčky.', + 'alpha_num' => ':attribute může obsahovat pouze písmena a čísla.', + 'array' => ':attribute musí být pole.', + 'unique_for_user' => 'Položka s tímto :attribute již existuje.', + 'before' => ':attribute nemůže být později než :date.', + 'unique_object_for_user' => 'Tento název je již používán.', + 'unique_account_for_user' => 'Tento název účtu je již používán.', + 'between.numeric' => ':attribute musí být v rozmezí :min a :max.', + 'between.file' => ':attribute musí být v rozmezí :min a :max kilobajtů.', + 'between.string' => ':attribute musí mít délku v rozmezí :min a :max znaků.', + 'between.array' => ':attribute musí mít mezi :min a :max položkami.', + 'boolean' => ':attribute musí mít hodnotu pravda nebo nepravda.', + 'confirmed' => 'Potvrzení :attribute se neshoduje.', + 'date' => ':attribute není platným datem.', + 'date_format' => ':attribute neodpovídá formátu :format.', + 'different' => ':attribute a :other se musí lišit.', + 'digits' => ':attribute musí obsahovat :digits číslic.', + 'digits_between' => ':attribute musí být v rozmezí :min a :max číslic.', + 'email' => ':attribute musí být platná e-mailová adresa.', + 'filled' => 'Pole :attribute nesmí být prázdné.', + 'exists' => 'Vybraný :attribute je neplatný.', + 'image' => 'Je třeba, aby :attribute byl obrázek.', + 'in' => 'Vybraný :attribute není platný.', + 'integer' => 'Je třeba, aby :attribute byl celé číslo.', + 'ip' => 'Je třeba, aby :attribute byla platná IP adresa.', + 'json' => 'Je třeba, aby :attribute byl platný JSON řetězec.', + 'max.numeric' => ':attribute nemůže být vyšší než :max.', + 'max.file' => 'The :attribute may not be greater than :max kilobytes.', + 'max.string' => 'The :attribute may not be greater than :max characters.', + 'max.array' => 'The :attribute may not have more than :max items.', + 'mimes' => 'The :attribute must be a file of type: :values.', + 'min.numeric' => 'Je třeba, aby :attribute bylo alespoň :min.', + 'lte.numeric' => 'Je třeba, aby :attribute byl nižší nebo roven :value.', + 'min.file' => 'Je třeba, aby :attribute byl alespoň :min kilobajtů.', + 'min.string' => 'Je třeba, aby :attribute bylo alespoň :min znaků dlouhé.', + 'min.array' => 'The :attribute must have at least :min items.', + 'not_in' => 'Vybraný :attribute není platný.', + 'numeric' => 'Je třeba, aby :attribute byl číslo.', + 'numeric_native' => 'Je třeba, aby částka v hlavní měně bylo číslo.', + 'numeric_destination' => 'Je třeba, aby cílová částka bylo číslo.', + 'numeric_source' => 'Je třeba, aby zdrojová částka bylo číslo.', + 'regex' => 'Formát :attribute není platný.', + 'required' => 'Kolonku :attribute je třeba vyplnit.', + 'required_if' => 'The :attribute field is required when :other is :value.', + 'required_unless' => 'The :attribute field is required unless :other is in :values.', + 'required_with' => 'The :attribute field is required when :values is present.', + 'required_with_all' => 'The :attribute field is required when :values is present.', + 'required_without' => 'The :attribute field is required when :values is not present.', + 'required_without_all' => 'The :attribute field is required when none of :values are present.', + 'same' => 'The :attribute and :other must match.', + 'size.numeric' => 'Je třeba, aby :attribute byl :size.', + 'amount_min_over_max' => 'The minimum amount cannot be larger than the maximum amount.', + 'size.file' => 'The :attribute must be :size kilobytes.', + 'size.string' => 'The :attribute must be :size characters.', + 'size.array' => 'The :attribute must contain :size items.', + 'unique' => 'The :attribute has already been taken.', + 'string' => 'Je třeba, aby :attribute byl řetězec.', + 'url' => 'Formát :attribute není platný.', + 'timezone' => 'Je třeba, aby :attribute byla platná zóna.', + '2fa_code' => 'Kolonka :attribute není platná.', + 'dimensions' => ':attribute nemá platné rozměry obrázku.', + 'distinct' => 'Kolonka :attribute má duplicitní hodnotu.', + 'file' => 'Je třeba, aby :attribute byl soubor.', + 'in_array' => 'The :attribute field does not exist in :other.', + 'present' => 'Je třeba, aby kolonka :attribute byla přítomna.', + 'amount_zero' => 'Celková částka nemůže být nula.', + 'unique_piggy_bank_for_user' => 'Je třeba, aby se názvy pokladniček neopakovaly.', + 'secure_password' => 'Toto není bezpečné heslo. Zkuste jiné. Více se dozvíte na http://bit.ly/FF3-password-security', + 'valid_recurrence_rep_type' => 'Neplatný typ opakování pro opakované transakce.', + 'valid_recurrence_rep_moment' => 'Invalid repetition moment for this type of repetition.', + 'invalid_account_info' => 'Neplatná informace o účtu.', + 'attributes' => [ + 'email' => 'e-mailová adresa', + 'description' => 'popis', + 'amount' => 'částka', + 'name' => 'název', + 'piggy_bank_id' => 'ID pokladničky', + 'targetamount' => 'cílová částka', + 'opening_balance_date' => 'opening balance date', + 'opening_balance' => 'opening balance', + 'match' => 'shoda', + 'amount_min' => 'minimální částka', + 'amount_max' => 'maximální částka', + 'title' => 'název', + 'tag' => 'štítek', + 'transaction_description' => 'popis transakce', + 'rule-action-value.1' => 'hodnota č. 1 akce pravidla', + 'rule-action-value.2' => 'hodnota č. 2 akce pravidla', + 'rule-action-value.3' => 'hodnota č. 3 akce pravidla', + 'rule-action-value.4' => 'hodnota akce pravidla č. 4', + 'rule-action-value.5' => 'hodnota č. 5 akce pravidla', + 'rule-action.1' => 'Akce pravidla č. 1', + 'rule-action.2' => 'Akce pravidla č. 2', + 'rule-action.3' => 'Akce pravidla č. 3', + 'rule-action.4' => 'akce pravidla č. 4', + 'rule-action.5' => 'rule action #5', + 'rule-trigger-value.1' => 'rule trigger value #1', + 'rule-trigger-value.2' => 'rule trigger value #2', + 'rule-trigger-value.3' => 'rule trigger value #3', + 'rule-trigger-value.4' => 'rule trigger value #4', + 'rule-trigger-value.5' => 'rule trigger value #5', + 'rule-trigger.1' => 'spouštěč pravidla č. 1', + 'rule-trigger.2' => 'spouštěč pravidla č. 2', + 'rule-trigger.3' => 'spouštěč pravidla č. 3', + 'rule-trigger.4' => 'spouštěč pravidla č. 4', + 'rule-trigger.5' => 'spouštěč pravidla č. 5', + ], + + // validation of accounts: + 'withdrawal_source_need_data' => 'Need to get a valid source account ID and/or valid source account name to continue.', + 'withdrawal_source_bad_data' => 'Could not find a valid source account when searching for ID ":id" or name ":name".', + 'withdrawal_dest_need_data' => 'Need to get a valid destination account ID and/or valid destination account name to continue.', + 'withdrawal_dest_bad_data' => 'Could not find a valid destination account when searching for ID ":id" or name ":name".', + + 'deposit_source_need_data' => 'Need to get a valid source account ID and/or valid source account name to continue.', + 'deposit_source_bad_data' => 'Could not find a valid source account when searching for ID ":id" or name ":name".', + 'deposit_dest_need_data' => 'Need to get a valid destination account ID and/or valid destination account name to continue.', + 'deposit_dest_bad_data' => 'Could not find a valid destination account when searching for ID ":id" or name ":name".', + + 'transfer_source_need_data' => 'Need to get a valid source account ID and/or valid source account name to continue.', + 'transfer_source_bad_data' => 'Could not find a valid source account when searching for ID ":id" or name ":name".', + 'transfer_dest_need_data' => 'Need to get a valid destination account ID and/or valid destination account name to continue.', + 'transfer_dest_bad_data' => 'Could not find a valid destination account when searching for ID ":id" or name ":name".', + 'need_id_in_edit' => 'Each split must have transaction_journal_id (either valid ID or 0).', + + 'ob_source_need_data' => 'Need to get a valid source account ID and/or valid source account name to continue.', + 'ob_dest_need_data' => 'Need to get a valid destination account ID and/or valid destination account name to continue.', + 'ob_dest_bad_data' => 'Could not find a valid destination account when searching for ID ":id" or name ":name".', + + 'generic_invalid_source' => 'You can\'t use this account as the source account.', + 'generic_invalid_destination' => 'You can\'t use this account as the destination account.', +]; diff --git a/resources/lang/de_DE/breadcrumbs.php b/resources/lang/de_DE/breadcrumbs.php index 5d26b57cd9..bf8d1ccbb6 100644 --- a/resources/lang/de_DE/breadcrumbs.php +++ b/resources/lang/de_DE/breadcrumbs.php @@ -23,35 +23,37 @@ declare(strict_types=1); return [ - 'home' => 'Startseite', - 'edit_currency' => 'Währung „:name” bearbeiten', - 'delete_currency' => 'Währung „:name” löschen', - 'newPiggyBank' => 'Neues Sparschwein erstellen', - 'edit_piggyBank' => 'Sparschwein „:name” bearbeiten', - 'preferences' => 'Einstellungen', - 'profile' => 'Profil', - 'changePassword' => 'Passwort ändern', - 'change_email' => 'E-Mail Adresse ändern', - 'bills' => 'Rechnungen', - 'newBill' => 'Neue Rechnung', - 'edit_bill' => 'Rechnung „:name” bearbeiten', - 'delete_bill' => 'Rechnung „:name” löschen', - 'reports' => 'Berichte', - 'search_result' => 'Suchergebnisse für ":query"', - 'withdrawal_list' => 'Ausgaben', - 'deposit_list' => 'Umsatz, Einkommen und Einzahlungen', - 'transfer_list' => 'Umbuchungen', - 'transfers_list' => 'Umbuchungen', - 'reconciliation_list' => 'Kontenabgleiche', - 'create_withdrawal' => 'Neue Ausgabe erstellen', - 'create_deposit' => 'Neue Einnahme erstellen', - 'create_transfer' => 'Neue Umbuchung erstellen', - 'edit_journal' => 'Transaktion ":description" bearbeiten', - 'edit_reconciliation' => '„:description” bearbeiten', - 'delete_journal' => 'Transaktion ":description" löschen', - 'tags' => 'Schlagwörter', - 'createTag' => 'Neues Schlagwort erstellen', - 'edit_tag' => 'Schlagwort „:tag” bearbeiten', - 'delete_tag' => 'Schlagwort „:tag” löschen', - 'delete_journal_link' => 'Transaktionsverknüpfung löschen', + 'home' => 'Startseite', + 'edit_currency' => 'Währung „:name” bearbeiten', + 'delete_currency' => 'Währung „:name” löschen', + 'newPiggyBank' => 'Neues Sparschwein erstellen', + 'edit_piggyBank' => 'Sparschwein „:name” bearbeiten', + 'preferences' => 'Einstellungen', + 'profile' => 'Profil', + 'changePassword' => 'Passwort ändern', + 'change_email' => 'E-Mail Adresse ändern', + 'bills' => 'Rechnungen', + 'newBill' => 'Neue Rechnung', + 'edit_bill' => 'Rechnung „:name” bearbeiten', + 'delete_bill' => 'Rechnung „:name” löschen', + 'reports' => 'Berichte', + 'search_result' => 'Suchergebnisse für ":query"', + 'withdrawal_list' => 'Ausgaben', + 'Withdrawal_list' => 'Aufwendungen', + 'deposit_list' => 'Umsatz, Einkommen und Einzahlungen', + 'transfer_list' => 'Umbuchungen', + 'transfers_list' => 'Umbuchungen', + 'reconciliation_list' => 'Kontenabgleiche', + 'create_withdrawal' => 'Neue Ausgabe erstellen', + 'create_deposit' => 'Neue Einnahme erstellen', + 'create_transfer' => 'Neue Umbuchung erstellen', + 'create_new_transaction' => 'Neue Buchung erstellen', + 'edit_journal' => 'Transaktion ":description" bearbeiten', + 'edit_reconciliation' => '„:description” bearbeiten', + 'delete_journal' => 'Transaktion ":description" löschen', + 'tags' => 'Schlagwörter', + 'createTag' => 'Neues Schlagwort erstellen', + 'edit_tag' => 'Schlagwort „:tag” bearbeiten', + 'delete_tag' => 'Schlagwort „:tag” löschen', + 'delete_journal_link' => 'Transaktionsverknüpfung löschen', ]; diff --git a/resources/lang/de_DE/firefly.php b/resources/lang/de_DE/firefly.php index d08d3a6738..c539a3c61e 100644 --- a/resources/lang/de_DE/firefly.php +++ b/resources/lang/de_DE/firefly.php @@ -33,7 +33,7 @@ return [ 'last_seven_days' => 'Letzte sieben Tage', 'last_thirty_days' => 'Letzte 30 Tage', 'welcomeBack' => 'Was ist gerade los?', - 'welcome_back' => 'What\'s playing?', + 'welcome_back' => 'Was ist gerade los?', 'everything' => 'Alle', 'today' => 'Heute', 'customRange' => 'Individueller Bereich', @@ -54,6 +54,7 @@ return [ 'create_new_stuff' => 'Erstelle neue Dinge', 'new_withdrawal' => 'Neue Ausgabe', 'create_new_transaction' => 'Neue Buchung erstellen', + 'new_transaction' => 'Neue Buchung', 'go_to_asset_accounts' => 'Bestandskonten anzeigen', 'go_to_budgets' => 'Budgets anzeigen', 'go_to_categories' => 'Kategorien anzeigen', @@ -77,33 +78,37 @@ return [ 'flash_error' => 'Fehler!', 'flash_info_multiple' => 'Es gibt eine Nachricht | Es gibt :count Nachrichten', 'flash_error_multiple' => 'Es gibt einen Fehler|Es gibt :count Fehler', - 'net_worth' => 'Vermögen', + 'net_worth' => 'Eigenkapital', 'route_has_no_help' => 'Es gibt keinen Hilfetext für diese Seite.', 'help_for_this_page' => 'Hilfe zu dieser Seite', 'no_help_could_be_found' => 'Es wurde kein Hilfetext gefunden.', 'no_help_title' => 'Es tut uns Leid, aber ein Fehler ist aufgetreten.', - 'two_factor_welcome' => 'Hallo :user!', - 'two_factor_enter_code' => 'Um fortzufahren geben Sie bitte ihren Zwei-Faktor-Authentifizierungscode ein. Ihre Anwendung kann diesen für Sie generieren.', - 'two_factor_code_here' => 'Code hier eingeben', - 'two_factor_title' => 'Zwei-Faktor-Authentifizierung', - 'authenticate' => 'Authentifizieren', - 'two_factor_forgot_title' => 'Zwei-Faktor-Authentifizierung verloren', - 'two_factor_forgot' => 'Ich kann keine 2FA-Codes generieren.', - 'two_factor_lost_header' => 'Haben Sie ihre Zwei-Faktor-Authentifizierung verloren?', - 'two_factor_lost_intro' => 'Leider ist dies etwas, dass sie nicht über die Weboberfläche zurücksetzen können. Sie haben zwei Möglichkeiten.', - 'two_factor_lost_fix_self' => 'Wenn Sie Ihre eigene Instanz von Firefly III betreiben, überprüfen Sie die Logdatei unter storage/logs für weitere Anweisungen.', - 'two_factor_lost_fix_owner' => 'Ansonsten, mailen Sie dem Inhaber der Website, :site_owner und bitten Sie ihn, Ihre Zwei-Faktor Authentifizierung zurückzusetzen.', - 'warning_much_data' => ':days Tage Daten können eine Weile zum Laden benötigen.', - 'registered' => 'Sie haben sich erfolgreich registriert!', - 'Default asset account' => 'Standard-Bestandskonto', - 'no_budget_pointer' => 'Sie scheinen noch keine Budgets festgelegt zu haben. Sie sollten einige davon auf der Seite Budgets anlegen. Budgets können Ihnen dabei helfen, den Überblick über die Ausgaben zu behalten.', - 'Savings account' => 'Sparkonto', - 'Credit card' => 'Kreditkarte', - 'source_accounts' => 'Ausgangskonto', - 'destination_accounts' => 'Zielkonto', - 'user_id_is' => 'Ihre Benutzerkennung ist :user', - 'field_supports_markdown' => 'Dieses Feld unterstützt Markdown.', - 'need_more_help' => 'Wenn Sie Hilfe beim Bedienen von Firefly III brauchen, erstellen Sie ein Ticket auf Github.', + 'two_factor_welcome' => 'Hallo!', + 'two_factor_enter_code' => 'Um fortzufahren geben Sie bitte ihren Zwei-Faktor-Authentifizierungscode ein. Ihre Anwendung kann diesen für Sie generieren.', + 'two_factor_code_here' => 'Code hier eingeben', + 'two_factor_title' => 'Zwei-Faktor-Authentifizierung', + 'authenticate' => 'Authentifizieren', + 'two_factor_forgot_title' => 'Zwei-Faktor-Authentifizierung verloren', + 'two_factor_forgot' => 'Ich kann keine 2FA-Codes generieren.', + 'two_factor_lost_header' => 'Haben Sie ihre Zwei-Faktor-Authentifizierung verloren?', + 'two_factor_lost_intro' => 'Wenn Sie auch Ihre Sicherungsschlüssel verloren haben, haben Sie Pech. Dies ist nichts, was Sie über die Weboberfläche beheben können. Sie haben jedoch zwei Möglichkeiten.', + 'two_factor_lost_fix_self' => 'Wenn Sie Ihre eigene Instanz von Firefly III ausführen, überprüfen Sie die Protokolle in storage/logs auf Anweisungen, oder führen Sie docker logs <container_id> aus, um die Anweisungen zu lesen (aktualisieren Sie diese Seite).', + 'two_factor_lost_fix_owner' => 'Ansonsten, mailen Sie dem Inhaber der Website, :site_owner und bitten Sie ihn, Ihre Zwei-Faktor Authentifizierung zurückzusetzen.', + 'mfa_backup_code' => 'Sie haben sich mit einem Sicherungsschlüssel bei Firefly III angemeldet. Dieser kann nun nicht mehr verwendet werden, also streichen Sie ihn aus Ihrer Liste.', + 'pref_two_factor_new_backup_codes' => 'Neue Sicherungsschlüssel abrufen', + 'pref_two_factor_backup_code_count' => 'Sie haben :count Sicherungsschlüssel.', + '2fa_i_have_them' => 'Wurde gespeichert!', + 'warning_much_data' => ':days Tage Daten können eine Weile zum Laden benötigen.', + 'registered' => 'Sie haben sich erfolgreich registriert!', + 'Default asset account' => 'Standard-Bestandskonto', + 'no_budget_pointer' => 'Sie scheinen noch keine Budgets festgelegt zu haben. Sie sollten einige davon auf der Seite Budgets anlegen. Budgets können Ihnen dabei helfen, den Überblick über die Ausgaben zu behalten.', + 'Savings account' => 'Sparkonto', + 'Credit card' => 'Kreditkarte', + 'source_accounts' => 'Ausgangskonto', + 'destination_accounts' => 'Zielkonto', + 'user_id_is' => 'Ihre Benutzerkennung ist :user', + 'field_supports_markdown' => 'Dieses Feld unterstützt Markdown.', + 'need_more_help' => 'Wenn Sie Hilfe beim Bedienen von Firefly III brauchen, erstellen Sie ein Ticket auf Github.', 'reenable_intro_text' => 'Sie können auch die Einführung wieder aktivieren.', 'intro_boxes_after_refresh' => 'Die Einführungsfelder werden wieder angezeigt, wenn Sie die Seite aktualisieren.', 'show_all_no_filter' => 'Alle Buchungen anzeigen, ohne diese nach Datum zu gruppieren.', @@ -220,21 +225,23 @@ return [ 'search_query' => 'Abfrage', 'search_found_transactions' => 'Firefly III hat :count Buchung(en) in :time Sekunden gefunden.', 'search_for_query' => 'Firefly III sucht nach Buchungen mit folgenden Wörtern im Namen: :query', - 'search_modifier_amount_is' => 'Betrag ist genau :value', - 'search_modifier_amount' => 'Betrag ist genau :value', - 'search_modifier_amount_max' => 'Betrag ist höchstens :value', - 'search_modifier_amount_min' => 'Betrag ist mindestens :value', - 'search_modifier_amount_less' => 'Betrag ist kleiner als :value', - 'search_modifier_amount_more' => 'Betrag ist größer als :value', - 'search_modifier_source' => 'Quellkonto ist :value', - 'search_modifier_destination' => 'Zielkonto ist :value', - 'search_modifier_category' => 'Kategorie ist :value', - 'search_modifier_budget' => 'Budget ist :value', - 'search_modifier_bill' => 'Rechnung ist :value', - 'search_modifier_type' => 'Buchungsart ist :value', - 'search_modifier_date' => 'Buchungsdatum ist :value', - 'search_modifier_date_before' => 'Buchungsdatum ist vor :value', - 'search_modifier_date_after' => 'Buchungsdatum ist nach :value', + 'search_modifier_amount_is' => 'Betrag ist genau :value', + 'search_modifier_amount' => 'Betrag ist genau :value', + 'search_modifier_amount_max' => 'Betrag ist höchstens :value', + 'search_modifier_amount_min' => 'Betrag ist mindestens :value', + 'search_modifier_amount_less' => 'Betrag ist kleiner als :value', + 'search_modifier_amount_more' => 'Betrag ist größer als :value', + 'search_modifier_source' => 'Quellkonto ist :value', + 'search_modifier_from' => 'Quellkonto ist :value', + 'search_modifier_destination' => 'Zielkonto ist :value', + 'search_modifier_to' => 'Zielkonto ist :value', + 'search_modifier_category' => 'Kategorie ist :value', + 'search_modifier_budget' => 'Budget ist :value', + 'search_modifier_bill' => 'Rechnung ist :value', + 'search_modifier_type' => 'Buchungsart ist :value', + 'search_modifier_date' => 'Buchungsdatum ist :value', + 'search_modifier_date_before' => 'Buchungsdatum ist vor :value', + 'search_modifier_date_after' => 'Buchungsdatum ist nach :value', 'search_modifier_on' => 'Buchungsdatum ist :value', 'search_modifier_before' => 'Buchungsdatum ist vor :value', 'search_modifier_after' => 'Buchungsdatum ist nach :value', @@ -257,33 +264,6 @@ return [ 'half-year' => 'halbjährlich', 'yearly' => 'jährlich', - // export data: - 'import_and_export' => 'Import und Export', - 'export_data' => 'Daten exportieren', - 'export_and_backup_data' => 'Daten exportieren', - 'export_data_intro' => 'Exporte können Sie verwenden um Ihre Daten in anderen Anwendungen zu importieren. Bitte beachten Sie, dass diese Dateien nicht als Backup gedacht sind. Sie enthalten nicht genügend Metadaten, um eine neue Firefly III-Installation vollständig wiederherzustellen. Wenn Sie Ihre Daten sichern möchten, sichern Sie bitte die Datenbank direkt.', - 'export_format' => 'Export-Format', - 'export_format_csv' => 'Durch Komma getrennte Werte (CSV-Datei)', - 'export_format_mt940' => 'MT940 kompatibles Format', - 'include_old_uploads_help' => 'Firefly III löscht keine originalen CSV-Dateien, welche zuvor importiert wurden. Sie können dem Export hinzugefügt werden.', - 'do_export' => 'Export', - 'export_status_never_started' => 'Der Export hat noch nicht begonnen', - 'export_status_make_exporter' => 'Export wird erstellt...', - 'export_status_collecting_journals' => 'Sammeln Ihrer Buchungen...', - 'export_status_collected_journals' => 'Alle Ihre Buchungen wurden zusammengetragen!', - 'export_status_converting_to_export_format' => 'Buchungen werden umgewandelt...', - 'export_status_converted_to_export_format' => 'Ihre Buchungen wurden umgewandelt!', - 'export_status_creating_journal_file' => 'Exportdatei wird erstellt...', - 'export_status_created_journal_file' => 'Exportdatei erstellt!', - 'export_status_collecting_attachments' => 'Sammeln aller Ihrer Anhänge...', - 'export_status_collected_attachments' => 'Alle Anhänge wurden zusammengetragen!', - 'export_status_collecting_old_uploads' => 'Sammeln aller bisherigen Uploads...', - 'export_status_collected_old_uploads' => 'Alle bisherigen Uploads wurden zusammengetragen!', - 'export_status_creating_zip_file' => 'Erstelle eine Zip-Datei...', - 'export_status_created_zip_file' => 'Zip-Datei erstellt!', - 'export_status_finished' => 'Export erfolgreich beendet! Yay!', - 'export_data_please_wait' => 'Bitte warten...', - // rules 'rules' => 'Regeln', 'rule_name' => 'Name der Regel', @@ -401,7 +381,7 @@ return [ 'rule_trigger_has_any_category' => 'Buchung hat eine (beliebige) Kategorie', 'rule_trigger_has_no_budget_choice' => 'Enthält kein Budget', 'rule_trigger_has_no_budget' => 'Buchung ohne Budget', - 'rule_trigger_has_any_budget_choice' => 'Enthält einen (beliebigen) Budget', + 'rule_trigger_has_any_budget_choice' => 'Enthält ein (beliebiges) Budget', 'rule_trigger_has_any_budget' => 'Buchung enthält ein (beliebiges) Budget', 'rule_trigger_has_no_tag_choice' => 'Enthält keine Schlagwörter', 'rule_trigger_has_no_tag' => 'Transaktion enthält keine Schlagwörter', @@ -455,7 +435,7 @@ return [ 'rule_action_set_notes' => 'Notizen auf „:action_value” setzen', 'rule_action_convert_deposit_choice' => 'Buchung in eine Einzahlung umwandeln', 'rule_action_convert_deposit' => 'Buchung von ":action_value" in eine Einzahlung umwandeln', - 'rule_action_convert_withdrawal_choice' => 'Buchung in eine Auszahlung umwandeln', + 'rule_action_convert_withdrawal_choice' => 'Buchung in eine Ausgabe umwandeln', 'rule_action_convert_withdrawal' => 'Buchung von ":action_value" in eine Auszahlung umwandeln', 'rule_action_convert_transfer_choice' => 'Buchung in eine Umbuchung umwandeln', 'rule_action_convert_transfer' => 'Buchung von ":action_value" in eine Umbuchung umwandeln', @@ -502,30 +482,33 @@ return [ 'pref_custom_fiscal_year_help' => 'In Ländern, in denen ein Geschäftsjahr nicht vom 1. Januar bis 31. Dezember dauert, können Sie diese Option ändern und Start / Ende des Geschäftsjahres angeben', 'pref_fiscal_year_start_label' => 'Startdatum des Geschäftsjahres', 'pref_two_factor_auth' => 'Zwei-Faktor-Authentifizierung', - 'pref_two_factor_auth_help' => 'Wenn Sie die Zwei-Faktor-Authentifizierung aktivieren, fügen Sie ihrem Benutzerkonto eine zusätzliche Sicherheitsebene hinzu. Sie loggen sich ein mit etwas das Sie wissen (ihrem Passwort) und etwas das Sie besitzen (einem Bestätigungscode). Bestätigungscodes werden von Anwendungen auf ihrem Smartphone, wie Authy oder Google Authenticator, generiert.', - 'pref_enable_two_factor_auth' => 'Aktiviere Zwei-Faktor-Authentifizierung', - 'pref_two_factor_auth_disabled' => 'Der Code für die Zwei-Faktor-Authentifizierung wurde gelöscht und gesperrt', - 'pref_two_factor_auth_remove_it' => 'Vergessen Sie nicht ihr Benutzerkonto aus ihrer Authentifizierungsapp zu entfernen!', - 'pref_two_factor_auth_code' => 'Code überprüfen', - 'pref_two_factor_auth_code_help' => 'Scannen Sie den QR-Code mit einer Anwendung wie Authy oder Google Authenticator auf ihrem Handy und geben Sie den generierten Code ein.', - 'pref_two_factor_auth_reset_code' => 'Verifizierungscode zurücksetzen', - 'pref_two_factor_auth_disable_2fa' => '2FA deaktivieren', - '2fa_use_secret_instead' => 'Wenn Sie den QR-Code nicht scannen können, verwenden Sie stattdessen diesen Schlüssel: :secret.', - 'pref_save_settings' => 'Einstellungen speichern', - 'saved_preferences' => 'Einstellungen gespeichert!', - 'preferences_general' => 'Allgemein', - 'preferences_frontpage' => 'Startbildschirm', - 'preferences_security' => 'Sicherheit', - 'preferences_layout' => 'Anordnung', - 'pref_home_show_deposits' => 'Einnahmen auf dem Startbildschirm anzeigen', - 'pref_home_show_deposits_info' => 'Der Startbildschirm zeigt schon Ihre Aufwandskonten an. Sollen zusätzlich Ihre Erlöskonten angezeigt werden?', - 'pref_home_do_show_deposits' => 'Ja, zeige sie an', - 'successful_count' => 'davon :count erfolgreich', - 'list_page_size_title' => 'Einträge pro Seite', - 'list_page_size_help' => 'Jede Liste von Elementen (Konten, Buchungen, usw.) zeigt höchstens so viele Elemente je Seite.', - 'list_page_size_label' => 'Einträge pro Seite', - 'between_dates' => '(:start und :end)', - 'pref_optional_fields_transaction' => 'Optionale Felder für Buchungen', + 'pref_two_factor_auth_help' => 'Wenn Sie die Zwei-Faktor-Authentifizierung aktivieren, fügen Sie ihrem Benutzerkonto eine zusätzliche Sicherheitsebene hinzu. Sie loggen sich ein mit etwas das Sie wissen (ihrem Passwort) und etwas das Sie besitzen (einem Bestätigungscode). Bestätigungscodes werden von Anwendungen auf ihrem Smartphone, wie Authy oder Google Authenticator, generiert.', + 'pref_enable_two_factor_auth' => 'Aktiviere Zwei-Faktor-Authentifizierung', + 'pref_two_factor_auth_disabled' => 'Der Code für die Zwei-Faktor-Authentifizierung wurde gelöscht und gesperrt', + 'pref_two_factor_auth_remove_it' => 'Vergessen Sie nicht ihr Benutzerkonto aus ihrer Authentifizierungsapp zu entfernen!', + 'pref_two_factor_auth_code' => 'Code überprüfen', + 'pref_two_factor_auth_code_help' => 'Scannen Sie den QR-Code mit einer Anwendung wie Authy oder Google Authenticator auf ihrem Handy und geben Sie den generierten Code ein.', + 'pref_two_factor_auth_reset_code' => 'Verifizierungscode zurücksetzen', + 'pref_two_factor_auth_disable_2fa' => '2FA deaktivieren', + '2fa_use_secret_instead' => 'Wenn Sie den QR-Code nicht einlesen können, können Sie stattdessen das Geheimnis verwenden: :secret.', + '2fa_backup_codes' => 'Speichern Sie diese Sicherungsschlüssel für den Zugriff, falls Sie Ihr Gerät verlieren sollten.', + '2fa_already_enabled' => 'Die 2-stufige Bestätigung ist bereits aktiviert.', + 'wrong_mfa_code' => 'Dieser MFA-Code ist ungültig.', + 'pref_save_settings' => 'Einstellungen speichern', + 'saved_preferences' => 'Einstellungen gespeichert!', + 'preferences_general' => 'Allgemein', + 'preferences_frontpage' => 'Startbildschirm', + 'preferences_security' => 'Sicherheit', + 'preferences_layout' => 'Anordnung', + 'pref_home_show_deposits' => 'Einnahmen auf dem Startbildschirm anzeigen', + 'pref_home_show_deposits_info' => 'Der Startbildschirm zeigt schon Ihre Aufwandskonten an. Sollen zusätzlich Ihre Erlöskonten angezeigt werden?', + 'pref_home_do_show_deposits' => 'Ja, zeige sie an', + 'successful_count' => 'davon :count erfolgreich', + 'list_page_size_title' => 'Einträge pro Seite', + 'list_page_size_help' => 'Jede Liste von Elementen (Konten, Buchungen, usw.) zeigt höchstens so viele Elemente je Seite.', + 'list_page_size_label' => 'Einträge pro Seite', + 'between_dates' => '(:start und :end)', + 'pref_optional_fields_transaction' => 'Optionale Felder für Buchungen', 'pref_optional_fields_transaction_help' => 'Wenn Sie eine Buchung anlegen sind standardmäßig nicht alle vorhandenen Felder aktiviert. Hier können Sie alle Felder aktivieren, die Sie gerne nutzen möchten. Alle Felder die deaktiviert sind, aber bereits ausgefüllt sind, werden unabhängig von ihren Einstellungen sichtbar sein.', 'optional_tj_date_fields' => 'Datumsfelder', 'optional_tj_business_fields' => 'Fachliche Felder', @@ -579,24 +562,25 @@ return [ 'login_with_new_email' => 'Sie können sich jetzt mit Ihrer neuen E-Mail-Adresse anmelden.', 'login_with_old_email' => 'Sie können sich jetzt wieder mit Ihrer alten E-Mail-Adresse anmelden.', 'login_provider_local_only' => 'Diese Aktion ist bei der Authentifizierung durch ":login_provider" nicht verfügbar.', - 'delete_local_info_only' => 'Dies wird nur lokale Firefly-III-Informationen löschen, da Sie durch ":login_provider" authentifiziert sind.', + 'delete_local_info_only' => 'Dies wird nur lokale Firefly-III-Informationen löschen, da Sie durch ":login_provider" authentifiziert sind.', // attachments - 'nr_of_attachments' => 'Ein Anhang |:count Anhänge', - 'attachments' => 'Anhänge', - 'edit_attachment' => 'Anhang „:name” bearbeiten', - 'update_attachment' => 'Anhang aktualisieren', - 'delete_attachment' => 'Anhang „:name” löschen', - 'attachment_deleted' => 'Anhang „:name” gelöscht', - 'attachment_updated' => 'Anhang „:name” aktualisiert', - 'upload_max_file_size' => 'Maximale Dateigröße: :size', - 'list_all_attachments' => 'Liste aller Anhänge', + 'nr_of_attachments' => 'Ein Anhang |:count Anhänge', + 'attachments' => 'Anhänge', + 'edit_attachment' => 'Anhang „:name” bearbeiten', + 'update_attachment' => 'Anhang aktualisieren', + 'delete_attachment' => 'Anhang „:name” löschen', + 'attachment_deleted' => 'Anhang „:name” gelöscht', + 'liabilities_deleted' => 'Verbindlichkeit „:name” gelöscht', + 'attachment_updated' => 'Anhang „:name” aktualisiert', + 'upload_max_file_size' => 'Maximale Dateigröße: :size', + 'list_all_attachments' => 'Liste aller Anhänge', // transaction index - 'title_expenses' => 'Ausgaben', - 'title_withdrawal' => 'Ausgaben', - 'title_revenue' => 'Einnahmen / Einkommen', - 'title_deposit' => 'Einnahmen / Einkommen', + 'title_expenses' => 'Ausgaben', + 'title_withdrawal' => 'Ausgaben', + 'title_revenue' => 'Einnahmen / Einkommen', + 'title_deposit' => 'Einnahmen / Einkommen', 'title_transfer' => 'Umbuchungen', 'title_transfers' => 'Umbuchungen', @@ -634,6 +618,9 @@ return [ 'converted_to_Transfer' => 'Die Buchung wurde in eine Umbuchung konvertiert', 'invalid_convert_selection' => 'Das von Ihnen ausgewählte Konto wird für diese Buchung bereits verwendet oder ist nicht vorhanden.', 'source_or_dest_invalid' => 'Die korrekten Buchungsdetails konnten nicht gefunden werden. Eine Konvertierung ist nicht möglich.', + 'convert_to_withdrawal' => 'In eine Ausgabe umwandeln', + 'convert_to_deposit' => 'In eine Einzahlung umwandeln', + 'convert_to_transfer' => 'In eine Umbuchungen umwandeln', // create new stuff: 'create_new_withdrawal' => 'Erstelle eine neue Ausgabe', @@ -695,7 +682,9 @@ return [ 'available_amount_indication' => 'Verwenden Sie diese Angaben, um einen Anhaltspunkt darüber zu erhalten, wie hoch Ihr komplettes Budget sein könnte.', 'suggested' => 'Vorgeschlagen', 'average_between' => 'Durchschnitt zwischen :start und :end', - 'over_budget_warn' => ' Normalerweise veranschlagen Sie ca. :amount pro Tag. Aktuell sind es aber :over_amount pro Tag.', + 'over_budget_warn' => 'Normalerweise kalkulieren Sie etwa :amount pro Tag. Diesmal ist es :over_amount pro Tag. Möchten Sie fortfahren?', + 'transferred_in' => 'Übertragen (eingehend)', + 'transferred_away' => 'Übertragen (ausgehend)', // bills: 'match_between_amounts' => 'Rechnung passt zu Transaktionen zwischen :low und :high.', @@ -779,6 +768,7 @@ return [ 'reconcile_options' => 'Kontenabgleich Optionen', 'reconcile_range' => 'Kontenabgleich-Bereich', 'start_reconcile' => 'Jetzt abgleichen', + 'cash_account_type' => 'Barzahlung', 'cash' => 'Bar', 'account_type' => 'Kontotyp', 'save_transactions_by_moving' => 'Speichern Sie diese Transaktion(en), indem Sie sie auf ein anderes Konto verschieben:', @@ -803,7 +793,9 @@ return [ 'reconcile_go_back' => 'Sie können dies jederzeit später bearbeiten oder löschen.', 'must_be_asset_account' => 'Sie können nur Bestandskonten abgleichen', 'reconciliation_stored' => 'Kontenabgleich gespeichert', - 'reconcilliation_transaction_title' => 'Ausgleich (:from zu :to)', + 'reconciliation_error' => 'Aufgrund eines Fehlers wurden die Buchungen als „Ausgeglichen” gekennzeichnet, aber die Korrektur wurde nicht gespeichert: :error.', + 'reconciliation_transaction_title' => 'Ausgleich (:from zu :to)', + 'sum_of_reconciliation' => 'Summe der Überleitungsrechnung', 'reconcile_this_account' => 'Dieses Konto abgleichen', 'confirm_reconciliation' => 'Kontenabgleich bestätigen', 'submitted_start_balance' => 'Übermitteltes Startguthaben', @@ -815,7 +807,7 @@ return [ 'interest_calc_daily' => 'Täglich', 'interest_calc_monthly' => 'Monatlich', 'interest_calc_yearly' => 'Jährlich', - 'initial_balance_account' => 'Eröffnungssaldo von :name', + 'initial_balance_account' => 'Anfangsguthaben von „:account”', // categories: 'new_category' => 'Neue Kategorie', @@ -847,6 +839,8 @@ return [ 'deleted_deposit' => 'Einnahme ":description" erfolgreich gelöscht', 'deleted_transfer' => 'Umbuchung ":description" erfolgreich gelöscht', 'stored_journal' => 'Neue Überweisung ":description" erfolgreich erstellt', + 'stored_journal_no_descr' => 'Ihre neue Buchung wurde erfolgreich erstellt', + 'updated_journal_no_descr' => 'Ihre Buchung wurde erfolgreich aktualisiert', 'select_transactions' => 'Buchungen auswählen', 'rule_group_select_transactions' => '„:title” auf Buchungen anwenden', 'rule_select_transactions' => '„:title” auf Buchungen anwenden', @@ -855,26 +849,33 @@ return [ 'mass_delete_journals' => 'Anzahl von Buchungen löschen', 'mass_edit_journals' => 'Anzahl von Buchungen bearbeiten', 'mass_bulk_journals' => 'Massenbearbeitung mehrerer Buchungen', - 'mass_bulk_journals_explain' => 'Wenn Sie Ihre Buchungen nicht einzeln mit der Massenbearbeitungsfunktion ändern möchten, können Sie diese in einem Schritt aktualisieren. Wählen Sie einfach in den untenstehenden Feldern die bevorzugte Kategorie, die Schlüsselwörter oder das zur Verfügung stehende Budget aus, und alle Umsätze in der Tabelle werden aktualisiert.', + 'mass_bulk_journals_explain' => 'Mit diesem Formular können Sie die Eigenschaften der unten aufgeführten Buchungen in einer einzigen umfassenden Aktualisierung ändern. Alle Buchungen in der Tabelle werden aktualisiert, wenn Sie die hier angezeigten Parameter ändern.', + 'part_of_split' => 'Diese Buchung ist Teil einer Splitbuchung. Wenn Sie nicht alle Aufteilungen ausgewählt haben, können Sie am Ende nur die Hälfte der Buchung ändern.', 'bulk_set_new_values' => 'Verwenden Sie die folgenden Felder, um neue Werte einzustellen. Wenn Sie sie leer lassen, werden sie für alle geleert. Beachten Sie auch, dass nur Ausgaben mit einem Budget versehen werden.', 'no_bulk_category' => 'Kategorie nicht aktualisieren', 'no_bulk_budget' => 'Budget nicht aktualisieren', - 'no_bulk_tags' => 'Schlagwörter nicht aktualisieren', - 'bulk_edit' => 'Massenbearbeitung', - 'cannot_edit_other_fields' => 'Andere Felder als die hier gezeigten können Sie nicht gleichzeitig bearbeitet werden, da es keinen Platz gibt, diese anzuzeigen. Bitte folgen Sie dem Link und bearbeiten Sie die Felder einzeln, wenn Sie diese bearbeiten möchten.', - 'no_budget' => '(kein Budget)', - 'no_budget_squared' => '(kein Budget)', - 'perm-delete-many' => 'Das Löschen von mehreren Elementen auf einmal kann sich störend auswirken. Bitte seien Sie vorsichtig.', - 'mass_deleted_transactions_success' => ':amount Überweisung(en) gelöscht.', - 'mass_edited_transactions_success' => ':amount Überweisung(en) aktualisiert', - 'opt_group_' => '(kein Kontotyp)', + 'no_bulk_tags' => 'Schlagwörter nicht aktualisieren', + 'mass_edit' => 'Ausgewählte einzeln bearbeiten', + 'bulk_edit' => 'Ausgewählte als Block bearbeiten', + 'mass_delete' => 'Ausgewählte löschen', + 'cannot_edit_other_fields' => 'Andere Felder als die hier gezeigten können Sie nicht gleichzeitig bearbeitet werden, da es keinen Platz gibt, diese anzuzeigen. Bitte folgen Sie dem Link und bearbeiten Sie die Felder einzeln, wenn Sie diese bearbeiten möchten.', + 'cannot_change_amount_reconciled' => 'Sie können den Betrag der abgeglichenen Buchungen nicht ändern.', + 'no_budget' => '(kein Budget)', + 'no_budget_squared' => '(kein Budget)', + 'perm-delete-many' => 'Das Löschen vieler Elemente auf einmal kann sehr problematisch sein. Bitte seien Sie vorsichtig. Sie können einen Teil einer Splitbuchung auf dieser Seite löschen, also seien Sie vorsichtig.', + 'mass_deleted_transactions_success' => ':amount Überweisung(en) gelöscht.', + 'mass_edited_transactions_success' => ':amount Überweisung(en) aktualisiert', + 'opt_group_' => '(kein Kontotyp)', 'opt_group_no_account_type' => '(kein Kontotyp)', 'opt_group_defaultAsset' => 'Standard-Anlagekonto', 'opt_group_savingAsset' => 'Sparkonten', 'opt_group_sharedAsset' => 'Gemeinsame Bestandskonten', 'opt_group_ccAsset' => 'Kreditkarten', 'opt_group_cashWalletAsset' => 'Geldbörsen', + 'opt_group_expense_account' => 'Aufwandskonten', + 'opt_group_revenue_account' => 'Erlöskonten', 'opt_group_l_Loan' => 'Verbindlichkeit: Darlehen', + 'opt_group_cash_account' => 'Geldkonto', 'opt_group_l_Debt' => 'Verbindlichkeit: Forderung', 'opt_group_l_Mortgage' => 'Verbindlichkeit: Hypothek', 'opt_group_l_Credit card' => 'Verbindlichkeit: Kreditkarte', @@ -973,7 +974,7 @@ return [ 'errors' => 'Fehler', 'debt_start_date' => 'Startdatum der Verschuldung', 'debt_start_amount' => 'Startbetrag der Verschuldung', - 'debt_start_amount_help' => 'Bitte geben Sie den ursprünglichen Betrag dieser Forderung als positive Zahl ein. Sie können auch den aktuellen Betrag eingeben. Stellen Sie sicher, dass Sie das untenstehende Datum so ändern, dass es mit dem Datum übereinstimmt.', + 'debt_start_amount_help' => 'Wenn Sie einen Betrag zu zahlen haben, ist es am besten, einen negativen Betrag einzugeben, da er Ihr Eigenkapital beeinflusst. Wenn Ihnen ein Betrag geschuldet wird, gilt das Gleiche. Auf den Hilfeseiten findest du weitere Informationen.', 'store_new_liabilities_account' => 'Neue Verbindlichkeit speichern', 'edit_liabilities_account' => 'Verbindlichkeit „:name” bearbeiten', @@ -1077,7 +1078,7 @@ return [ 'select_expense_revenue' => 'Aufwands-/Erlöskonto auswählen', 'multi_currency_report_sum' => 'Da diese Liste Konten mit mehreren Währungen enthält, ergeben die Summe(n), die angezeigt werden, möglicherweise keinen Sinn. Der Bericht wird immer auf Ihre Standardwährung zurückgesetzt.', 'sum_in_default_currency' => 'Der Betrag wird immer in Ihrer Standardwährung angegeben.', - 'net_filtered_prefs' => 'Dieses Diagramm wird niemals Konten einbeziehen, die die Option "Im Nettovermögen enthalten" deaktiviert haben.', + 'net_filtered_prefs' => 'Dieses Diagramm wird niemals Konten einbeziehen, die die Option "Im Eigenkapital enthalten" deaktiviert haben.', // charts: 'chart' => 'Diagram', @@ -1145,6 +1146,7 @@ return [ 'deleted_piggy_bank' => 'Sparschwein „:name” gelöscht', 'added_amount_to_piggy' => ':amount zu „:name” hinzugefügt', 'removed_amount_from_piggy' => ':amount aus „:name” entfernt', + 'piggy_events' => 'Zugehörige Sparschweine', // tags 'delete_tag' => 'Schlagwort „:tag” entfernen', @@ -1154,47 +1156,57 @@ return [ 'updated_tag' => 'Schlagwort „:tag” aktualisiertern', 'created_tag' => 'Schlagwort „:tag” wurde angelegt!', - 'transaction_journal_information' => 'Transaktionsinformationen', - 'transaction_journal_meta' => 'Metainformationen', - 'total_amount' => 'Gesamtbetrag', - 'number_of_decimals' => 'Anzahl der Nachkommastellen', + 'transaction_journal_information' => 'Transaktionsinformationen', + 'transaction_journal_meta' => 'Metainformationen', + 'transaction_journal_more' => 'Weitere Informationen', + 'att_part_of_journal' => 'Unter „:journal” gespeichert', + 'total_amount' => 'Gesamtbetrag', + 'number_of_decimals' => 'Anzahl der Nachkommastellen', // administration - 'administration' => 'Verwaltung', - 'user_administration' => 'Nutzerinformation', - 'list_all_users' => 'Alle Benutzer', - 'all_users' => 'Alle Benutzer', - 'instance_configuration' => 'Konfiguration', - 'firefly_instance_configuration' => 'Konfigurationsoptionen für Firefly III', - 'setting_single_user_mode' => 'Einzelnutzermodus', - 'setting_single_user_mode_explain' => 'Dies ist eine sehr fortschrittliche Funktion, welche aber sehr nützlich sein kann. Stellen Sie sicher, dass Sie die Dokumentation (❓-Symbol in der oberen rechten Ecke) lesen, bevor Sie fortfahren.', - 'store_configuration' => 'Konfiguration speichern', - 'single_user_administration' => 'Benutzerverwaltung für :email', - 'edit_user' => 'Benutzer :email bearbeiten', - 'hidden_fields_preferences' => 'Zur Zeit sind nicht alle Felder sichtbar. Sie müssen in den Einstellungen aktiviert werden.', - 'user_data_information' => 'Nutzerdaten', - 'user_information' => 'Benutzerinformationen', - 'total_size' => 'Gesamtgröße', - 'budget_or_budgets' => 'Budget(s)', - 'budgets_with_limits' => 'Budget(s) mit festgelegtem Betrag', - 'nr_of_rules_in_total_groups' => ':count_rules Regel(n) in :count_groups Gruppenregel(n)', - 'tag_or_tags' => 'Schlagwörter', - 'configuration_updated' => 'Die Konfiguration wurde aktualisiert', - 'setting_is_demo_site' => 'Demonstrationsseite', - 'setting_is_demo_site_explain' => 'Wenn sie diese Option auswählen, wird sich diese Installation wie eine Demonstrationsseite verhalten, was ungewollte Auswirkungen haben kann.', - 'block_code_bounced' => 'E-Mail-Nachricht(en) wurden abgewiesen', - 'block_code_expired' => 'Demo-Konto abgelaufen', - 'no_block_code' => 'Kein Grund für Block oder Benutzer nicht blockiert', - 'block_code_email_changed' => 'Der Benutzer hat die neue E-Mail-Adresse noch nicht bestätigt', - 'admin_update_email' => 'Im Gegensatz zur Profilseite wird der Benutzer NICHT benachrichtigt, dass seine E-Mail-Adresse geändert wurde!', - 'update_user' => 'Benutzer aktualisieren', - 'updated_user' => 'Nutzerdaten wurden geändert.', - 'delete_user' => 'Benutzer :email löschen', - 'user_deleted' => 'Der Nutzer wurde gelöscht', - 'send_test_email' => 'Test-E-Mail senden', - 'send_test_email_text' => 'Um zu sehen, ob Ihre Installation E-Mails senden kann, drücken Sie bitte diese Taste. Sie werden hier keinen Fehler sehen, die Protokolldateien werden etwaige Fehler anzeigen. Sie können diese Taste so oft drücken, wie Sie möchten. Es gibt keine Spamüberprüfung. Die Nachricht wird an :email gesendet und sollte in Kürze ankommen.', - 'send_message' => 'Nachricht senden', - 'send_test_triggered' => 'Der Test wurde ausgelöst. Überprüfen Sie Ihren Posteingang und die Protokolldateien.', + 'administration' => 'Verwaltung', + 'user_administration' => 'Nutzerinformation', + 'list_all_users' => 'Alle Benutzer', + 'all_users' => 'Alle Benutzer', + 'instance_configuration' => 'Konfiguration', + 'firefly_instance_configuration' => 'Konfigurationsoptionen für Firefly III', + 'setting_single_user_mode' => 'Einzelnutzermodus', + 'setting_single_user_mode_explain' => 'Dies ist eine sehr fortschrittliche Funktion, welche aber sehr nützlich sein kann. Stellen Sie sicher, dass Sie die Dokumentation (❓-Symbol in der oberen rechten Ecke) lesen, bevor Sie fortfahren.', + 'store_configuration' => 'Konfiguration speichern', + 'single_user_administration' => 'Benutzerverwaltung für :email', + 'edit_user' => 'Benutzer :email bearbeiten', + 'hidden_fields_preferences' => 'Sie können weitere Buchungsoptionen in Ihren Einstellungen aktivieren.', + 'user_data_information' => 'Nutzerdaten', + 'user_information' => 'Benutzerinformationen', + 'total_size' => 'Gesamtgröße', + 'budget_or_budgets' => 'Budget(s)', + 'budgets_with_limits' => 'Budget(s) mit festgelegtem Betrag', + 'nr_of_rules_in_total_groups' => ':count_rules Regel(n) in :count_groups Gruppenregel(n)', + 'tag_or_tags' => 'Schlagwörter', + 'configuration_updated' => 'Die Konfiguration wurde aktualisiert', + 'setting_is_demo_site' => 'Demonstrationsseite', + 'setting_is_demo_site_explain' => 'Wenn sie diese Option auswählen, wird sich diese Installation wie eine Demonstrationsseite verhalten, was ungewollte Auswirkungen haben kann.', + 'block_code_bounced' => 'E-Mail-Nachricht(en) wurden abgewiesen', + 'block_code_expired' => 'Demo-Konto abgelaufen', + 'no_block_code' => 'Kein Grund für Block oder Benutzer nicht blockiert', + 'block_code_email_changed' => 'Der Benutzer hat die neue E-Mail-Adresse noch nicht bestätigt', + 'admin_update_email' => 'Im Gegensatz zur Profilseite wird der Benutzer NICHT benachrichtigt, dass seine E-Mail-Adresse geändert wurde!', + 'update_user' => 'Benutzer aktualisieren', + 'updated_user' => 'Nutzerdaten wurden geändert.', + 'delete_user' => 'Benutzer :email löschen', + 'user_deleted' => 'Der Nutzer wurde gelöscht', + 'send_test_email' => 'Test-E-Mail senden', + 'send_test_email_text' => 'Um zu sehen, ob Ihre Installation E-Mails senden kann, drücken Sie bitte diese Taste. Sie werden hier keinen Fehler sehen, die Protokolldateien werden etwaige Fehler anzeigen. Sie können diese Taste so oft drücken, wie Sie möchten. Es gibt keine Spamüberprüfung. Die Nachricht wird an :email gesendet und sollte in Kürze ankommen.', + 'send_message' => 'Nachricht senden', + 'send_test_triggered' => 'Der Test wurde ausgelöst. Überprüfen Sie Ihren Posteingang und die Protokolldateien.', + + 'split_transaction_title' => 'Beschreibung der Splittbuchung', + 'split_title_help' => 'Wenn Sie eine Splittbuchung anlegen, muss es eine eindeutige Beschreibung für alle Aufteilungen der Buchhaltung geben.', + 'transaction_information' => 'Buchungsinformation', + 'you_create_transfer' => 'Sie erstellen gerade eine Umbuchung.', + 'you_create_withdrawal' => 'Sie erstellen gerade eine Ausgabe.', + 'you_create_deposit' => 'Sie erstellen gerade eine Einzahlung.', + // links 'journal_link_configuration' => 'Konfiguration der Buchungsverknüpfungen', @@ -1214,12 +1226,13 @@ return [ 'do_not_save_connection' => '(Verbindung nicht speichern)', 'link_transaction' => 'Überweisung verknüpfen', 'link_to_other_transaction' => 'Diese Buchung mit einer anderen Zahlung verknüpfen', - 'select_transaction_to_link' => 'Wählen Sie eine Buchung aus, um diese Zahlung mit … zu verknüpfen', + 'select_transaction_to_link' => 'Wählen Sie eine Buchung aus, mit der Sie diese Buchung verknüpfen möchten. Die Links sind derzeit in Firefly III unbenutzt (abgesehen davon, dass sie gezeigt werden), aber es ist geplant, dies in der Zukunft zu ändern. Verwenden Sie das Suchfeld, um eine Buchung entweder nach Titel oder nach ID auszuwählen. Wenn Sie benutzerdefinierte Verbindungstypen hinzufügen möchten, besuchen Sie den Administrationsbereich.', 'this_transaction' => 'Diese Transaktion', 'transaction' => 'Überweisung', 'comments' => 'Kommentare', - 'to_link_not_found' => 'Wenn die Überweisung, mit der Sie sie verknüpfen möchten, nicht aufgeführt ist, geben Sie einfach deren ID ein.', + 'link_notes' => 'Alle Notizen, die Sie über den Link speichern möchten.', 'invalid_link_selection' => 'Diese Buchungen konnten nicht verknüpft werden', + 'selected_transaction' => 'Ausgewählte Buchung', 'journals_linked' => 'Buchungen wurden verknüpft.', 'journals_error_linked' => 'Diese Buchungen sind bereits verknüpft.', 'journals_link_to_self' => 'Sie können eine Buchung nicht mit sich selbst verknüpfen.', @@ -1258,12 +1271,11 @@ return [ 'split_this_withdrawal' => 'Diese Ausgabe aufteilen', 'split_this_deposit' => 'Diese Einnahme aufteilen', 'split_this_transfer' => 'Diese Umbuchung aufteilen', - 'cannot_edit_multiple_source' => 'Sie dürfen die aufgeteilte Buchung #:id mit der Beschreibung „:description” nicht bearbeiten, da sie mehreren Zielkonten zugeordnet ist.', - 'cannot_edit_multiple_dest' => 'Sie dürfen die aufgeteilte Buchung #:id mit der Beschreibung „:description” nicht bearbeiten, da sie mehreren Zielkonten zugeordnet ist.', - 'cannot_edit_reconciled' => 'Sie dürfen die Buchung #:id mit der Beschreibung „:description” nicht bearbeiten, da sie als „Abgeglichen” gekennzeichnet ist.', 'cannot_edit_opening_balance' => 'Sie können die Eröffnungsbilanz eines Kontos nicht bearbeiten.', 'no_edit_multiple_left' => 'Sie haben keine gültigen Buchungen zur Bearbeitung ausgewählt.', - 'cannot_convert_split_journal' => 'Eine Splitbuchung konnte nicht umgesetzt werden', + 'breadcrumb_convert_group' => 'Buchung umwandeln', + 'convert_invalid_source' => 'Die Quellinformationen sind für Buchung #%d ungültig.', + 'convert_invalid_destination' => 'Die Zielinformationen sind für Buchung „#%d” ungültig.', // Import page (general strings only) 'import_index_title' => 'Buchungen in Firefly III importieren', @@ -1389,4 +1401,15 @@ return [ 'will_jump_monday' => 'Wird am Montag statt am Wochenende ausgeführt.', 'except_weekends' => 'Außer an Wochenenden', 'recurrence_deleted' => 'Dauerauftrag „:title” gelöscht', + + // new lines for summary controller. + 'box_balance_in_currency' => 'Guthaben (:currency)', + 'box_spent_in_currency' => 'Ausgaben (:currency)', + 'box_earned_in_currency' => 'Einnahmen (:currency)', + 'box_bill_paid_in_currency' => 'Gezahlte Rechnungen (:currency)', + 'box_bill_unpaid_in_currency' => 'Unbezahlte Rechnungen (:currency)', + 'box_left_to_spend_in_currency' => 'Verbleibend zum Ausgeben (:currency)', + 'box_net_worth_in_currency' => 'Eigenkapital (:currency)', + 'box_spend_per_day' => 'Pro Tag verbleibend zum Ausgeben: :amount', + ]; diff --git a/resources/lang/de_DE/form.php b/resources/lang/de_DE/form.php index 76de7a10be..21a1c324be 100644 --- a/resources/lang/de_DE/form.php +++ b/resources/lang/de_DE/form.php @@ -53,24 +53,25 @@ return [ 'destination_account' => 'Zielkonto', 'journal_destination_id' => 'Anlagenkonto (Ziel)', 'asset_destination_account' => 'Zielkonto', - 'include_net_worth' => 'Im Nettovermögen enthalten', + 'include_net_worth' => 'Im Eigenkapital enthalten', 'asset_source_account' => 'Quellkonto', 'journal_description' => 'Beschreibung', 'note' => 'Notizen', + 'store_new_transaction' => 'Neue Buchung speichern', 'split_journal' => 'Diese Überweisung aufteilen', 'split_journal_explanation' => 'Diese Überweisung in mehrere Teile aufteilen', 'currency' => 'Währung', 'account_id' => 'Bestandskonto', 'budget_id' => 'Budget', - 'openingBalance' => 'Eröffnungsbilanz', + 'opening_balance' => 'Eröffnungsbilanz', 'tagMode' => 'Schlagwort-Modus', 'tag_position' => 'Schlagwort-Speicherort', - 'virtualBalance' => 'Virtueller Kontostand', + 'virtual_balance' => 'Virtueller Kontostand', 'targetamount' => 'Zielbetrag', - 'accountRole' => 'Rolle des Kontos', - 'openingBalanceDate' => 'Eröffnungsbilanzdatum', - 'ccType' => 'Zahlungsplan der Kreditkarte', - 'ccMonthlyPaymentDate' => 'Monatliches Zahlungsdatum der Kreditkarte', + 'account_role' => 'Kontenfunktion', + 'opening_balance_date' => 'Eröffnungsbilanzdatum', + 'cc_type' => 'Kreditkartenzahlungsplan', + 'cc_monthly_payment_date' => 'Monatlicher Kreditkartenzahlungsplan', 'piggy_bank_id' => 'Sparschwein', 'returnHere' => 'Hierhin zurückkehren', 'returnHereExplanation' => 'Nach dem Speichern hierher zurückkehren, um ein weiteres Element zu erstellen.', @@ -118,7 +119,7 @@ return [ 'symbol' => 'Zeichen', 'code' => 'Schlüssel', 'iban' => 'IBAN', - 'accountNumber' => 'Kontonummer', + 'account_number' => 'Kontonummer', 'creditCardNumber' => 'Kreditkartennummer', 'has_headers' => 'Kopfzeilen', 'date_format' => 'Datumsformat', @@ -139,12 +140,8 @@ return [ 'stop_processing' => 'Verarbeitung beenden', 'start_date' => 'Anfang des Bereichs', 'end_date' => 'Ende des Bereichs', - 'export_start_range' => 'Beginn des Exportbereichs', - 'export_end_range' => 'Ende des Exportbereichs', - 'export_format' => 'Dateiformat', 'include_attachments' => 'Hochgeladene Anhänge hinzufügen', 'include_old_uploads' => 'Importierte Daten hinzufügen', - 'accounts' => 'Exportiere die Überweisungen von diesem Konto', 'delete_account' => 'Konto „:name” löschen', 'delete_bill' => 'Rechnung „:name” löschen', 'delete_budget' => 'Budget „:name” löschen', @@ -256,4 +253,7 @@ return [ 'weekend' => 'Wochenende', 'client_secret' => 'Kundengeheimnis', + 'withdrawal_destination_id' => 'Zielkonto', + 'deposit_source_id' => 'Quellkonto', + ]; diff --git a/resources/lang/de_DE/import.php b/resources/lang/de_DE/import.php index 768e8c4162..0694e3422d 100644 --- a/resources/lang/de_DE/import.php +++ b/resources/lang/de_DE/import.php @@ -207,6 +207,10 @@ return [ 'specific_rabo_descr' => 'Behebt mögliche Probleme mit Rabobank-Dateien', 'specific_pres_name' => 'President\'s Choice Financial CA', 'specific_pres_descr' => 'Behebt mögliche Probleme mit PC-Dateien', + 'specific_belfius_name' => 'Belfius BE', + 'specific_belfius_descr' => 'Behebt mögliche Probleme mit Belfius-Dateien', + 'specific_ingbelgium_name' => 'ING BE', + 'specific_ingbelgium_descr' => 'Behebt mögliche Probleme mit ING Belgien Dateien', // job configuration for file provider (stage: roles) 'job_config_roles_title' => 'Import einrichten (3/4) • Funktion jeder Spalte festlegen', 'job_config_roles_text' => 'Jede Spalte in Ihrer CSV-Datei enthält bestimmte Daten. Bitte geben Sie an, welche Art von Daten enthalten sind. Die Option "Daten zuordnen" bedeutet, dass jeder Eintrag in der Spalte mit einem Wert aus Ihrer der Datenbank ersetzt wird. Eine oft zugeordnete Spalte ist die Spalte, welche die IBAN des fremden Kontos enthält. Diese können leicht mit bereits angelegten IBANs in Ihrer Datenbank verglichen werden.', @@ -291,14 +295,14 @@ return [ 'column_rabo-debit-credit' => 'Rabobank-spezifisches Belastungs- und Kreditkennzeichen', 'column_ing-debit-credit' => 'ING-spezifisches Belastungs- und Kreditkennzeichen', 'column_generic-debit-credit' => 'Allgemeine Überziehungs- und Kreditanzeige', - 'column_sepa-ct-id' => 'SEPA • Ende-zu-Ende-Identifikationsnummer', - 'column_sepa-ct-op' => 'SEPA • Zielkonto-Identifikationsnummer', - 'column_sepa-db' => 'SEPA - Mandatskennung', - 'column_sepa-cc' => 'SEPA • Verrechnungsschlüssel', - 'column_sepa-ci' => 'SEPA • Identifikationsnummer des Zahlungsempfängers', - 'column_sepa-ep' => 'SEPA • Externer Verwendungszweck', - 'column_sepa-country' => 'SEPA • Landesschlüssel', - 'column_sepa-batch-id' => 'SEPA Batch-Kennung', + 'column_sepa_ct_id' => 'SEPA • Ende-zu-Ende-Identifikationsnummer', + 'column_sepa_ct_op' => 'SEPA-Gläubiger-Identifikationsnummer', + 'column_sepa_db' => 'SEPA • Mandatskennung', + 'column_sepa_cc' => 'SEPA • Verrechnungsschlüssel', + 'column_sepa_ci' => 'SEPA • Identifikationsnummer des Zahlungsempfängers', + 'column_sepa_ep' => 'SEPA • Externer Verwendungszweck', + 'column_sepa_country' => 'SEPA • Landesschlüssel', + 'column_sepa_batch_id' => 'SEPA • Stapel-Kennung', 'column_tags-comma' => 'Schlagwörter (durch Kommata getrennt)', 'column_tags-space' => 'Schlagwörter (durch Leerzeichen getrennt)', 'column_account-number' => 'Bestandskonto (Kontonr.)', @@ -306,4 +310,7 @@ return [ 'column_note' => 'Notiz(en)', 'column_internal-reference' => 'Interne Referenz', + // error message + 'duplicate_row' => 'Zeile #:row (":description") konnte nicht importiert werden, da sie bereits existiert.', + ]; diff --git a/resources/lang/de_DE/intro.php b/resources/lang/de_DE/intro.php index 1e342957ce..fb2c8e4459 100644 --- a/resources/lang/de_DE/intro.php +++ b/resources/lang/de_DE/intro.php @@ -24,39 +24,61 @@ declare(strict_types=1); return [ // index - 'index_intro' => 'Wilkommen auf der Startseite von Firefly III. Bitte nehmen Sie sich die Zeit, um ein Gefühl dafür zu bekommen, wie Firefly III funktioniert.', - 'index_accounts-chart' => 'Dieses Diagramm zeigt den aktuellen Saldo Ihrer Bestandskonten. Sie können die anzuzeigenden Konten in Ihren Einstellungen auswählen.', - 'index_box_out_holder' => 'Diese kleine und deren benachbarten Boxen geben Ihnen einen schnellen Überblick über Ihre finanzielle Situation.', - 'index_help' => 'Wenn Sie jemals Hilfe bei einer Seite oder einem Formular benötigen, drücken Sie diese Taste.', - 'index_outro' => 'Die meisten Seiten von Firefly III werden mit einer kleinen Tour wie dieser beginnen. Bitte kontaktieren Sie mich, wenn Sie Fragen oder Kommentare haben. Viel Spaß!', - 'index_sidebar-toggle' => 'Um neue Transaktionen, Konten oder andere Dinge zu erstellen, verwenden Sie das Menü unter diesem Symbol.', + 'index_intro' => 'Wilkommen auf der Startseite von Firefly III. Bitte nehmen Sie sich die Zeit, um ein Gefühl dafür zu bekommen, wie Firefly III funktioniert.', + 'index_accounts-chart' => 'Dieses Diagramm zeigt den aktuellen Saldo Ihrer Bestandskonten. Sie können die anzuzeigenden Konten in Ihren Einstellungen auswählen.', + 'index_box_out_holder' => 'Diese kleine und ihre benachbarten Boxen geben Ihnen einen schnellen Überblick über Ihre finanzielle Situation.', + 'index_help' => 'Wenn Sie jemals Hilfe bei einer Seite oder einem Formular benötigen, drücken Sie diese Taste.', + 'index_outro' => 'Die meisten Seiten von Firefly III werden mit einer kleinen Tour wie dieser beginnen. Bitte kontaktieren Sie mich, wenn Sie Fragen oder Kommentare haben. Viel Spaß!', + 'index_sidebar-toggle' => 'Um neue Transaktionen, Konten oder andere Dinge zu erstellen, verwenden Sie das Menü unter diesem Symbol.', + 'index_cash_account' => 'Dies sind die bisher angelegten Konten. Sie können das Geldkonto verwenden, um die Barausgaben zu verfolgen, aber es ist natürlich nicht zwingend erforderlich.', + + // transactions (withdrawal) + 'transactions_create_withdrawal_source' => 'Wählen Sie aus dieser Auswahlliste Ihr bevorzugtes Anlagenkonto oder Ihre bevorzugte Verbindlichkeit aus.', + 'transactions_create_withdrawal_destination' => 'Wählen Sie hier ein Aufwandskonto. Lassen Sie es leer, wenn Sie eine Barauslage buchen möchten.', + 'transactions_create_withdrawal_foreign_currency' => 'Verwenden Sie dieses Feld, um eine Fremdwährung und einen Betrag festzulegen.', + 'transactions_create_withdrawal_more_meta' => 'Viele weitere Metadaten, die Sie in diesen Feldern festlegen.', + 'transactions_create_withdrawal_split_add' => 'Wenn Sie eine Buchung aufteilen möchten, können Sie mit dieser Schaltfläche weitere Aufteilungen hinzufügen', + + // transactions (deposit) + 'transactions_create_deposit_source' => 'Zahlungsempfänger in dieser Auswahlliste/Textbox für das automatische Vervollständigen auswählen oder eingeben. Leer lassen, wenn Sie eine Bareinzahlung buchen möchten.', + 'transactions_create_deposit_destination' => 'Wählen Sie hier ein Aktiv- oder Passivkonto aus.', + 'transactions_create_deposit_foreign_currency' => 'Verwenden Sie dieses Feld, um eine Fremdwährung und einen Betrag festzulegen.', + 'transactions_create_deposit_more_meta' => 'Viele weitere Metadaten, die Sie in diesen Feldern festlegen.', + 'transactions_create_deposit_split_add' => 'Wenn Sie eine Buchung aufteilen möchten, können Sie mit dieser Schaltfläche weitere Aufteilungen hinzufügen', + + // transactions (transfer) + 'transactions_create_transfer_source' => 'Wählen Sie hier das Quell-Anlagenkonto aus.', + 'transactions_create_transfer_destination' => 'Wählen Sie hier das Ziel-Anlagenkonto aus.', + 'transactions_create_transfer_foreign_currency' => 'Verwenden Sie dieses Feld, um eine Fremdwährung und einen Betrag festzulegen.', + 'transactions_create_transfer_more_meta' => 'Viele weitere Metadaten, die Sie in diesen Feldern festlegen.', + 'transactions_create_transfer_split_add' => 'Wenn Sie eine Buchung aufteilen möchten, können Sie mit dieser Schaltfläche weitere Aufteilungen hinzufügen', // create account: - 'accounts_create_iban' => 'Geben Sie Ihren Konten eine gültige IBAN. Dies könnte einen Datenimport in Zukunft sehr einfach machen.', - 'accounts_create_asset_opening_balance' => 'Bestandskonten können eine "Eröffnungsbilanz" haben, welche den Beginn des Verlaufs dieses Kontos in Firefly III angibt.', - 'accounts_create_asset_currency' => 'Firefly III unterstützt mehrere Währungen. Bestandskonten ist eine Hauptwährung zugeordnet, die Sie hier festlegen müssen.', - 'accounts_create_asset_virtual' => 'Es kann manchmal helfen, Ihrem Konto ein virtuelles Gleichgewicht zu geben: eine zusätzliche Menge, die dem tatsächlichen Kontostand immer hinzugefügt oder daraus entfernt wird.', + 'accounts_create_iban' => 'Geben Sie Ihren Konten eine gültige IBAN. Dies könnte einen Datenimport in Zukunft sehr einfach machen.', + 'accounts_create_asset_opening_balance' => 'Bestandskonten können eine "Eröffnungsbilanz" haben, welche den Beginn des Verlaufs dieses Kontos in Firefly III angibt.', + 'accounts_create_asset_currency' => 'Firefly III unterstützt mehrere Währungen. Bestandskonten ist eine Hauptwährung zugeordnet, die Sie hier festlegen müssen.', + 'accounts_create_asset_virtual' => 'Es kann manchmal helfen, Ihrem Konto ein virtuelles Gleichgewicht zu geben: eine zusätzliche Menge, die dem tatsächlichen Kontostand immer hinzugefügt oder daraus entfernt wird.', // budgets index - 'budgets_index_intro' => 'Budgets werden zur Verwaltung Ihrer Finanzen verwendet und bilden eine der Kernfunktionen von Firefly III.', - 'budgets_index_set_budget' => 'Stellen Sie Ihr Gesamt-Budget für jeden Zeitraum so ein, dass Firefly III Ihnen mitteilen kann, ob Sie alle verfügbaren Gelder einem Budget zugeordnet haben.', - 'budgets_index_see_expenses_bar' => 'Dieser Balken wird sich langsam füllen, wenn Sie Geld ausgeben.', - 'budgets_index_navigate_periods' => 'Navigieren Sie durch Zeitabschnitte, um Budgets im Voraus festzulegen.', - 'budgets_index_new_budget' => 'Erstellen Sie neue Budgets nach Ihren Wünschen.', - 'budgets_index_list_of_budgets' => 'Verwenden Sie diese Tabelle, um die Beträge für jedes Budget festzulegen und einen Überblick zu erhalten.', - 'budgets_index_outro' => 'Um mehr über die Finanzplanung zu erfahren, klicken Sie auf das Hilfesymbol in der oberen rechten Ecke.', + 'budgets_index_intro' => 'Budgets werden zur Verwaltung Ihrer Finanzen verwendet und bilden eine der Kernfunktionen von Firefly III.', + 'budgets_index_set_budget' => 'Stellen Sie Ihr Gesamt-Budget für jeden Zeitraum so ein, dass Firefly III Ihnen mitteilen kann, ob Sie alle verfügbaren Gelder einem Budget zugeordnet haben.', + 'budgets_index_see_expenses_bar' => 'Dieser Balken wird sich langsam füllen, wenn Sie Geld ausgeben.', + 'budgets_index_navigate_periods' => 'Navigieren Sie durch Zeitabschnitte, um Budgets im Voraus festzulegen.', + 'budgets_index_new_budget' => 'Erstellen Sie neue Budgets nach Ihren Wünschen.', + 'budgets_index_list_of_budgets' => 'Verwenden Sie diese Tabelle, um die Beträge für jedes Budget festzulegen und einen Überblick zu erhalten.', + 'budgets_index_outro' => 'Um mehr über die Finanzplanung zu erfahren, klicken Sie auf das Hilfesymbol in der oberen rechten Ecke.', // reports (index) - 'reports_index_intro' => 'Verwenden Sie diese Reports, um detaillierte Einblicke in Ihre Finanzen zu erhalten.', - 'reports_index_inputReportType' => 'Wählen Sie einen Berichtstyp aus. Sehen Sie sich die Hilfeseiten an, um zu sehen, was jeder Bericht Ihnen zeigt.', - 'reports_index_inputAccountsSelect' => 'Sie können Bestandskonten ausschließen oder einbeziehen, wie Sie es für richtig halten.', - 'reports_index_inputDateRange' => 'Der gewählte Datumsbereich liegt ganz bei Ihnen: von einem Tag bis 10 Jahre.', - 'reports_index_extra-options-box' => 'Abhängig von dem ausgewählten Bericht können Sie hier zusätzliche Filter und Optionen auswählen. Sehen Sie sich dieses Feld an, wenn Sie Berichtstypen ändern.', + 'reports_index_intro' => 'Verwenden Sie diese Reports, um detaillierte Einblicke in Ihre Finanzen zu erhalten.', + 'reports_index_inputReportType' => 'Wählen Sie einen Berichtstyp aus. Sehen Sie sich die Hilfeseiten an, um zu sehen, was jeder Bericht Ihnen zeigt.', + 'reports_index_inputAccountsSelect' => 'Sie können Bestandskonten ausschließen oder einbeziehen, wie Sie es für richtig halten.', + 'reports_index_inputDateRange' => 'Der gewählte Datumsbereich liegt ganz bei Ihnen: von einem Tag bis 10 Jahre.', + 'reports_index_extra-options-box' => 'Abhängig von dem ausgewählten Bericht können Sie hier zusätzliche Filter und Optionen auswählen. Sehen Sie sich dieses Feld an, wenn Sie Berichtstypen ändern.', // reports (reports) - 'reports_report_default_intro' => 'Dieser Bericht gibt Ihnen einen schnellen und umfassenden Überblick über Ihre Finanzen. Wenn Sie etwas anderes sehen möchten, kontaktieren Sie mich bitte nicht!', - 'reports_report_audit_intro' => 'In diesem Bericht erhalten Sie detaillierte Einblicke in Ihre Bestandskonten.', - 'reports_report_audit_optionsBox' => 'Verwenden Sie diese Kontrollkästchen, um die Spalten anzuzeigen oder auszublenden, an denen Sie interessiert sind.', + 'reports_report_default_intro' => 'Dieser Bericht gibt Ihnen einen schnellen und umfassenden Überblick über Ihre Finanzen. Wenn Sie etwas anderes sehen möchten, kontaktieren Sie mich bitte nicht!', + 'reports_report_audit_intro' => 'In diesem Bericht erhalten Sie detaillierte Einblicke in Ihre Bestandskonten.', + 'reports_report_audit_optionsBox' => 'Verwenden Sie diese Kontrollkästchen, um die Spalten anzuzeigen oder auszublenden, an denen Sie interessiert sind.', 'reports_report_category_intro' => 'Dieser Bericht gibt Ihnen Einblick in eine oder mehrere Kategorien.', 'reports_report_category_pieCharts' => 'Diese Diagramme geben Ihnen Einblick in Ausgaben und Einnahmen pro Kategorie oder pro Konto.', diff --git a/resources/lang/de_DE/list.php b/resources/lang/de_DE/list.php index 26611feb85..4558928621 100644 --- a/resources/lang/de_DE/list.php +++ b/resources/lang/de_DE/list.php @@ -23,71 +23,71 @@ declare(strict_types=1); return [ - 'buttons' => 'Schaltflächen', - 'icon' => 'Symbol', - 'id' => 'Id', - 'create_date' => 'Erstellt am', - 'update_date' => 'Aktualisiert am', - 'updated_at' => 'Aktualisiert am', - 'balance_before' => 'Kontostand vorher', - 'balance_after' => 'Kontostand nachher', - 'name' => 'Name', - 'role' => 'Rolle', - 'currentBalance' => 'Aktueller Kontostand', - 'linked_to_rules' => 'Verlinkte Regeln', - 'active' => 'Aktiv?', - 'lastActivity' => 'Letzte Aktivität', - 'balanceDiff' => 'Saldendifferenz', - 'matchesOn' => 'Zusammengeführt am', - 'account_type' => 'Kontotyp', - 'created_at' => 'Erstellt am', - 'account' => 'Konto', - 'matchingAmount' => 'Betrag', - 'split_number' => 'Geteilt #', - 'destination' => 'Empfänger', - 'source' => 'Quelle', - 'next_expected_match' => 'Nächste erwartete Übereinstimmung', - 'automatch' => 'Automatisch erkennen?', - 'repeat_freq' => 'Wiederholungen', - 'description' => 'Beschreibung', - 'amount' => 'Betrag', - 'internal_reference' => 'Interne Referenz', - 'date' => 'Datum', - 'interest_date' => 'Zinstermin', - 'book_date' => 'Buchungsdatum', - 'process_date' => 'Bearbeitungsdatum', - 'due_date' => 'Fälligkeitstermin', - 'payment_date' => 'Zahlungsdatum', - 'invoice_date' => 'Rechnungsdatum', - 'interal_reference' => 'Interner Verweis', - 'notes' => 'Notizen', - 'from' => 'Von', - 'piggy_bank' => 'Sparschwein', - 'to' => 'An', - 'budget' => 'Budget', - 'category' => 'Kategorie', - 'bill' => 'Rechnung', - 'withdrawal' => 'Ausgabe', - 'deposit' => 'Einlage', - 'transfer' => 'Überweisung', - 'type' => 'Typ', - 'completed' => 'Abgeschlossen', - 'iban' => 'IBAN', - 'paid_current_period' => 'Diese Periode bezahlt', - 'email' => 'E-Mail', - 'registered_at' => 'Registriert am', - 'is_blocked' => 'Ist blockiert', - 'is_admin' => 'Ist Admin', - 'has_two_factor' => 'Nutzt 2FA', - 'blocked_code' => 'Blockcode', - 'source_account' => 'Quellkonto', + 'buttons' => 'Schaltflächen', + 'icon' => 'Symbol', + 'id' => 'Id', + 'create_date' => 'Erstellt am', + 'update_date' => 'Aktualisiert am', + 'updated_at' => 'Aktualisiert am', + 'balance_before' => 'Kontostand vorher', + 'balance_after' => 'Kontostand nachher', + 'name' => 'Name', + 'role' => 'Rolle', + 'currentBalance' => 'Aktueller Kontostand', + 'linked_to_rules' => 'Verlinkte Regeln', + 'active' => 'Aktiv?', + 'transaction_type' => 'Typ', + 'lastActivity' => 'Letzte Aktivität', + 'balanceDiff' => 'Saldendifferenz', + 'matchesOn' => 'Zusammengeführt am', + 'account_type' => 'Kontotyp', + 'created_at' => 'Erstellt am', + 'account' => 'Konto', + 'matchingAmount' => 'Betrag', + 'split_number' => 'Geteilt #', + 'destination' => 'Empfänger', + 'source' => 'Quelle', + 'next_expected_match' => 'Nächste erwartete Übereinstimmung', + 'automatch' => 'Automatisch erkennen?', + 'repeat_freq' => 'Wiederholungen', + 'description' => 'Beschreibung', + 'amount' => 'Betrag', + 'internal_reference' => 'Interne Referenz', + 'date' => 'Datum', + 'interest_date' => 'Zinstermin', + 'book_date' => 'Buchungsdatum', + 'process_date' => 'Bearbeitungsdatum', + 'due_date' => 'Fälligkeitstermin', + 'payment_date' => 'Zahlungsdatum', + 'invoice_date' => 'Rechnungsdatum', + 'interal_reference' => 'Interner Verweis', + 'notes' => 'Notizen', + 'from' => 'Von', + 'piggy_bank' => 'Sparschwein', + 'to' => 'An', + 'budget' => 'Budget', + 'category' => 'Kategorie', + 'bill' => 'Rechnung', + 'withdrawal' => 'Ausgabe', + 'deposit' => 'Einlage', + 'transfer' => 'Überweisung', + 'type' => 'Typ', + 'completed' => 'Abgeschlossen', + 'iban' => 'IBAN', + 'paid_current_period' => 'Diese Periode bezahlt', + 'email' => 'E-Mail', + 'registered_at' => 'Registriert am', + 'is_blocked' => 'Ist blockiert', + 'is_admin' => 'Ist Admin', + 'has_two_factor' => 'Nutzt 2FA', + 'blocked_code' => 'Blockcode', + 'source_account' => 'Quellkonto', 'destination_account' => 'Zielkonto', 'accounts_count' => 'Anzahl Konten', 'journals_count' => 'Anzahl der Zahlungsvorgänge', 'attachments_count' => 'Anzahl Anhänge', 'bills_count' => 'Anzahl Rechnungen', 'categories_count' => 'Anzahl Kategorien', - 'export_jobs_count' => 'Anzahl exportierter Jobs', 'import_jobs_count' => 'Anzahl importierter Jobs', 'budget_count' => 'Anzahl Kostenpläne', 'rule_and_groups_count' => 'Anzahl Regeln und Regelgruppen', @@ -106,14 +106,14 @@ return [ 'account_on_spectre' => 'Konto (Spectre)', 'account_on_ynab' => 'Konto (YNAB)', 'do_import' => 'Von diesem Konto importieren', - 'sepa-ct-id' => 'SEPA • Ende-zu-Ende-Identifikationsnummer', - 'sepa-ct-op' => 'SEPA • Zielkonto-Identifikationsnummer', - 'sepa-db' => 'SEPA - Mandatskennung', - 'sepa-country' => 'SEPA • Land', - 'sepa-cc' => 'SEPA • Verrechnungsschlüssel', - 'sepa-ep' => 'SEPA • Externer Verwendungszweck', - 'sepa-ci' => 'SEPA • Identifikationsnummer des Zahlungsempfängers', - 'sepa-batch-id' => 'SEPA Batch-Kennung', + 'sepa_ct_id' => 'SEPA • Ende-zu-Ende-Identifikationsnummer', + 'sepa_ct_op' => 'SEPA-Gläubiger-Identifikationsnummer', + 'sepa_db' => 'SEPA • Mandatskennung', + 'sepa_country' => 'SEPA • Land', + 'sepa_cc' => 'SEPA • Verrechnungsschlüssel', + 'sepa_ep' => 'SEPA • Externer Verwendungszweck', + 'sepa_ci' => 'SEPA • Identifikationsnummer des Zahlungsempfängers', + 'sepa_batch_id' => 'SEPA • Stapel-Kennung', 'external_id' => 'Externe Kennung', 'account_at_bunq' => 'Konto bei „bunq”', 'file_name' => 'Dateiname', diff --git a/resources/lang/de_DE/validation.php b/resources/lang/de_DE/validation.php index 6f29920127..4ac7d87ebc 100644 --- a/resources/lang/de_DE/validation.php +++ b/resources/lang/de_DE/validation.php @@ -36,12 +36,16 @@ return [ 'file_attached' => 'Datei „:name” erfolgreich hochgeladen.', 'must_exist' => 'Die ID in Feld :attribute existiert nicht in der Datenbank.', 'all_accounts_equal' => 'Alle Konten in diesem Feld müssen identisch sein.', + 'group_title_mandatory' => 'Ein Gruppentitel ist zwingend erforderlich, wenn mehr als eine Buchung vorliegt.', + 'transaction_types_equal' => 'Alle Aufteilungen müssen vom gleichen Typ sein.', + 'invalid_transaction_type' => 'Ungültige Transaktionstyp', 'invalid_selection' => 'Ihre Auswahl ist ungültig.', 'belongs_user' => 'Dieser Wert ist für dieses Feld ungültig.', 'at_least_one_transaction' => 'Sie brauchen mindestens eine Transaktion.', 'at_least_one_repetition' => 'Mindestens eine Wiederholung erforderlich.', 'require_repeat_until' => 'Erfordert entweder eine Anzahl von Wiederholungen oder ein Enddatum (repeat_until). Nicht beides.', 'require_currency_info' => 'Der Inhalt dieses Feldes ist ohne Währungsinformationen ungültig.', + 'require_currency_amount' => 'Der Inhalt dieses Feldes ist ohne Fremdbetragsangaben ungültig.', 'equal_description' => 'Die Transaktionsbeschreibung darf nicht der globalen Beschreibung entsprechen.', 'file_invalid_mime' => 'Die Datei „:name” ist vom Typ „:mime”, welcher nicht zum Hochladen zugelassen ist.', 'file_too_large' => 'Die Datei „:name” ist zu groß.', @@ -135,8 +139,8 @@ return [ 'name' => 'Name', 'piggy_bank_id' => 'Sparschwein ID', 'targetamount' => 'Zielbetrag', - 'openingBalanceDate' => 'Datum des Eröffnungskontostands', - 'openingBalance' => 'Eröffnungskontostand', + 'opening_balance_date' => 'Datum des Eröffnungskontostands', + 'opening_balance' => 'Eröffnungskontostand', 'match' => 'Übereinstimmung', 'amount_min' => 'Mindestbetrag', 'amount_max' => 'Höchstbetrag', @@ -164,4 +168,28 @@ return [ 'rule-trigger.4' => 'Regel 4 auslösen', 'rule-trigger.5' => 'Regel #5 auslösen', ], + + // validation of accounts: + 'withdrawal_source_need_data' => 'Um fortzufahren, benötigen Sie eine gültige Quellkontenkennung und/oder einen gültigen Quellkontonamen.', + 'withdrawal_source_bad_data' => 'Bei der Suche nach der Kennung „:id” oder dem Namen „:name” konnte kein gültiges Quellkonto gefunden werden.', + 'withdrawal_dest_need_data' => 'Um fortzufahren, benötigen Sie eine gültige Zielkontenkennung und/oder einen gültigen Zielkontonamen.', + 'withdrawal_dest_bad_data' => 'Bei der Suche nach Kennung „:id” oder Name „:name” konnte kein gültiges Zielkonto gefunden werden.', + + 'deposit_source_need_data' => 'Um fortzufahren, benötigen Sie eine gültige Quellkontenkennung und/oder einen gültigen Quellkontonamen.', + 'deposit_source_bad_data' => 'Bei der Suche nach der Kennung „:id” oder dem Namen „:name” konnte kein gültiges Quellkonto gefunden werden.', + 'deposit_dest_need_data' => 'Um fortzufahren, benötigen Sie eine gültige Zielkontenkennung und/oder einen gültigen Zielkontonamen.', + 'deposit_dest_bad_data' => 'Bei der Suche nach der Kennung „:id” oder dem Namen „:name” konnte kein gültiges Zielkonto gefunden werden.', + + 'transfer_source_need_data' => 'Um fortzufahren, benötigen Sie eine gültige Quellkontenkennung und/oder einen gültigen Quellkontonamen.', + 'transfer_source_bad_data' => 'Bei der Suche nach der Kennung „:id” oder dem Namen „:name” konnte kein gültiges Quellkonto gefunden werden.', + 'transfer_dest_need_data' => 'Um fortzufahren, benötigen Sie eine gültige Zielkontenkennung und/oder einen gültigen Zielkontonamen.', + 'transfer_dest_bad_data' => 'Bei der Suche nach der Kennung „:id” oder dem Namen „:name” konnte kein gültiges Zielkonto gefunden werden.', + 'need_id_in_edit' => 'Jeder Aufteilungen muss eine transaction_journal_id (entweder gültige ID oder 0) aufweisen.', + + 'ob_source_need_data' => 'Sie benötigen eine gültige Quellkontonummer und/oder einen gültigen Quellkontonamen, um fortzufahren.', + 'ob_dest_need_data' => 'Sie benötigen eine gültige Zielkontennummer und/oder einen gültigen Zielkontonamen, um fortzufahren.', + 'ob_dest_bad_data' => 'Bei der Suche nach der ID ":id" oder dem Namen ":name" konnte kein gültiges Zielkonto gefunden werden.', + + 'generic_invalid_source' => 'Sie können dieses Konto nicht als Quellkonto verwenden.', + 'generic_invalid_destination' => 'Sie können dieses Konto nicht als Zielkonto verwenden.', ]; diff --git a/resources/lang/en_US/breadcrumbs.php b/resources/lang/en_US/breadcrumbs.php index 3a98e01f6f..946979e57e 100644 --- a/resources/lang/en_US/breadcrumbs.php +++ b/resources/lang/en_US/breadcrumbs.php @@ -23,35 +23,37 @@ declare(strict_types=1); return [ - 'home' => 'Home', - 'edit_currency' => 'Edit currency ":name"', - 'delete_currency' => 'Delete currency ":name"', - 'newPiggyBank' => 'Create a new piggy bank', - 'edit_piggyBank' => 'Edit piggy bank ":name"', - 'preferences' => 'Preferences', - 'profile' => 'Profile', - 'changePassword' => 'Change your password', - 'change_email' => 'Change your email address', - 'bills' => 'Bills', - 'newBill' => 'New bill', - 'edit_bill' => 'Edit bill ":name"', - 'delete_bill' => 'Delete bill ":name"', - 'reports' => 'Reports', - 'search_result' => 'Search results for ":query"', - 'withdrawal_list' => 'Expenses', - 'deposit_list' => 'Revenue, income and deposits', - 'transfer_list' => 'Transfers', - 'transfers_list' => 'Transfers', - 'reconciliation_list' => 'Reconciliations', - 'create_withdrawal' => 'Create new withdrawal', - 'create_deposit' => 'Create new deposit', - 'create_transfer' => 'Create new transfer', - 'edit_journal' => 'Edit transaction ":description"', - 'edit_reconciliation' => 'Edit ":description"', - 'delete_journal' => 'Delete transaction ":description"', - 'tags' => 'Tags', - 'createTag' => 'Create new tag', - 'edit_tag' => 'Edit tag ":tag"', - 'delete_tag' => 'Delete tag ":tag"', - 'delete_journal_link' => 'Delete link between transactions', + 'home' => 'Home', + 'edit_currency' => 'Edit currency ":name"', + 'delete_currency' => 'Delete currency ":name"', + 'newPiggyBank' => 'Create a new piggy bank', + 'edit_piggyBank' => 'Edit piggy bank ":name"', + 'preferences' => 'Preferences', + 'profile' => 'Profile', + 'changePassword' => 'Change your password', + 'change_email' => 'Change your email address', + 'bills' => 'Bills', + 'newBill' => 'New bill', + 'edit_bill' => 'Edit bill ":name"', + 'delete_bill' => 'Delete bill ":name"', + 'reports' => 'Reports', + 'search_result' => 'Search results for ":query"', + 'withdrawal_list' => 'Expenses', + 'Withdrawal_list' => 'Expenses', + 'deposit_list' => 'Revenue, income and deposits', + 'transfer_list' => 'Transfers', + 'transfers_list' => 'Transfers', + 'reconciliation_list' => 'Reconciliations', + 'create_withdrawal' => 'Create new withdrawal', + 'create_deposit' => 'Create new deposit', + 'create_transfer' => 'Create new transfer', + 'create_new_transaction' => 'Create a new transaction', + 'edit_journal' => 'Edit transaction ":description"', + 'edit_reconciliation' => 'Edit ":description"', + 'delete_journal' => 'Delete transaction ":description"', + 'tags' => 'Tags', + 'createTag' => 'Create new tag', + 'edit_tag' => 'Edit tag ":tag"', + 'delete_tag' => 'Delete tag ":tag"', + 'delete_journal_link' => 'Delete link between transactions', ]; diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index 14bcc69f30..80ab62d925 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -33,7 +33,7 @@ return [ 'last_seven_days' => 'Last seven days', 'last_thirty_days' => 'Last thirty days', 'welcomeBack' => 'What\'s playing?', - 'welcome_back' => 'What\'s playing?', + 'welcome_back' => 'What\'s playing?', 'everything' => 'Everything', 'today' => 'today', 'customRange' => 'Custom range', @@ -54,6 +54,7 @@ return [ 'create_new_stuff' => 'Create new stuff', 'new_withdrawal' => 'New withdrawal', 'create_new_transaction' => 'Create new transaction', + 'new_transaction' => 'New transaction', 'go_to_asset_accounts' => 'View your asset accounts', 'go_to_budgets' => 'Go to your budgets', 'go_to_categories' => 'Go to your categories', @@ -82,28 +83,32 @@ return [ 'help_for_this_page' => 'Help for this page', 'no_help_could_be_found' => 'No help text could be found.', 'no_help_title' => 'Apologies, an error occurred.', - 'two_factor_welcome' => 'Hello, :user!', - 'two_factor_enter_code' => 'To continue, please enter your two factor authentication code. Your application can generate it for you.', - 'two_factor_code_here' => 'Enter code here', - 'two_factor_title' => 'Two factor authentication', - 'authenticate' => 'Authenticate', - 'two_factor_forgot_title' => 'Lost two factor authentication', - 'two_factor_forgot' => 'I forgot my two-factor thing.', - 'two_factor_lost_header' => 'Lost your two factor authentication?', - 'two_factor_lost_intro' => 'Unfortunately, this is not something you can reset from the web interface. You have two choices.', - 'two_factor_lost_fix_self' => 'If you run your own instance of Firefly III, check the logs in storage/logs for instructions.', - 'two_factor_lost_fix_owner' => 'Otherwise, email the site owner, :site_owner and ask them to reset your two factor authentication.', - 'warning_much_data' => ':days days of data may take a while to load.', - 'registered' => 'You have registered successfully!', - 'Default asset account' => 'Default asset account', - 'no_budget_pointer' => 'You seem to have no budgets yet. You should create some on the budgets-page. Budgets can help you keep track of expenses.', - 'Savings account' => 'Savings account', - 'Credit card' => 'Credit card', - 'source_accounts' => 'Source account(s)', - 'destination_accounts' => 'Destination account(s)', - 'user_id_is' => 'Your user id is :user', - 'field_supports_markdown' => 'This field supports Markdown.', - 'need_more_help' => 'If you need more help using Firefly III, please open a ticket on Github.', + 'two_factor_welcome' => 'Hello!', + 'two_factor_enter_code' => 'To continue, please enter your two factor authentication code. Your application can generate it for you.', + 'two_factor_code_here' => 'Enter code here', + 'two_factor_title' => 'Two factor authentication', + 'authenticate' => 'Authenticate', + 'two_factor_forgot_title' => 'Lost two factor authentication', + 'two_factor_forgot' => 'I forgot my two-factor thing.', + 'two_factor_lost_header' => 'Lost your two factor authentication?', + 'two_factor_lost_intro' => 'If you lost your backup codes as well, you have bad luck. This is not something you can fix from the web interface. You have two choices.', + 'two_factor_lost_fix_self' => 'If you run your own instance of Firefly III, check the logs in storage/logs for instructions, or run docker logs <container_id> to see the instructions (refresh this page).', + 'two_factor_lost_fix_owner' => 'Otherwise, email the site owner, :site_owner and ask them to reset your two factor authentication.', + 'mfa_backup_code' => 'You have used a backup code to login to Firefly III. It can\'t be used again, so cross it from your list.', + 'pref_two_factor_new_backup_codes' => 'Get new backup codes', + 'pref_two_factor_backup_code_count' => 'You have :count valid backup code(s).', + '2fa_i_have_them' => 'I stored them!', + 'warning_much_data' => ':days days of data may take a while to load.', + 'registered' => 'You have registered successfully!', + 'Default asset account' => 'Default asset account', + 'no_budget_pointer' => 'You seem to have no budgets yet. You should create some on the budgets-page. Budgets can help you keep track of expenses.', + 'Savings account' => 'Savings account', + 'Credit card' => 'Credit card', + 'source_accounts' => 'Source account(s)', + 'destination_accounts' => 'Destination account(s)', + 'user_id_is' => 'Your user id is :user', + 'field_supports_markdown' => 'This field supports Markdown.', + 'need_more_help' => 'If you need more help using Firefly III, please open a ticket on Github.', 'reenable_intro_text' => 'You can also reenable the introduction guidance.', 'intro_boxes_after_refresh' => 'The introduction boxes will reappear when you refresh the page.', 'show_all_no_filter' => 'Show all transactions without grouping them by date.', @@ -220,21 +225,23 @@ return [ 'search_query' => 'Query', 'search_found_transactions' => 'Firefly III found :count transaction(s) in :time seconds.', 'search_for_query' => 'Firefly III is searching for transactions with all of these words in them: :query', - 'search_modifier_amount_is' => 'Amount is exactly :value', - 'search_modifier_amount' => 'Amount is exactly :value', - 'search_modifier_amount_max' => 'Amount is at most :value', - 'search_modifier_amount_min' => 'Amount is at least :value', - 'search_modifier_amount_less' => 'Amount is less than :value', - 'search_modifier_amount_more' => 'Amount is more than :value', - 'search_modifier_source' => 'Source account is :value', - 'search_modifier_destination' => 'Destination account is :value', - 'search_modifier_category' => 'Category is :value', - 'search_modifier_budget' => 'Budget is :value', - 'search_modifier_bill' => 'Bill is :value', - 'search_modifier_type' => 'Transaction type is :value', - 'search_modifier_date' => 'Transaction date is :value', - 'search_modifier_date_before' => 'Transaction date is before :value', - 'search_modifier_date_after' => 'Transaction date is after :value', + 'search_modifier_amount_is' => 'Amount is exactly :value', + 'search_modifier_amount' => 'Amount is exactly :value', + 'search_modifier_amount_max' => 'Amount is at most :value', + 'search_modifier_amount_min' => 'Amount is at least :value', + 'search_modifier_amount_less' => 'Amount is less than :value', + 'search_modifier_amount_more' => 'Amount is more than :value', + 'search_modifier_source' => 'Source account is :value', + 'search_modifier_from' => 'Source account is :value', + 'search_modifier_destination' => 'Destination account is :value', + 'search_modifier_to' => 'Destination account is :value', + 'search_modifier_category' => 'Category is :value', + 'search_modifier_budget' => 'Budget is :value', + 'search_modifier_bill' => 'Bill is :value', + 'search_modifier_type' => 'Transaction type is :value', + 'search_modifier_date' => 'Transaction date is :value', + 'search_modifier_date_before' => 'Transaction date is before :value', + 'search_modifier_date_after' => 'Transaction date is after :value', 'search_modifier_on' => 'Transaction date is :value', 'search_modifier_before' => 'Transaction date is before :value', 'search_modifier_after' => 'Transaction date is after :value', @@ -257,33 +264,6 @@ return [ 'half-year' => 'every half year', 'yearly' => 'yearly', - // export data: - 'import_and_export' => 'Import and export', - 'export_data' => 'Export data', - 'export_and_backup_data' => 'Export data', - 'export_data_intro' => 'Use the exported data to move to a new financial application. Please note that these files are not meant as a backup. They do not contain enough meta-data to fully restore a new Firefly III installation. If you want to make a backup of your data, please backup the database directly.', - 'export_format' => 'Export format', - 'export_format_csv' => 'Comma separated values (CSV file)', - 'export_format_mt940' => 'MT940 compatible format', - 'include_old_uploads_help' => 'Firefly III does not throw away the original CSV files you have imported in the past. You can include them in your export.', - 'do_export' => 'Export', - 'export_status_never_started' => 'The export has not started yet', - 'export_status_make_exporter' => 'Creating exporter thing...', - 'export_status_collecting_journals' => 'Collecting your transactions...', - 'export_status_collected_journals' => 'Collected your transactions!', - 'export_status_converting_to_export_format' => 'Converting your transactions...', - 'export_status_converted_to_export_format' => 'Converted your transactions!', - 'export_status_creating_journal_file' => 'Creating the export file...', - 'export_status_created_journal_file' => 'Created the export file!', - 'export_status_collecting_attachments' => 'Collecting all your attachments...', - 'export_status_collected_attachments' => 'Collected all your attachments!', - 'export_status_collecting_old_uploads' => 'Collecting all your previous uploads...', - 'export_status_collected_old_uploads' => 'Collected all your previous uploads!', - 'export_status_creating_zip_file' => 'Creating a zip file...', - 'export_status_created_zip_file' => 'Created a zip file!', - 'export_status_finished' => 'Export has succesfully finished! Yay!', - 'export_data_please_wait' => 'Please wait...', - // rules 'rules' => 'Rules', 'rule_name' => 'Name of rule', @@ -502,30 +482,33 @@ return [ 'pref_custom_fiscal_year_help' => 'In countries that use a financial year other than January 1 to December 31, you can switch this on and specify start / end days of the fiscal year', 'pref_fiscal_year_start_label' => 'Fiscal year start date', 'pref_two_factor_auth' => '2-step verification', - 'pref_two_factor_auth_help' => 'When you enable 2-step verification (also known as two-factor authentication), you add an extra layer of security to your account. You sign in with something you know (your password) and something you have (a verification code). Verification codes are generated by an application on your phone, such as Authy or Google Authenticator.', - 'pref_enable_two_factor_auth' => 'Enable 2-step verification', - 'pref_two_factor_auth_disabled' => '2-step verification code removed and disabled', - 'pref_two_factor_auth_remove_it' => 'Don\'t forget to remove the account from your authentication app!', - 'pref_two_factor_auth_code' => 'Verify code', - 'pref_two_factor_auth_code_help' => 'Scan the QR code with an application on your phone such as Authy or Google Authenticator and enter the generated code.', - 'pref_two_factor_auth_reset_code' => 'Reset verification code', - 'pref_two_factor_auth_disable_2fa' => 'Disable 2FA', - '2fa_use_secret_instead' => 'If you cannot scan the QR code, feel free to use the secret instead: :secret.', - 'pref_save_settings' => 'Save settings', - 'saved_preferences' => 'Preferences saved!', - 'preferences_general' => 'General', - 'preferences_frontpage' => 'Home screen', - 'preferences_security' => 'Security', - 'preferences_layout' => 'Layout', - 'pref_home_show_deposits' => 'Show deposits on the home screen', - 'pref_home_show_deposits_info' => 'The home screen already shows your expense accounts. Should it also show your revenue accounts?', - 'pref_home_do_show_deposits' => 'Yes, show them', - 'successful_count' => 'of which :count successful', - 'list_page_size_title' => 'Page size', - 'list_page_size_help' => 'Any list of things (accounts, transactions, etc) shows at most this many per page.', - 'list_page_size_label' => 'Page size', - 'between_dates' => '(:start and :end)', - 'pref_optional_fields_transaction' => 'Optional fields for transactions', + 'pref_two_factor_auth_help' => 'When you enable 2-step verification (also known as two-factor authentication), you add an extra layer of security to your account. You sign in with something you know (your password) and something you have (a verification code). Verification codes are generated by an application on your phone, such as Authy or Google Authenticator.', + 'pref_enable_two_factor_auth' => 'Enable 2-step verification', + 'pref_two_factor_auth_disabled' => '2-step verification code removed and disabled', + 'pref_two_factor_auth_remove_it' => 'Don\'t forget to remove the account from your authentication app!', + 'pref_two_factor_auth_code' => 'Verify code', + 'pref_two_factor_auth_code_help' => 'Scan the QR code with an application on your phone such as Authy or Google Authenticator and enter the generated code.', + 'pref_two_factor_auth_reset_code' => 'Reset verification code', + 'pref_two_factor_auth_disable_2fa' => 'Disable 2FA', + '2fa_use_secret_instead' => 'If you cannot scan the QR code, feel free to use the secret instead: :secret.', + '2fa_backup_codes' => 'Store these backup codes for access in case you lose your device.', + '2fa_already_enabled' => '2-step verification is already enabled.', + 'wrong_mfa_code' => 'This MFA code is not valid.', + 'pref_save_settings' => 'Save settings', + 'saved_preferences' => 'Preferences saved!', + 'preferences_general' => 'General', + 'preferences_frontpage' => 'Home screen', + 'preferences_security' => 'Security', + 'preferences_layout' => 'Layout', + 'pref_home_show_deposits' => 'Show deposits on the home screen', + 'pref_home_show_deposits_info' => 'The home screen already shows your expense accounts. Should it also show your revenue accounts?', + 'pref_home_do_show_deposits' => 'Yes, show them', + 'successful_count' => 'of which :count successful', + 'list_page_size_title' => 'Page size', + 'list_page_size_help' => 'Any list of things (accounts, transactions, etc) shows at most this many per page.', + 'list_page_size_label' => 'Page size', + 'between_dates' => '(:start and :end)', + 'pref_optional_fields_transaction' => 'Optional fields for transactions', 'pref_optional_fields_transaction_help' => 'By default not all fields are enabled when creating a new transaction (because of the clutter). Below, you can enable these fields if you think they could be useful for you. Of course, any field that is disabled, but already filled in, will be visible regardless of the setting.', 'optional_tj_date_fields' => 'Date fields', 'optional_tj_business_fields' => 'Business fields', @@ -579,24 +562,25 @@ return [ 'login_with_new_email' => 'You can now login with your new email address.', 'login_with_old_email' => 'You can now login with your old email address again.', 'login_provider_local_only' => 'This action is not available when authenticating through ":login_provider".', - 'delete_local_info_only' => 'Because you authenticate through ":login_provider", this will only delete local Firefly III information.', + 'delete_local_info_only' => 'Because you authenticate through ":login_provider", this will only delete local Firefly III information.', // attachments - 'nr_of_attachments' => 'One attachment|:count attachments', - 'attachments' => 'Attachments', - 'edit_attachment' => 'Edit attachment ":name"', - 'update_attachment' => 'Update attachment', - 'delete_attachment' => 'Delete attachment ":name"', - 'attachment_deleted' => 'Deleted attachment ":name"', - 'attachment_updated' => 'Updated attachment ":name"', - 'upload_max_file_size' => 'Maximum file size: :size', - 'list_all_attachments' => 'List of all attachments', + 'nr_of_attachments' => 'One attachment|:count attachments', + 'attachments' => 'Attachments', + 'edit_attachment' => 'Edit attachment ":name"', + 'update_attachment' => 'Update attachment', + 'delete_attachment' => 'Delete attachment ":name"', + 'attachment_deleted' => 'Deleted attachment ":name"', + 'liabilities_deleted' => 'Deleted liability ":name"', + 'attachment_updated' => 'Updated attachment ":name"', + 'upload_max_file_size' => 'Maximum file size: :size', + 'list_all_attachments' => 'List of all attachments', // transaction index - 'title_expenses' => 'Expenses', - 'title_withdrawal' => 'Expenses', - 'title_revenue' => 'Revenue / income', - 'title_deposit' => 'Revenue / income', + 'title_expenses' => 'Expenses', + 'title_withdrawal' => 'Expenses', + 'title_revenue' => 'Revenue / income', + 'title_deposit' => 'Revenue / income', 'title_transfer' => 'Transfers', 'title_transfers' => 'Transfers', @@ -634,6 +618,9 @@ return [ 'converted_to_Transfer' => 'The transaction has been converted to a transfer', 'invalid_convert_selection' => 'The account you have selected is already used in this transaction or does not exist.', 'source_or_dest_invalid' => 'Cannot find the correct transaction details. Conversion is not possible.', + 'convert_to_withdrawal' => 'Convert to a withdrawal', + 'convert_to_deposit' => 'Convert to a deposit', + 'convert_to_transfer' => 'Convert to a transfer', // create new stuff: 'create_new_withdrawal' => 'Create new withdrawal', @@ -695,7 +682,9 @@ return [ 'available_amount_indication' => 'Use these amounts to get an indication of what your total budget could be.', 'suggested' => 'Suggested', 'average_between' => 'Average between :start and :end', - 'over_budget_warn' => ' Normally you budget about :amount per day. This is :over_amount per day.', + 'over_budget_warn' => ' Usually you budget about :amount per day. This time it\'s :over_amount per day. Are you sure?', + 'transferred_in' => 'Transferred (in)', + 'transferred_away' => 'Transferred (away)', // bills: 'match_between_amounts' => 'Bill matches transactions between :low and :high.', @@ -779,6 +768,7 @@ return [ 'reconcile_options' => 'Reconciliation options', 'reconcile_range' => 'Reconciliation range', 'start_reconcile' => 'Start reconciling', + 'cash_account_type' => 'Cash', 'cash' => 'cash', 'account_type' => 'Account type', 'save_transactions_by_moving' => 'Save these transaction(s) by moving them to another account:', @@ -803,7 +793,9 @@ return [ 'reconcile_go_back' => 'You can always edit or delete a correction later.', 'must_be_asset_account' => 'You can only reconcile asset accounts', 'reconciliation_stored' => 'Reconciliation stored', - 'reconcilliation_transaction_title' => 'Reconciliation (:from to :to)', + 'reconciliation_error' => 'Due to an error the transactions were marked as reconciled but the correction has not been stored: :error.', + 'reconciliation_transaction_title' => 'Reconciliation (:from to :to)', + 'sum_of_reconciliation' => 'Sum of reconciliation', 'reconcile_this_account' => 'Reconcile this account', 'confirm_reconciliation' => 'Confirm reconciliation', 'submitted_start_balance' => 'Submitted start balance', @@ -815,7 +807,7 @@ return [ 'interest_calc_daily' => 'Per day', 'interest_calc_monthly' => 'Per month', 'interest_calc_yearly' => 'Per year', - 'initial_balance_account' => 'Initial balance account of :name', + 'initial_balance_account' => 'Initial balance account of :account', // categories: 'new_category' => 'New category', @@ -847,6 +839,8 @@ return [ 'deleted_deposit' => 'Successfully deleted deposit ":description"', 'deleted_transfer' => 'Successfully deleted transfer ":description"', 'stored_journal' => 'Successfully created new transaction ":description"', + 'stored_journal_no_descr' => 'Successfully created your new transaction', + 'updated_journal_no_descr' => 'Successfully updated your transaction', 'select_transactions' => 'Select transactions', 'rule_group_select_transactions' => 'Apply ":title" to transactions', 'rule_select_transactions' => 'Apply ":title" to transactions', @@ -855,26 +849,33 @@ return [ 'mass_delete_journals' => 'Delete a number of transactions', 'mass_edit_journals' => 'Edit a number of transactions', 'mass_bulk_journals' => 'Bulk edit a number of transactions', - 'mass_bulk_journals_explain' => 'If you do not want to change your transactions one-by-one using the mass-edit function, you can update them in one go. Simply select the preferred category, tag(s) or budget in the fields below, and all the transactions in the table will be updated.', + 'mass_bulk_journals_explain' => 'This form allows you to change properties of the transactions listed below in one sweeping update. All the transactions in the table will be updated when you change the parameters you see here.', + 'part_of_split' => 'This transaction is part of a split transaction. If you have not selected all the splits, you may end up with changing only half the transaction.', 'bulk_set_new_values' => 'Use the inputs below to set new values. If you leave them empty, they will be made empty for all. Also, note that only withdrawals will be given a budget.', 'no_bulk_category' => 'Don\'t update category', 'no_bulk_budget' => 'Don\'t update budget', - 'no_bulk_tags' => 'Don\'t update tag(s)', - 'bulk_edit' => 'Bulk edit', - 'cannot_edit_other_fields' => 'You cannot mass-edit other fields than the ones here, because there is no room to show them. Please follow the link and edit them by one-by-one, if you need to edit these fields.', - 'no_budget' => '(no budget)', - 'no_budget_squared' => '(no budget)', - 'perm-delete-many' => 'Deleting many items in one go can be very disruptive. Please be cautious.', - 'mass_deleted_transactions_success' => 'Deleted :amount transaction(s).', - 'mass_edited_transactions_success' => 'Updated :amount transaction(s)', - 'opt_group_' => '(no account type)', + 'no_bulk_tags' => 'Don\'t update tag(s)', + 'mass_edit' => 'Edit selected individually', + 'bulk_edit' => 'Edit selected in bulk', + 'mass_delete' => 'Delete selected', + 'cannot_edit_other_fields' => 'You cannot mass-edit other fields than the ones here, because there is no room to show them. Please follow the link and edit them by one-by-one, if you need to edit these fields.', + 'cannot_change_amount_reconciled' => 'You can\'t change the amount of reconciled transactions.', + 'no_budget' => '(no budget)', + 'no_budget_squared' => '(no budget)', + 'perm-delete-many' => 'Deleting many items in one go can be very disruptive. Please be cautious. You can delete part of a split transaction from this page, so take care.', + 'mass_deleted_transactions_success' => 'Deleted :amount transaction(s).', + 'mass_edited_transactions_success' => 'Updated :amount transaction(s)', + 'opt_group_' => '(no account type)', 'opt_group_no_account_type' => '(no account type)', 'opt_group_defaultAsset' => 'Default asset accounts', 'opt_group_savingAsset' => 'Savings accounts', 'opt_group_sharedAsset' => 'Shared asset accounts', 'opt_group_ccAsset' => 'Credit cards', 'opt_group_cashWalletAsset' => 'Cash wallets', + 'opt_group_expense_account' => 'Expense accounts', + 'opt_group_revenue_account' => 'Revenue accounts', 'opt_group_l_Loan' => 'Liability: Loan', + 'opt_group_cash_account' => 'Cash account', 'opt_group_l_Debt' => 'Liability: Debt', 'opt_group_l_Mortgage' => 'Liability: Mortgage', 'opt_group_l_Credit card' => 'Liability: Credit card', @@ -973,7 +974,7 @@ return [ 'errors' => 'Errors', 'debt_start_date' => 'Start date of debt', 'debt_start_amount' => 'Start amount of debt', - 'debt_start_amount_help' => 'Please enter the original amount of this liability as a positive number. You may also enter the current amount. Make sure to edit the date below to match.', + 'debt_start_amount_help' => 'If you owe an amount its best to enter a negative amount, because it influences your net worth. If you\'re owed an amount the same applies. Check out the help pages for more information.', 'store_new_liabilities_account' => 'Store new liability', 'edit_liabilities_account' => 'Edit liability ":name"', @@ -1145,6 +1146,7 @@ return [ 'deleted_piggy_bank' => 'Deleted piggy bank ":name"', 'added_amount_to_piggy' => 'Added :amount to ":name"', 'removed_amount_from_piggy' => 'Removed :amount from ":name"', + 'piggy_events' => 'Related piggy banks', // tags 'delete_tag' => 'Delete tag ":tag"', @@ -1154,47 +1156,57 @@ return [ 'updated_tag' => 'Updated tag ":tag"', 'created_tag' => 'Tag ":tag" has been created!', - 'transaction_journal_information' => 'Transaction information', - 'transaction_journal_meta' => 'Meta information', - 'total_amount' => 'Total amount', - 'number_of_decimals' => 'Number of decimals', + 'transaction_journal_information' => 'Transaction information', + 'transaction_journal_meta' => 'Meta information', + 'transaction_journal_more' => 'More information', + 'att_part_of_journal' => 'Stored under ":journal"', + 'total_amount' => 'Total amount', + 'number_of_decimals' => 'Number of decimals', // administration - 'administration' => 'Administration', - 'user_administration' => 'User administration', - 'list_all_users' => 'All users', - 'all_users' => 'All users', - 'instance_configuration' => 'Configuration', - 'firefly_instance_configuration' => 'Configuration options for Firefly III', - 'setting_single_user_mode' => 'Single user mode', - 'setting_single_user_mode_explain' => 'By default, Firefly III only accepts one (1) registration: you. This is a security measure, preventing others from using your instance unless you allow them to. Future registrations are blocked. When you uncheck this box, others can use your instance as well, assuming they can reach it (when it is connected to the internet).', - 'store_configuration' => 'Store configuration', - 'single_user_administration' => 'User administration for :email', - 'edit_user' => 'Edit user :email', - 'hidden_fields_preferences' => 'Not all fields are visible right now. You must enable them in your settings.', - 'user_data_information' => 'User data', - 'user_information' => 'User information', - 'total_size' => 'total size', - 'budget_or_budgets' => 'budget(s)', - 'budgets_with_limits' => 'budget(s) with configured amount', - 'nr_of_rules_in_total_groups' => ':count_rules rule(s) in :count_groups rule group(s)', - 'tag_or_tags' => 'tag(s)', - 'configuration_updated' => 'The configuration has been updated', - 'setting_is_demo_site' => 'Demo site', - 'setting_is_demo_site_explain' => 'If you check this box, this installation will behave as if it is the demo site, which can have weird side effects.', - 'block_code_bounced' => 'Email message(s) bounced', - 'block_code_expired' => 'Demo account expired', - 'no_block_code' => 'No reason for block or user not blocked', - 'block_code_email_changed' => 'User has not yet confirmed new email address', - 'admin_update_email' => 'Contrary to the profile page, the user will NOT be notified their email address has changed!', - 'update_user' => 'Update user', - 'updated_user' => 'User data has been changed.', - 'delete_user' => 'Delete user :email', - 'user_deleted' => 'The user has been deleted', - 'send_test_email' => 'Send test email message', - 'send_test_email_text' => 'To see if your installation is capable of sending email, please press this button. You will not see an error here (if any), the log files will reflect any errors. You can press this button as many times as you like. There is no spam control. The message will be sent to :email and should arrive shortly.', - 'send_message' => 'Send message', - 'send_test_triggered' => 'Test was triggered. Check your inbox and the log files.', + 'administration' => 'Administration', + 'user_administration' => 'User administration', + 'list_all_users' => 'All users', + 'all_users' => 'All users', + 'instance_configuration' => 'Configuration', + 'firefly_instance_configuration' => 'Configuration options for Firefly III', + 'setting_single_user_mode' => 'Single user mode', + 'setting_single_user_mode_explain' => 'By default, Firefly III only accepts one (1) registration: you. This is a security measure, preventing others from using your instance unless you allow them to. Future registrations are blocked. When you uncheck this box, others can use your instance as well, assuming they can reach it (when it is connected to the internet).', + 'store_configuration' => 'Store configuration', + 'single_user_administration' => 'User administration for :email', + 'edit_user' => 'Edit user :email', + 'hidden_fields_preferences' => 'You can enable more transaction options in your settings.', + 'user_data_information' => 'User data', + 'user_information' => 'User information', + 'total_size' => 'total size', + 'budget_or_budgets' => 'budget(s)', + 'budgets_with_limits' => 'budget(s) with configured amount', + 'nr_of_rules_in_total_groups' => ':count_rules rule(s) in :count_groups rule group(s)', + 'tag_or_tags' => 'tag(s)', + 'configuration_updated' => 'The configuration has been updated', + 'setting_is_demo_site' => 'Demo site', + 'setting_is_demo_site_explain' => 'If you check this box, this installation will behave as if it is the demo site, which can have weird side effects.', + 'block_code_bounced' => 'Email message(s) bounced', + 'block_code_expired' => 'Demo account expired', + 'no_block_code' => 'No reason for block or user not blocked', + 'block_code_email_changed' => 'User has not yet confirmed new email address', + 'admin_update_email' => 'Contrary to the profile page, the user will NOT be notified their email address has changed!', + 'update_user' => 'Update user', + 'updated_user' => 'User data has been changed.', + 'delete_user' => 'Delete user :email', + 'user_deleted' => 'The user has been deleted', + 'send_test_email' => 'Send test email message', + 'send_test_email_text' => 'To see if your installation is capable of sending email, please press this button. You will not see an error here (if any), the log files will reflect any errors. You can press this button as many times as you like. There is no spam control. The message will be sent to :email and should arrive shortly.', + 'send_message' => 'Send message', + 'send_test_triggered' => 'Test was triggered. Check your inbox and the log files.', + + 'split_transaction_title' => 'Description of the split transaction', + 'split_title_help' => 'If you create a split transaction, there must be a global description for all splits of the transaction.', + 'transaction_information' => 'Transaction information', + 'you_create_transfer' => 'You\'re creating a transfer.', + 'you_create_withdrawal' => 'You\'re creating a withdrawal.', + 'you_create_deposit' => 'You\'re creating a deposit.', + // links 'journal_link_configuration' => 'Transaction links configuration', @@ -1214,12 +1226,13 @@ return [ 'do_not_save_connection' => '(do not save connection)', 'link_transaction' => 'Link transaction', 'link_to_other_transaction' => 'Link this transaction to another transaction', - 'select_transaction_to_link' => 'Select a transaction to link this transaction to', + 'select_transaction_to_link' => 'Select a transaction to link this transaction to. The links are currently unused in Firefly III (apart from being shown), but I plan to change this in the future. Use the search box to select a transaction either by title or by ID. If you want to add custom link types, check out the administration section.', 'this_transaction' => 'This transaction', 'transaction' => 'Transaction', 'comments' => 'Comments', - 'to_link_not_found' => 'If the transaction you want to link to is not listed, simply enter its ID.', + 'link_notes' => 'Any notes you wish to store with the link.', 'invalid_link_selection' => 'Cannot link these transactions', + 'selected_transaction' => 'Selected transaction', 'journals_linked' => 'Transactions are linked.', 'journals_error_linked' => 'These transactions are already linked.', 'journals_link_to_self' => 'You cannot link a transaction to itself', @@ -1258,12 +1271,11 @@ return [ 'split_this_withdrawal' => 'Split this withdrawal', 'split_this_deposit' => 'Split this deposit', 'split_this_transfer' => 'Split this transfer', - 'cannot_edit_multiple_source' => 'You cannot edit splitted transaction #:id with description ":description" because it contains multiple source accounts.', - 'cannot_edit_multiple_dest' => 'You cannot edit splitted transaction #:id with description ":description" because it contains multiple destination accounts.', - 'cannot_edit_reconciled' => 'You cannot edit transaction #:id with description ":description" because it has been marked as reconciled.', 'cannot_edit_opening_balance' => 'You cannot edit the opening balance of an account.', 'no_edit_multiple_left' => 'You have selected no valid transactions to edit.', - 'cannot_convert_split_journal' => 'Cannot convert a split transaction', + 'breadcrumb_convert_group' => 'Convert transaction', + 'convert_invalid_source' => 'Source information is invalid for transaction #%d.', + 'convert_invalid_destination' => 'Destination information is invalid for transaction #%d.', // Import page (general strings only) 'import_index_title' => 'Import transactions into Firefly III', @@ -1389,4 +1401,15 @@ return [ 'will_jump_monday' => 'Will be created on Monday instead of the weekends.', 'except_weekends' => 'Except weekends', 'recurrence_deleted' => 'Recurring transaction ":title" deleted', + + // new lines for summary controller. + 'box_balance_in_currency' => 'Balance (:currency)', + 'box_spent_in_currency' => 'Spent (:currency)', + 'box_earned_in_currency' => 'Earned (:currency)', + 'box_bill_paid_in_currency' => 'Bills paid (:currency)', + 'box_bill_unpaid_in_currency' => 'Bills unpaid (:currency)', + 'box_left_to_spend_in_currency' => 'Left to spend (:currency)', + 'box_net_worth_in_currency' => 'Net worth (:currency)', + 'box_spend_per_day' => 'Left to spend per day: :amount', + ]; diff --git a/resources/lang/en_US/form.php b/resources/lang/en_US/form.php index 72b7030890..661ac8b976 100644 --- a/resources/lang/en_US/form.php +++ b/resources/lang/en_US/form.php @@ -57,20 +57,21 @@ return [ 'asset_source_account' => 'Source account', 'journal_description' => 'Description', 'note' => 'Notes', + 'store_new_transaction' => 'Store new transaction', 'split_journal' => 'Split this transaction', 'split_journal_explanation' => 'Split this transaction in multiple parts', 'currency' => 'Currency', 'account_id' => 'Asset account', 'budget_id' => 'Budget', - 'openingBalance' => 'Opening balance', + 'opening_balance' => 'Opening balance', 'tagMode' => 'Tag mode', 'tag_position' => 'Tag location', - 'virtualBalance' => 'Virtual balance', + 'virtual_balance' => 'Virtual balance', 'targetamount' => 'Target amount', - 'accountRole' => 'Account role', - 'openingBalanceDate' => 'Opening balance date', - 'ccType' => 'Credit card payment plan', - 'ccMonthlyPaymentDate' => 'Credit card monthly payment date', + 'account_role' => 'Account role', + 'opening_balance_date' => 'Opening balance date', + 'cc_type' => 'Credit card payment plan', + 'cc_monthly_payment_date' => 'Credit card monthly payment date', 'piggy_bank_id' => 'Piggy bank', 'returnHere' => 'Return here', 'returnHereExplanation' => 'After storing, return here to create another one.', @@ -118,7 +119,7 @@ return [ 'symbol' => 'Symbol', 'code' => 'Code', 'iban' => 'IBAN', - 'accountNumber' => 'Account number', + 'account_number' => 'Account number', 'creditCardNumber' => 'Credit card number', 'has_headers' => 'Headers', 'date_format' => 'Date format', @@ -139,12 +140,8 @@ return [ 'stop_processing' => 'Stop processing', 'start_date' => 'Start of range', 'end_date' => 'End of range', - 'export_start_range' => 'Start of export range', - 'export_end_range' => 'End of export range', - 'export_format' => 'File format', 'include_attachments' => 'Include uploaded attachments', 'include_old_uploads' => 'Include imported data', - 'accounts' => 'Export transactions from these accounts', 'delete_account' => 'Delete account ":name"', 'delete_bill' => 'Delete bill ":name"', 'delete_budget' => 'Delete budget ":name"', @@ -256,4 +253,7 @@ return [ 'weekend' => 'Weekend', 'client_secret' => 'Client secret', + 'withdrawal_destination_id' => 'Destination account', + 'deposit_source_id' => 'Source account', + ]; diff --git a/resources/lang/en_US/import.php b/resources/lang/en_US/import.php index 3f19501654..3a907af062 100644 --- a/resources/lang/en_US/import.php +++ b/resources/lang/en_US/import.php @@ -207,6 +207,10 @@ return [ 'specific_rabo_descr' => 'Fixes potential problems with Rabobank files', 'specific_pres_name' => 'President\'s Choice Financial CA', 'specific_pres_descr' => 'Fixes potential problems with PC files', + 'specific_belfius_name' => 'Belfius BE', + 'specific_belfius_descr' => 'Fixes potential problems with Belfius files', + 'specific_ingbelgium_name' => 'ING BE', + 'specific_ingbelgium_descr' => 'Fixes potential problems with ING Belgium files', // job configuration for file provider (stage: roles) 'job_config_roles_title' => 'Import setup (3/4) - Define each column\'s role', 'job_config_roles_text' => 'Each column in your CSV file contains certain data. Please indicate what kind of data the importer should expect. The option to "map" data means that you will link each entry found in the column to a value in your database. An often mapped column is the column that contains the IBAN of the opposing account. That can be easily matched to IBAN\'s present in your database already.', @@ -291,14 +295,14 @@ return [ 'column_rabo-debit-credit' => 'Rabobank specific debit/credit indicator', 'column_ing-debit-credit' => 'ING specific debit/credit indicator', 'column_generic-debit-credit' => 'Generic bank debit/credit indicator', - 'column_sepa-ct-id' => 'SEPA end-to-end Identifier', - 'column_sepa-ct-op' => 'SEPA Opposing Account Identifier', - 'column_sepa-db' => 'SEPA Mandate Identifier', - 'column_sepa-cc' => 'SEPA Clearing Code', - 'column_sepa-ci' => 'SEPA Creditor Identifier', - 'column_sepa-ep' => 'SEPA External Purpose', - 'column_sepa-country' => 'SEPA Country Code', - 'column_sepa-batch-id' => 'SEPA Batch ID', + 'column_sepa_ct_id' => 'SEPA end-to-end Identifier', + 'column_sepa_ct_op' => 'SEPA Opposing Account Identifier', + 'column_sepa_db' => 'SEPA Mandate Identifier', + 'column_sepa_cc' => 'SEPA Clearing Code', + 'column_sepa_ci' => 'SEPA Creditor Identifier', + 'column_sepa_ep' => 'SEPA External Purpose', + 'column_sepa_country' => 'SEPA Country Code', + 'column_sepa_batch_id' => 'SEPA Batch ID', 'column_tags-comma' => 'Tags (comma separated)', 'column_tags-space' => 'Tags (space separated)', 'column_account-number' => 'Asset account (account number)', @@ -306,4 +310,7 @@ return [ 'column_note' => 'Note(s)', 'column_internal-reference' => 'Internal reference', + // error message + 'duplicate_row' => 'Row #:row (":description") could not be imported. It already exists.', + ]; diff --git a/resources/lang/en_US/intro.php b/resources/lang/en_US/intro.php index 535057c8c6..c3451393f5 100644 --- a/resources/lang/en_US/intro.php +++ b/resources/lang/en_US/intro.php @@ -24,39 +24,61 @@ declare(strict_types=1); return [ // index - 'index_intro' => 'Welcome to the index page of Firefly III. Please take the time to walk through this intro to get a feeling of how Firefly III works.', - 'index_accounts-chart' => 'This chart shows the current balance of your asset accounts. You can select the accounts visible here in your preferences.', - 'index_box_out_holder' => 'This little box and the boxes next to this one will give you a quick overview of your financial situation.', - 'index_help' => 'If you ever need help with a page or a form, press this button.', - 'index_outro' => 'Most pages of Firefly III will start with a little tour like this one. Please contact me when you have questions or comments. Enjoy!', - 'index_sidebar-toggle' => 'To create new transactions, accounts or other things, use the menu under this icon.', + 'index_intro' => 'Welcome to the index page of Firefly III. Please take the time to walk through this intro to get a feeling of how Firefly III works.', + 'index_accounts-chart' => 'This chart shows the current balance of your asset accounts. You can select the accounts visible here in your preferences.', + 'index_box_out_holder' => 'This little box and the boxes next to this one will give you a quick overview of your financial situation.', + 'index_help' => 'If you ever need help with a page or a form, press this button.', + 'index_outro' => 'Most pages of Firefly III will start with a little tour like this one. Please contact me when you have questions or comments. Enjoy!', + 'index_sidebar-toggle' => 'To create new transactions, accounts or other things, use the menu under this icon.', + 'index_cash_account' => 'These are the accounts created so far. You can use the cash account to track cash expenses but it\'s not mandatory of course.', + + // transactions (withdrawal) + 'transactions_create_withdrawal_source' => 'Select your favorite asset account or liability from this dropdown.', + 'transactions_create_withdrawal_destination' => 'Select an expense account here. Leave it empty if you want to make a cash expense.', + 'transactions_create_withdrawal_foreign_currency' => 'Use this field to set a foreign currency and amount.', + 'transactions_create_withdrawal_more_meta' => 'Plenty of other meta data you set in these fields.', + 'transactions_create_withdrawal_split_add' => 'If you want to split a transaction, add more splits with this button', + + // transactions (deposit) + 'transactions_create_deposit_source' => 'Select or type the payee in this auto-completing dropdown/textbox. Leave it empty if you want to make a cash deposit.', + 'transactions_create_deposit_destination' => 'Select an asset or liability account here.', + 'transactions_create_deposit_foreign_currency' => 'Use this field to set a foreign currency and amount.', + 'transactions_create_deposit_more_meta' => 'Plenty of other meta data you set in these fields.', + 'transactions_create_deposit_split_add' => 'If you want to split a transaction, add more splits with this button', + + // transactions (transfer) + 'transactions_create_transfer_source' => 'Select the source asset account here.', + 'transactions_create_transfer_destination' => 'Select the destination asset account here.', + 'transactions_create_transfer_foreign_currency' => 'Use this field to set a foreign currency and amount.', + 'transactions_create_transfer_more_meta' => 'Plenty of other meta data you set in these fields.', + 'transactions_create_transfer_split_add' => 'If you want to split a transaction, add more splits with this button', // create account: - 'accounts_create_iban' => 'Give your accounts a valid IBAN. This could make a data import very easy in the future.', - 'accounts_create_asset_opening_balance' => 'Assets accounts may have an "opening balance", indicating the start of this account\'s history in Firefly III.', - 'accounts_create_asset_currency' => 'Firefly III supports multiple currencies. Asset accounts have one main currency, which you must set here.', - 'accounts_create_asset_virtual' => 'It can sometimes help to give your account a virtual balance: an extra amount always added to or removed from the actual balance.', + 'accounts_create_iban' => 'Give your accounts a valid IBAN. This could make a data import very easy in the future.', + 'accounts_create_asset_opening_balance' => 'Assets accounts may have an "opening balance", indicating the start of this account\'s history in Firefly III.', + 'accounts_create_asset_currency' => 'Firefly III supports multiple currencies. Asset accounts have one main currency, which you must set here.', + 'accounts_create_asset_virtual' => 'It can sometimes help to give your account a virtual balance: an extra amount always added to or removed from the actual balance.', // budgets index - 'budgets_index_intro' => 'Budgets are used to manage your finances and form one of the core functions of Firefly III.', - 'budgets_index_set_budget' => 'Set your total budget for every period so Firefly III can tell you if you have budgeted all available money.', - 'budgets_index_see_expenses_bar' => 'Spending money will slowly fill this bar.', - 'budgets_index_navigate_periods' => 'Navigate through periods to easily set budgets ahead of time.', - 'budgets_index_new_budget' => 'Create new budgets as you see fit.', - 'budgets_index_list_of_budgets' => 'Use this table to set the amounts for each budget and see how you are doing.', - 'budgets_index_outro' => 'To learn more about budgeting, checkout the help icon in the top right corner.', + 'budgets_index_intro' => 'Budgets are used to manage your finances and form one of the core functions of Firefly III.', + 'budgets_index_set_budget' => 'Set your total budget for every period so Firefly III can tell you if you have budgeted all available money.', + 'budgets_index_see_expenses_bar' => 'Spending money will slowly fill this bar.', + 'budgets_index_navigate_periods' => 'Navigate through periods to easily set budgets ahead of time.', + 'budgets_index_new_budget' => 'Create new budgets as you see fit.', + 'budgets_index_list_of_budgets' => 'Use this table to set the amounts for each budget and see how you are doing.', + 'budgets_index_outro' => 'To learn more about budgeting, checkout the help icon in the top right corner.', // reports (index) - 'reports_index_intro' => 'Use these reports to get detailed insights in your finances.', - 'reports_index_inputReportType' => 'Pick a report type. Check out the help pages to see what each report shows you.', - 'reports_index_inputAccountsSelect' => 'You can exclude or include asset accounts as you see fit.', - 'reports_index_inputDateRange' => 'The selected date range is entirely up to you: from one day to 10 years.', - 'reports_index_extra-options-box' => 'Depending on the report you have selected, you can select extra filters and options here. Watch this box when you change report types.', + 'reports_index_intro' => 'Use these reports to get detailed insights in your finances.', + 'reports_index_inputReportType' => 'Pick a report type. Check out the help pages to see what each report shows you.', + 'reports_index_inputAccountsSelect' => 'You can exclude or include asset accounts as you see fit.', + 'reports_index_inputDateRange' => 'The selected date range is entirely up to you: from one day to 10 years.', + 'reports_index_extra-options-box' => 'Depending on the report you have selected, you can select extra filters and options here. Watch this box when you change report types.', // reports (reports) - 'reports_report_default_intro' => 'This report will give you a quick and comprehensive overview of your finances. If you wish to see anything else, please don\'t hestitate to contact me!', - 'reports_report_audit_intro' => 'This report will give you detailed insights in your asset accounts.', - 'reports_report_audit_optionsBox' => 'Use these check boxes to show or hide the columns you are interested in.', + 'reports_report_default_intro' => 'This report will give you a quick and comprehensive overview of your finances. If you wish to see anything else, please don\'t hestitate to contact me!', + 'reports_report_audit_intro' => 'This report will give you detailed insights in your asset accounts.', + 'reports_report_audit_optionsBox' => 'Use these check boxes to show or hide the columns you are interested in.', 'reports_report_category_intro' => 'This report will give you insight in one or multiple categories.', 'reports_report_category_pieCharts' => 'These charts will give you insight in expenses and income per category or per account.', diff --git a/resources/lang/en_US/list.php b/resources/lang/en_US/list.php index 4a50b795c8..d1556419fc 100644 --- a/resources/lang/en_US/list.php +++ b/resources/lang/en_US/list.php @@ -23,71 +23,71 @@ declare(strict_types=1); return [ - 'buttons' => 'Buttons', - 'icon' => 'Icon', - 'id' => 'ID', - 'create_date' => 'Created at', - 'update_date' => 'Updated at', - 'updated_at' => 'Updated at', - 'balance_before' => 'Balance before', - 'balance_after' => 'Balance after', - 'name' => 'Name', - 'role' => 'Role', - 'currentBalance' => 'Current balance', - 'linked_to_rules' => 'Relevant rules', - 'active' => 'Is active?', - 'lastActivity' => 'Last activity', - 'balanceDiff' => 'Balance difference', - 'matchesOn' => 'Matched on', - 'account_type' => 'Account type', - 'created_at' => 'Created at', - 'account' => 'Account', - 'matchingAmount' => 'Amount', - 'split_number' => 'Split #', - 'destination' => 'Destination', - 'source' => 'Source', - 'next_expected_match' => 'Next expected match', - 'automatch' => 'Auto match?', - 'repeat_freq' => 'Repeats', - 'description' => 'Description', - 'amount' => 'Amount', - 'internal_reference' => 'Internal reference', - 'date' => 'Date', - 'interest_date' => 'Interest date', - 'book_date' => 'Book date', - 'process_date' => 'Processing date', - 'due_date' => 'Due date', - 'payment_date' => 'Payment date', - 'invoice_date' => 'Invoice date', - 'interal_reference' => 'Internal reference', - 'notes' => 'Notes', - 'from' => 'From', - 'piggy_bank' => 'Piggy bank', - 'to' => 'To', - 'budget' => 'Budget', - 'category' => 'Category', - 'bill' => 'Bill', - 'withdrawal' => 'Withdrawal', - 'deposit' => 'Deposit', - 'transfer' => 'Transfer', - 'type' => 'Type', - 'completed' => 'Completed', - 'iban' => 'IBAN', - 'paid_current_period' => 'Paid this period', - 'email' => 'Email', - 'registered_at' => 'Registered at', - 'is_blocked' => 'Is blocked', - 'is_admin' => 'Is admin', - 'has_two_factor' => 'Has 2FA', - 'blocked_code' => 'Block code', - 'source_account' => 'Source account', + 'buttons' => 'Buttons', + 'icon' => 'Icon', + 'id' => 'ID', + 'create_date' => 'Created at', + 'update_date' => 'Updated at', + 'updated_at' => 'Updated at', + 'balance_before' => 'Balance before', + 'balance_after' => 'Balance after', + 'name' => 'Name', + 'role' => 'Role', + 'currentBalance' => 'Current balance', + 'linked_to_rules' => 'Relevant rules', + 'active' => 'Is active?', + 'transaction_type' => 'Type', + 'lastActivity' => 'Last activity', + 'balanceDiff' => 'Balance difference', + 'matchesOn' => 'Matched on', + 'account_type' => 'Account type', + 'created_at' => 'Created at', + 'account' => 'Account', + 'matchingAmount' => 'Amount', + 'split_number' => 'Split #', + 'destination' => 'Destination', + 'source' => 'Source', + 'next_expected_match' => 'Next expected match', + 'automatch' => 'Auto match?', + 'repeat_freq' => 'Repeats', + 'description' => 'Description', + 'amount' => 'Amount', + 'internal_reference' => 'Internal reference', + 'date' => 'Date', + 'interest_date' => 'Interest date', + 'book_date' => 'Book date', + 'process_date' => 'Processing date', + 'due_date' => 'Due date', + 'payment_date' => 'Payment date', + 'invoice_date' => 'Invoice date', + 'interal_reference' => 'Internal reference', + 'notes' => 'Notes', + 'from' => 'From', + 'piggy_bank' => 'Piggy bank', + 'to' => 'To', + 'budget' => 'Budget', + 'category' => 'Category', + 'bill' => 'Bill', + 'withdrawal' => 'Withdrawal', + 'deposit' => 'Deposit', + 'transfer' => 'Transfer', + 'type' => 'Type', + 'completed' => 'Completed', + 'iban' => 'IBAN', + 'paid_current_period' => 'Paid this period', + 'email' => 'Email', + 'registered_at' => 'Registered at', + 'is_blocked' => 'Is blocked', + 'is_admin' => 'Is admin', + 'has_two_factor' => 'Has 2FA', + 'blocked_code' => 'Block code', + 'source_account' => 'Source account', 'destination_account' => 'Destination account', 'accounts_count' => 'Number of accounts', 'journals_count' => 'Number of transactions', 'attachments_count' => 'Number of attachments', 'bills_count' => 'Number of bills', 'categories_count' => 'Number of categories', - 'export_jobs_count' => 'Number of export jobs', 'import_jobs_count' => 'Number of import jobs', 'budget_count' => 'Number of budgets', 'rule_and_groups_count' => 'Number of rules and rule groups', @@ -106,14 +106,14 @@ return [ 'account_on_spectre' => 'Account (Spectre)', 'account_on_ynab' => 'Account (YNAB)', 'do_import' => 'Import from this account', - 'sepa-ct-id' => 'SEPA End to End Identifier', - 'sepa-ct-op' => 'SEPA Opposing Account Identifier', - 'sepa-db' => 'SEPA Mandate Identifier', - 'sepa-country' => 'SEPA Country', - 'sepa-cc' => 'SEPA Clearing Code', - 'sepa-ep' => 'SEPA External Purpose', - 'sepa-ci' => 'SEPA Creditor Identifier', - 'sepa-batch-id' => 'SEPA Batch ID', + 'sepa_ct_id' => 'SEPA End to End Identifier', + 'sepa_ct_op' => 'SEPA Opposing Account Identifier', + 'sepa_db' => 'SEPA Mandate Identifier', + 'sepa_country' => 'SEPA Country', + 'sepa_cc' => 'SEPA Clearing Code', + 'sepa_ep' => 'SEPA External Purpose', + 'sepa_ci' => 'SEPA Creditor Identifier', + 'sepa_batch_id' => 'SEPA Batch ID', 'external_id' => 'External ID', 'account_at_bunq' => 'Account with bunq', 'file_name' => 'File name', diff --git a/resources/lang/en_US/validation.php b/resources/lang/en_US/validation.php index 6bbd33b028..a88bd5a43b 100644 --- a/resources/lang/en_US/validation.php +++ b/resources/lang/en_US/validation.php @@ -33,15 +33,19 @@ return [ 'rule_trigger_value' => 'This value is invalid for the selected trigger.', 'rule_action_value' => 'This value is invalid for the selected action.', 'file_already_attached' => 'Uploaded file ":name" is already attached to this object.', - 'file_attached' => 'Succesfully uploaded file ":name".', + 'file_attached' => 'Successfully uploaded file ":name".', 'must_exist' => 'The ID in field :attribute does not exist in the database.', 'all_accounts_equal' => 'All accounts in this field must be equal.', + 'group_title_mandatory' => 'A group title is mandatory when there is more than one transaction.', + 'transaction_types_equal' => 'All splits must be of the same type.', + 'invalid_transaction_type' => 'Invalid transaction type.', 'invalid_selection' => 'Your selection is invalid.', 'belongs_user' => 'This value is invalid for this field.', 'at_least_one_transaction' => 'Need at least one transaction.', 'at_least_one_repetition' => 'Need at least one repetition.', 'require_repeat_until' => 'Require either a number of repetitions, or an end date (repeat_until). Not both.', 'require_currency_info' => 'The content of this field is invalid without currency information.', + 'require_currency_amount' => 'The content of this field is invalid without foreign amount information.', 'equal_description' => 'Transaction description should not equal global description.', 'file_invalid_mime' => 'File ":name" is of type ":mime" which is not accepted as a new upload.', 'file_too_large' => 'File ":name" is too large.', @@ -135,8 +139,8 @@ return [ 'name' => 'name', 'piggy_bank_id' => 'piggy bank ID', 'targetamount' => 'target amount', - 'openingBalanceDate' => 'opening balance date', - 'openingBalance' => 'opening balance', + 'opening_balance_date' => 'opening balance date', + 'opening_balance' => 'opening balance', 'match' => 'match', 'amount_min' => 'minimum amount', 'amount_max' => 'maximum amount', @@ -164,4 +168,28 @@ return [ 'rule-trigger.4' => 'rule trigger #4', 'rule-trigger.5' => 'rule trigger #5', ], + + // validation of accounts: + 'withdrawal_source_need_data' => 'Need to get a valid source account ID and/or valid source account name to continue.', + 'withdrawal_source_bad_data' => 'Could not find a valid source account when searching for ID ":id" or name ":name".', + 'withdrawal_dest_need_data' => 'Need to get a valid destination account ID and/or valid destination account name to continue.', + 'withdrawal_dest_bad_data' => 'Could not find a valid destination account when searching for ID ":id" or name ":name".', + + 'deposit_source_need_data' => 'Need to get a valid source account ID and/or valid source account name to continue.', + 'deposit_source_bad_data' => 'Could not find a valid source account when searching for ID ":id" or name ":name".', + 'deposit_dest_need_data' => 'Need to get a valid destination account ID and/or valid destination account name to continue.', + 'deposit_dest_bad_data' => 'Could not find a valid destination account when searching for ID ":id" or name ":name".', + + 'transfer_source_need_data' => 'Need to get a valid source account ID and/or valid source account name to continue.', + 'transfer_source_bad_data' => 'Could not find a valid source account when searching for ID ":id" or name ":name".', + 'transfer_dest_need_data' => 'Need to get a valid destination account ID and/or valid destination account name to continue.', + 'transfer_dest_bad_data' => 'Could not find a valid destination account when searching for ID ":id" or name ":name".', + 'need_id_in_edit' => 'Each split must have transaction_journal_id (either valid ID or 0).', + + 'ob_source_need_data' => 'Need to get a valid source account ID and/or valid source account name to continue.', + 'ob_dest_need_data' => 'Need to get a valid destination account ID and/or valid destination account name to continue.', + 'ob_dest_bad_data' => 'Could not find a valid destination account when searching for ID ":id" or name ":name".', + + 'generic_invalid_source' => 'You can\'t use this account as the source account.', + 'generic_invalid_destination' => 'You can\'t use this account as the destination account.', ]; diff --git a/resources/lang/es_ES/breadcrumbs.php b/resources/lang/es_ES/breadcrumbs.php index bb7793aee1..13a6565f17 100644 --- a/resources/lang/es_ES/breadcrumbs.php +++ b/resources/lang/es_ES/breadcrumbs.php @@ -23,35 +23,37 @@ declare(strict_types=1); return [ - 'home' => 'Inicio', - 'edit_currency' => 'Editar moneda ":name"', - 'delete_currency' => 'Eliminar moneda ":name"', - 'newPiggyBank' => 'Crear nueva hucha', - 'edit_piggyBank' => 'Editar hucha ":name"', - 'preferences' => 'Preferencias', - 'profile' => 'Perfil', - 'changePassword' => 'Cambiar contraseña', - 'change_email' => 'Cambiar su dirección de correo electrónico', - 'bills' => 'Facturas', - 'newBill' => 'Nueva factura', - 'edit_bill' => 'Editar factura ":name"', - 'delete_bill' => 'Eliminar factura ":name"', - 'reports' => 'Informes', - 'search_result' => 'Resultados de la búsqueda para ":query"', - 'withdrawal_list' => 'Gastos', - 'deposit_list' => 'Ganancia, ingresos y depósitos', - 'transfer_list' => 'Transferencias', - 'transfers_list' => 'Transferencias', - 'reconciliation_list' => 'Reconciliaciones', - 'create_withdrawal' => 'Crear nuevo retiro', - 'create_deposit' => 'Crear nuevo depósito', - 'create_transfer' => 'Crear nueva transferencia', - 'edit_journal' => 'Editar transacción ":description"', - 'edit_reconciliation' => 'Editar ":description"', - 'delete_journal' => 'Eliminar transacción ":description"', - 'tags' => 'Etiquetas', - 'createTag' => 'Crear nueva etiqueta', - 'edit_tag' => 'Editar etiqueta ":tag"', - 'delete_tag' => 'Eliminar etiqueta ":tag"', - 'delete_journal_link' => 'Eliminar enlace entre transacciones', + 'home' => 'Inicio', + 'edit_currency' => 'Editar moneda ":name"', + 'delete_currency' => 'Eliminar moneda ":name"', + 'newPiggyBank' => 'Crear nueva hucha', + 'edit_piggyBank' => 'Editar hucha ":name"', + 'preferences' => 'Preferencias', + 'profile' => 'Perfil', + 'changePassword' => 'Cambiar contraseña', + 'change_email' => 'Cambiar su dirección de correo electrónico', + 'bills' => 'Facturas', + 'newBill' => 'Nueva factura', + 'edit_bill' => 'Editar factura ":name"', + 'delete_bill' => 'Eliminar factura ":name"', + 'reports' => 'Informes', + 'search_result' => 'Resultados de la búsqueda para ":query"', + 'withdrawal_list' => 'Gastos', + 'Withdrawal_list' => 'Expenses', + 'deposit_list' => 'Ganancia, ingresos y depósitos', + 'transfer_list' => 'Transferencias', + 'transfers_list' => 'Transferencias', + 'reconciliation_list' => 'Reconciliaciones', + 'create_withdrawal' => 'Crear nuevo retiro', + 'create_deposit' => 'Crear nuevo depósito', + 'create_transfer' => 'Crear nueva transferencia', + 'create_new_transaction' => 'Crear una nueva transacción', + 'edit_journal' => 'Editar transacción ":description"', + 'edit_reconciliation' => 'Editar ":description"', + 'delete_journal' => 'Eliminar transacción ":description"', + 'tags' => 'Etiquetas', + 'createTag' => 'Crear nueva etiqueta', + 'edit_tag' => 'Editar etiqueta ":tag"', + 'delete_tag' => 'Eliminar etiqueta ":tag"', + 'delete_journal_link' => 'Eliminar enlace entre transacciones', ]; diff --git a/resources/lang/es_ES/firefly.php b/resources/lang/es_ES/firefly.php index 8b88851e88..f7f63195aa 100644 --- a/resources/lang/es_ES/firefly.php +++ b/resources/lang/es_ES/firefly.php @@ -33,7 +33,7 @@ return [ 'last_seven_days' => 'Últimos siete días', 'last_thirty_days' => 'Últimos treinta días', 'welcomeBack' => '¿Qué está pasando?', - 'welcome_back' => '¿Qué está pasando?', + 'welcome_back' => '¿Qué está pasando?', 'everything' => 'Todo', 'today' => 'hoy', 'customRange' => 'Rango personalizado', @@ -54,6 +54,7 @@ return [ 'create_new_stuff' => 'Crear nueva etiqueta', 'new_withdrawal' => 'Nuevo retiro', 'create_new_transaction' => 'Crear una nueva transacción', + 'new_transaction' => 'Nueva transacción', 'go_to_asset_accounts' => 'Ver tus cuentas de activos', 'go_to_budgets' => 'Ir a tus presupuestos', 'go_to_categories' => 'Ir a tus categorías', @@ -82,28 +83,32 @@ return [ 'help_for_this_page' => 'Ayuda para esta página', 'no_help_could_be_found' => 'No se encontró un texto de ayuda.', 'no_help_title' => 'Disculpas, un error ha ocurrido.', - 'two_factor_welcome' => 'Hola %{user}!', - 'two_factor_enter_code' => 'Para continuar, introduce tu código de autenticación de dos pasos. La aplicación puede generarlo para usted.', - 'two_factor_code_here' => 'Ingresar código aquí', - 'two_factor_title' => 'Autenticación en dos pasos', - 'authenticate' => 'Autentificar', - 'two_factor_forgot_title' => 'Autenticación en dos pasos perdida', - 'two_factor_forgot' => 'Olvidé mi cosa de dos factores.', - 'two_factor_lost_header' => '¿Perdiste tu autentificación de dos factores?', - 'two_factor_lost_intro' => 'Por desgracia, esto no es algo que se pueda restablecer desde la interfaz web. Tienes dos opciones.', - 'two_factor_lost_fix_self' => 'Si usted ejecuta su propia instancia de Firefly III, revise los registros en storage/logs para tener instrucciones.', - 'two_factor_lost_fix_owner' => 'De lo contrario, comuníquese por mail con el dueño del sitio, :site_owner y pídale que restablezca su autenticación de dos pasos.', - 'warning_much_data' => ':days días de datos pueden tomar tiempo en cargarse.', - 'registered' => '¡Te has registrado con éxito!', - 'Default asset account' => 'Cuenta de ingresos por defecto', - 'no_budget_pointer' => 'Parece que aún no tiene presupuestos. Debe crear algunos en la página presupuestos. Los presupuestos pueden ayudarle a realizar un seguimiento de los gastos.', - 'Savings account' => 'Cuenta de ahorros', - 'Credit card' => 'Tarjeta de crédito', - 'source_accounts' => 'Cuenta(s) origen', - 'destination_accounts' => 'Cuenta(s) destino', - 'user_id_is' => 'Tu id de usuario es :user', - 'field_supports_markdown' => 'Este campo admite Markdown.', - 'need_more_help' => 'Si necesita más ayuda con Firefly III, por favor abre un ticket en Github.', + 'two_factor_welcome' => 'Hello!', + 'two_factor_enter_code' => 'Para continuar, introduce tu código de autenticación de dos pasos. La aplicación puede generarlo para usted.', + 'two_factor_code_here' => 'Ingresar código aquí', + 'two_factor_title' => 'Autenticación en dos pasos', + 'authenticate' => 'Autentificar', + 'two_factor_forgot_title' => 'Autenticación en dos pasos perdida', + 'two_factor_forgot' => 'Olvidé mi cosa de dos factores.', + 'two_factor_lost_header' => '¿Perdiste tu autentificación de dos factores?', + 'two_factor_lost_intro' => 'If you lost your backup codes as well, you have bad luck. This is not something you can fix from the web interface. You have two choices.', + 'two_factor_lost_fix_self' => 'If you run your own instance of Firefly III, check the logs in storage/logs for instructions, or run docker logs <container_id> to see the instructions (refresh this page).', + 'two_factor_lost_fix_owner' => 'De lo contrario, comuníquese por mail con el dueño del sitio, :site_owner y pídale que restablezca su autenticación de dos pasos.', + 'mfa_backup_code' => 'You have used a backup code to login to Firefly III. It can\'t be used again, so cross it from your list.', + 'pref_two_factor_new_backup_codes' => 'Get new backup codes', + 'pref_two_factor_backup_code_count' => 'You have :count valid backup code(s).', + '2fa_i_have_them' => 'I stored them!', + 'warning_much_data' => ':days días de datos pueden tomar tiempo en cargarse.', + 'registered' => '¡Te has registrado con éxito!', + 'Default asset account' => 'Cuenta de ingresos por defecto', + 'no_budget_pointer' => 'Parece que aún no tiene presupuestos. Debe crear algunos en la página presupuestos. Los presupuestos pueden ayudarle a realizar un seguimiento de los gastos.', + 'Savings account' => 'Cuenta de ahorros', + 'Credit card' => 'Tarjeta de crédito', + 'source_accounts' => 'Cuenta(s) origen', + 'destination_accounts' => 'Cuenta(s) destino', + 'user_id_is' => 'Tu id de usuario es :user', + 'field_supports_markdown' => 'Este campo admite Markdown.', + 'need_more_help' => 'Si necesita más ayuda con Firefly III, por favor abre un ticket en Github.', 'reenable_intro_text' => 'También puede volver a activar la guía de introducción.', 'intro_boxes_after_refresh' => 'Los cuadros de introducción volverán a aparecer cuando actualices la página.', 'show_all_no_filter' => 'Mostrar todas las transacciones sin agruparlas por fecha.', @@ -220,21 +225,23 @@ return [ 'search_query' => 'Consulta', 'search_found_transactions' => 'Firefly III encontró :count transacción(es) en :time segundos.', 'search_for_query' => 'Firefly III está buscando transacciones que contengan todas estas palabras: :query', - 'search_modifier_amount_is' => 'Cantidad es exactamente :value', - 'search_modifier_amount' => 'Cantidad es exactamente :value', - 'search_modifier_amount_max' => 'Cantidad máxima :value', - 'search_modifier_amount_min' => 'Cantidad mínima :value', - 'search_modifier_amount_less' => 'Cantidad menor que :value', - 'search_modifier_amount_more' => 'Cantidad es más de :value', - 'search_modifier_source' => 'Cuenta de origen es :value', - 'search_modifier_destination' => 'Cuenta de destino es :value', - 'search_modifier_category' => 'Categoría es :value', - 'search_modifier_budget' => 'Presupuesto es :value', - 'search_modifier_bill' => 'Factura es :value', - 'search_modifier_type' => 'Tipo de transacción es :value', - 'search_modifier_date' => 'Fecha de transacción es :value', - 'search_modifier_date_before' => 'Fecha de transacción es anterior a :value', - 'search_modifier_date_after' => 'Fecha de transacción es posterior a :value', + 'search_modifier_amount_is' => 'Cantidad es exactamente :value', + 'search_modifier_amount' => 'Cantidad es exactamente :value', + 'search_modifier_amount_max' => 'Cantidad máxima :value', + 'search_modifier_amount_min' => 'Cantidad mínima :value', + 'search_modifier_amount_less' => 'Cantidad menor que :value', + 'search_modifier_amount_more' => 'Cantidad es más de :value', + 'search_modifier_source' => 'Cuenta de origen es :value', + 'search_modifier_from' => 'Source account is :value', + 'search_modifier_destination' => 'Cuenta de destino es :value', + 'search_modifier_to' => 'Destination account is :value', + 'search_modifier_category' => 'Categoría es :value', + 'search_modifier_budget' => 'Presupuesto es :value', + 'search_modifier_bill' => 'Factura es :value', + 'search_modifier_type' => 'Tipo de transacción es :value', + 'search_modifier_date' => 'Fecha de transacción es :value', + 'search_modifier_date_before' => 'Fecha de transacción es anterior a :value', + 'search_modifier_date_after' => 'Fecha de transacción es posterior a :value', 'search_modifier_on' => 'Fecha de transacción es :value', 'search_modifier_before' => 'Fecha de transacción es anterior a :value', 'search_modifier_after' => 'Fecha de transacción es posterior a :value', @@ -257,33 +264,6 @@ return [ 'half-year' => 'cada medio año', 'yearly' => 'anualmente', - // export data: - 'import_and_export' => 'Importar y exportar', - 'export_data' => 'Exportar datos', - 'export_and_backup_data' => 'Exportar datos', - 'export_data_intro' => 'Use los datos exportados para pasar a una nueva aplicación financiera. Por favor tenga en cuenta que estos archivos no son un respaldo. No contienen suficiente metadatos para restaurar completamente una nueva instalación de Firefly III. Si usted desea realizar un respaldo de sus datos, por favor haga un respaldo de la base de datos directamente.', - 'export_format' => 'Formato de exportación', - 'export_format_csv' => 'Las comas separan valores (archivos CSV)', - 'export_format_mt940' => 'MT940 formato compatible', - 'include_old_uploads_help' => 'Firefly III no arroja los archivos originales CVS que usted importo en el pasado. Usted puede incluirlos en su informe.', - 'do_export' => 'Exportar', - 'export_status_never_started' => 'La exportación no ha comenzado aun', - 'export_status_make_exporter' => 'Creando cosa del exportador...', - 'export_status_collecting_journals' => 'Recolectando sus transacciones...', - 'export_status_collected_journals' => '¡Recolectadas sus transacciones!', - 'export_status_converting_to_export_format' => 'Convirtiendo tus transacciones...', - 'export_status_converted_to_export_format' => '¡Convertidas tus transacciones!', - 'export_status_creating_journal_file' => 'Creando el archivo de exportación...', - 'export_status_created_journal_file' => '¡El archivo de exportación creado!', - 'export_status_collecting_attachments' => 'Recolectando todos tus accesorios...', - 'export_status_collected_attachments' => '¡Recolectados todos sus accesorios!', - 'export_status_collecting_old_uploads' => 'Recolectando todas sus cargas anteriores...', - 'export_status_collected_old_uploads' => 'Recolectando todas sus cargas anteriores!', - 'export_status_creating_zip_file' => 'Creando un archivo zip...', - 'export_status_created_zip_file' => '¡Se creo un archivo zip!', - 'export_status_finished' => 'Exportación ha acabado con éxito! Hurra!', - 'export_data_please_wait' => 'Espera, por favor...', - // rules 'rules' => 'Reglas', 'rule_name' => 'Nombre de la regla', @@ -502,30 +482,33 @@ return [ 'pref_custom_fiscal_year_help' => 'En países que utilizan año fiscal diferente del 1 al 31 de diciembre, usted puede cambiarlo y especificar los días de inicio / y termino del año fiscal', 'pref_fiscal_year_start_label' => 'Fecha de inicio del año fiscal', 'pref_two_factor_auth' => 'Verificación en 2 pasos', - 'pref_two_factor_auth_help' => 'Cuando usted habilita la verificación en 2 pasos ( también conocida como autenticacion de dos factores) usted agrega una capa adicional de seguridad a su cuenta. usted inicia la sesión que conoce (código de verificación). los códigos de generación son generados por una aplicación de su teléfono, tales como Authy o Google Authenticador.', - 'pref_enable_two_factor_auth' => 'Permita la verificación de 2 pasos', - 'pref_two_factor_auth_disabled' => 'Codigo de verificacion en 2 pasos removido y inabilitado', - 'pref_two_factor_auth_remove_it' => 'No olvide eliminar la cuenta de su aplicación de autenticacion!', - 'pref_two_factor_auth_code' => 'Verificar código', - 'pref_two_factor_auth_code_help' => 'Escanee el código QR con una aplicación en su teléfono como Authy o Google autenticator y ingrese el código generado.', - 'pref_two_factor_auth_reset_code' => 'Reiniciar código de verificación', - 'pref_two_factor_auth_disable_2fa' => 'Deshabilitar 2FA', - '2fa_use_secret_instead' => 'Si no puede escanear el código QR, no dude en utilizar el secreto en su lugar: :secret.', - 'pref_save_settings' => 'Guardar la configuración', - 'saved_preferences' => '¡Preferencias guardadas!', - 'preferences_general' => 'General', - 'preferences_frontpage' => 'Pantalla de inicio', - 'preferences_security' => 'Seguridad', - 'preferences_layout' => 'Diseño', - 'pref_home_show_deposits' => 'Mostrar los depósitos en la pantalla de inicio', - 'pref_home_show_deposits_info' => 'La pantalla de inicio ya muestra sus cuentas de gastos. ¿debería mostrar también su cuenta de ingresos?', - 'pref_home_do_show_deposits' => 'Si, muestrales', - 'successful_count' => 'de cual :count exitoso', - 'list_page_size_title' => 'Tamaño de pagina', - 'list_page_size_help' => 'Cualquier lista de cosas (cuentas, transacciones, etc) muestra como mucho esta cantidad por pagina.', - 'list_page_size_label' => 'Tamaño de pagina', - 'between_dates' => '(:start y :end)', - 'pref_optional_fields_transaction' => 'Campos opcionales para transacciones', + 'pref_two_factor_auth_help' => 'Cuando usted habilita la verificación en 2 pasos ( también conocida como autenticacion de dos factores) usted agrega una capa adicional de seguridad a su cuenta. usted inicia la sesión que conoce (código de verificación). los códigos de generación son generados por una aplicación de su teléfono, tales como Authy o Google Authenticador.', + 'pref_enable_two_factor_auth' => 'Permita la verificación de 2 pasos', + 'pref_two_factor_auth_disabled' => 'Codigo de verificacion en 2 pasos removido y inabilitado', + 'pref_two_factor_auth_remove_it' => 'No olvide eliminar la cuenta de su aplicación de autenticacion!', + 'pref_two_factor_auth_code' => 'Verificar código', + 'pref_two_factor_auth_code_help' => 'Escanee el código QR con una aplicación en su teléfono como Authy o Google autenticator y ingrese el código generado.', + 'pref_two_factor_auth_reset_code' => 'Reiniciar código de verificación', + 'pref_two_factor_auth_disable_2fa' => 'Deshabilitar 2FA', + '2fa_use_secret_instead' => 'If you cannot scan the QR code, feel free to use the secret instead: :secret.', + '2fa_backup_codes' => 'Store these backup codes for access in case you lose your device.', + '2fa_already_enabled' => '2-step verification is already enabled.', + 'wrong_mfa_code' => 'This MFA code is not valid.', + 'pref_save_settings' => 'Guardar la configuración', + 'saved_preferences' => '¡Preferencias guardadas!', + 'preferences_general' => 'General', + 'preferences_frontpage' => 'Pantalla de inicio', + 'preferences_security' => 'Seguridad', + 'preferences_layout' => 'Diseño', + 'pref_home_show_deposits' => 'Mostrar los depósitos en la pantalla de inicio', + 'pref_home_show_deposits_info' => 'La pantalla de inicio ya muestra sus cuentas de gastos. ¿debería mostrar también su cuenta de ingresos?', + 'pref_home_do_show_deposits' => 'Si, muestrales', + 'successful_count' => 'de cual :count exitoso', + 'list_page_size_title' => 'Tamaño de pagina', + 'list_page_size_help' => 'Cualquier lista de cosas (cuentas, transacciones, etc) muestra como mucho esta cantidad por pagina.', + 'list_page_size_label' => 'Tamaño de pagina', + 'between_dates' => '(:start y :end)', + 'pref_optional_fields_transaction' => 'Campos opcionales para transacciones', 'pref_optional_fields_transaction_help' => 'Por defecto no todos los campos se habilitan al crear una nueva transacción (debido al desorden). abajo usted puede habilitar estos campos si usted piensa que pueden ser útiles. por supuesto, cualquier campo que este desactivado, pero ya completado, sera visible a pesar de la configuración.', 'optional_tj_date_fields' => 'Campos de fecha', 'optional_tj_business_fields' => 'Campos comerciales', @@ -579,24 +562,25 @@ return [ 'login_with_new_email' => 'Usted puede ahora iniciar sesión con su nueva dirección de correo electrónico.', 'login_with_old_email' => 'Usted puede ahora iniciar sesión con su vieja dirección de correo electrónico otra vez.', 'login_provider_local_only' => 'Esta acción no está disponible cuando se identifica a través de ":login_provider".', - 'delete_local_info_only' => 'Debido a que se identificado a través de ":login_provider", sólo se eliminará la información local de Firefly III.', + 'delete_local_info_only' => 'Debido a que se identificado a través de ":login_provider", sólo se eliminará la información local de Firefly III.', // attachments - 'nr_of_attachments' => 'Un archivo adjunto:count archivos adjuntos', - 'attachments' => 'Archivos adjuntos', - 'edit_attachment' => 'Editar cuenta de archivos ":name"', - 'update_attachment' => 'Actualizar archivo adjunto', - 'delete_attachment' => 'Eliminar archivo adjunto ":name"', - 'attachment_deleted' => 'Eliminar archivo adjunto ":name"', - 'attachment_updated' => 'Actualizar archivo adjunto ":name"', - 'upload_max_file_size' => 'Tamaño máximo de archivo::size', - 'list_all_attachments' => 'Listado de documentos adjuntos', + 'nr_of_attachments' => 'Un archivo adjunto:count archivos adjuntos', + 'attachments' => 'Archivos adjuntos', + 'edit_attachment' => 'Editar cuenta de archivos ":name"', + 'update_attachment' => 'Actualizar archivo adjunto', + 'delete_attachment' => 'Eliminar archivo adjunto ":name"', + 'attachment_deleted' => 'Eliminar archivo adjunto ":name"', + 'liabilities_deleted' => 'Deleted liability ":name"', + 'attachment_updated' => 'Actualizar archivo adjunto ":name"', + 'upload_max_file_size' => 'Tamaño máximo de archivo::size', + 'list_all_attachments' => 'Listado de documentos adjuntos', // transaction index - 'title_expenses' => 'Gastos', - 'title_withdrawal' => 'Gastos', - 'title_revenue' => 'Ingresos / salarios', - 'title_deposit' => 'Ingresos / salarios', + 'title_expenses' => 'Gastos', + 'title_withdrawal' => 'Gastos', + 'title_revenue' => 'Ingresos / salarios', + 'title_deposit' => 'Ingresos / salarios', 'title_transfer' => 'Transferencias', 'title_transfers' => 'Transferencias', @@ -634,6 +618,9 @@ return [ 'converted_to_Transfer' => 'La transacción se convirtió en transferencia', 'invalid_convert_selection' => 'L a cuenta que usted ha selecionado ya esta en uso o no existe.', 'source_or_dest_invalid' => 'No pude encontrar los detalles correctos de la transacción. La conversión no es posible.', + 'convert_to_withdrawal' => 'Convert to a withdrawal', + 'convert_to_deposit' => 'Convert to a deposit', + 'convert_to_transfer' => 'Convert to a transfer', // create new stuff: 'create_new_withdrawal' => 'Crear nuevo retiro', @@ -695,7 +682,9 @@ return [ 'available_amount_indication' => 'Utilice estas cantidades para obtener una indicación de lo que podría ser su presupuesto total.', 'suggested' => 'Sugerido', 'average_between' => 'Promedio entre :start y :end', - 'over_budget_warn' => ' Normalmente usas de tu presupuesto, cerca de :amount por día. Se trata de :over_amount por día.', + 'over_budget_warn' => ' Usually you budget about :amount per day. This time it\'s :over_amount per day. Are you sure?', + 'transferred_in' => 'Transferred (in)', + 'transferred_away' => 'Transferred (away)', // bills: 'match_between_amounts' => 'La cuenta iguala transacciones entre :low y :high.', @@ -779,6 +768,7 @@ return [ 'reconcile_options' => 'Opciones de reconciliacion', 'reconcile_range' => 'Rango de reconciliacion', 'start_reconcile' => 'Comienzo de reconciliación', + 'cash_account_type' => 'Cash', 'cash' => 'efectivo', 'account_type' => 'Tipo de cuenta', 'save_transactions_by_moving' => 'Guarde estas transacción (es) moviendolas a otra cuenta:', @@ -803,7 +793,9 @@ return [ 'reconcile_go_back' => 'Usted puede siempre editar o eliminar una corrección mas tarde.', 'must_be_asset_account' => 'Usted solo puede reconciliar cuentas de activos', 'reconciliation_stored' => 'Reconciliación almacenada', - 'reconcilliation_transaction_title' => 'Reconciliación (:from a :to)', + 'reconciliation_error' => 'Due to an error the transactions were marked as reconciled but the correction has not been stored: :error.', + 'reconciliation_transaction_title' => 'Reconciliation (:from to :to)', + 'sum_of_reconciliation' => 'Sum of reconciliation', 'reconcile_this_account' => 'Reconciliar esta cuenta', 'confirm_reconciliation' => 'Confirmar la reconciliacion', 'submitted_start_balance' => 'Balance final enviado', @@ -815,7 +807,7 @@ return [ 'interest_calc_daily' => 'Por dia', 'interest_calc_monthly' => 'Por mes', 'interest_calc_yearly' => 'Por año', - 'initial_balance_account' => 'Balance inicial de la cuenta :name', + 'initial_balance_account' => 'Initial balance account of :account', // categories: 'new_category' => 'Nueva categoría', @@ -847,6 +839,8 @@ return [ 'deleted_deposit' => 'Deposito eliminado exitosamente ":description"', 'deleted_transfer' => 'Transferencia eliminada exitosamente ":description"', 'stored_journal' => 'Nueva transacción creada exitosamente ":description"', + 'stored_journal_no_descr' => 'Successfully created your new transaction', + 'updated_journal_no_descr' => 'Successfully updated your transaction', 'select_transactions' => 'Seleccionar transacciones', 'rule_group_select_transactions' => 'Aplicar ":title" a las transacciones', 'rule_select_transactions' => 'Aplicar ":title" a las transacciones', @@ -855,26 +849,33 @@ return [ 'mass_delete_journals' => 'Eliminar un numero de transacciones', 'mass_edit_journals' => 'Editar un numero de transacciones', 'mass_bulk_journals' => 'Editar múltiples transacciones', - 'mass_bulk_journals_explain' => 'Si usted no desea cambiar sus transacciones a una mediante la función de edición masiva, usted puede actualizarlas de una sola vez. simplemente seleccione la categoría, las etiquetas o el presupuesto preferidos en los campos de abajo, y todas las transacciones de la tabla se actualizaran.', + 'mass_bulk_journals_explain' => 'This form allows you to change properties of the transactions listed below in one sweeping update. All the transactions in the table will be updated when you change the parameters you see here.', + 'part_of_split' => 'This transaction is part of a split transaction. If you have not selected all the splits, you may end up with changing only half the transaction.', 'bulk_set_new_values' => 'Use las entradas abajo para establecer nuevos valores. Si usted los deja vacíos, serán vacíos para todos. también, tenga en cuenta que solo los retiros tendrán un presupuesto.', 'no_bulk_category' => 'No actualizar la categoria', 'no_bulk_budget' => 'No actualizar el presupuesto', - 'no_bulk_tags' => 'No actualizar etiqueta(s)', - 'bulk_edit' => 'Edición masiva', - 'cannot_edit_other_fields' => 'Usted no puede editar en masa otros campos ademas de los que están aquí, porque no hay espacio para mostrarlos. siga el enlace y editelo uno a uno, si usted necesita editar estos campos.', - 'no_budget' => '(sin presupuesto)', - 'no_budget_squared' => '(sin presupuesto)', - 'perm-delete-many' => 'Eliminar muchos elementos de una sola vez puede ser perturbador. Por favor sea cuidadoso.', - 'mass_deleted_transactions_success' => 'Eliminar :amount transacción (es).', - 'mass_edited_transactions_success' => 'Actualizado :amount transacción (es)', - 'opt_group_' => '(Sin tipo de cuenta)', + 'no_bulk_tags' => 'No actualizar etiqueta(s)', + 'mass_edit' => 'Edit selected individually', + 'bulk_edit' => 'Edit selected in bulk', + 'mass_delete' => 'Delete selected', + 'cannot_edit_other_fields' => 'Usted no puede editar en masa otros campos ademas de los que están aquí, porque no hay espacio para mostrarlos. siga el enlace y editelo uno a uno, si usted necesita editar estos campos.', + 'cannot_change_amount_reconciled' => 'You can\'t change the amount of reconciled transactions.', + 'no_budget' => '(sin presupuesto)', + 'no_budget_squared' => '(sin presupuesto)', + 'perm-delete-many' => 'Deleting many items in one go can be very disruptive. Please be cautious. You can delete part of a split transaction from this page, so take care.', + 'mass_deleted_transactions_success' => 'Eliminar :amount transacción (es).', + 'mass_edited_transactions_success' => 'Actualizado :amount transacción (es)', + 'opt_group_' => '(Sin tipo de cuenta)', 'opt_group_no_account_type' => '(Sin tipo de cuenta)', 'opt_group_defaultAsset' => 'Cuentas de activos por defecto', 'opt_group_savingAsset' => 'Cuenta de ahorros', 'opt_group_sharedAsset' => 'Cuenta de activos compartidas', 'opt_group_ccAsset' => 'Tarjetas de credito', 'opt_group_cashWalletAsset' => 'Billeteras de efectivo', + 'opt_group_expense_account' => 'Expense accounts', + 'opt_group_revenue_account' => 'Revenue accounts', 'opt_group_l_Loan' => 'Pasivo: Préstamo', + 'opt_group_cash_account' => 'Cash account', 'opt_group_l_Debt' => 'Pasivo: Deuda', 'opt_group_l_Mortgage' => 'Pasivo: Hipoteca', 'opt_group_l_Credit card' => 'Pasivo: Tarjeta de crédito', @@ -973,7 +974,7 @@ return [ 'errors' => 'Errores', 'debt_start_date' => 'Fecha de inicio de deuda', 'debt_start_amount' => 'Cantidad inicial de la deuda', - 'debt_start_amount_help' => 'Introduce la cantidad original de este pasivo como un número positivo. También puede ingresar la cantidad actual. Asegúrese de editar la fecha de abajo para que coincida.', + 'debt_start_amount_help' => 'If you owe an amount its best to enter a negative amount, because it influences your net worth. If you\'re owed an amount the same applies. Check out the help pages for more information.', 'store_new_liabilities_account' => 'Crear nuevo pasivo', 'edit_liabilities_account' => 'Editar pasivo ":name"', @@ -1145,6 +1146,7 @@ return [ 'deleted_piggy_bank' => 'Hucha ":name" eliminada', 'added_amount_to_piggy' => 'Agregado :amount a ":name"', 'removed_amount_from_piggy' => 'Eliminado :amount de :name', + 'piggy_events' => 'Related piggy banks', // tags 'delete_tag' => 'Eliminar etiqueta ":tag"', @@ -1154,47 +1156,57 @@ return [ 'updated_tag' => 'Actualizar etiqueta ":tag"', 'created_tag' => 'Etiqueta ":tag" ha sido creado!', - 'transaction_journal_information' => 'Información de transacción', - 'transaction_journal_meta' => 'Información Meta', - 'total_amount' => 'Cantidad total', - 'number_of_decimals' => 'Número de decimales', + 'transaction_journal_information' => 'Información de transacción', + 'transaction_journal_meta' => 'Información Meta', + 'transaction_journal_more' => 'More information', + 'att_part_of_journal' => 'Stored under ":journal"', + 'total_amount' => 'Cantidad total', + 'number_of_decimals' => 'Número de decimales', // administration - 'administration' => 'Administración', - 'user_administration' => 'Administración de usuarios', - 'list_all_users' => 'Todos los usuarios', - 'all_users' => 'Todo usuario', - 'instance_configuration' => 'Configuracion', - 'firefly_instance_configuration' => 'Opciones de configuración de Firefly III', - 'setting_single_user_mode' => 'Modo de usuario único', - 'setting_single_user_mode_explain' => 'Por defecto, Firefly III solo acepta un (1) registro: Usted. Esta es una medida de seguridad que impide que otros utilicen su instancia a menos que usted lo permita. Los registros futuros están bloqueados. Cuando usted desbloquee esta casilla, otros pueden usar su instancia también, suponiendo que puedan alcanzarla ( cuando este conectada a Internet).', - 'store_configuration' => 'Configuración de tienda', - 'single_user_administration' => 'Administración de usuarios para :email', - 'edit_user' => 'Editar usuario :email', - 'hidden_fields_preferences' => 'No todos los campos están visibles ahora. Debes habilitarlos en tu configuración.', - 'user_data_information' => 'Datos del usuario', - 'user_information' => 'Información del usuario', - 'total_size' => 'tamaño total', - 'budget_or_budgets' => 'presupuesto(s)', - 'budgets_with_limits' => 'presupuesto(s) con el importe configurado', - 'nr_of_rules_in_total_groups' => ':count_rules reglas en :count_groups grupo de reglas', - 'tag_or_tags' => 'etiqueta (s)', - 'configuration_updated' => 'La configuración ha sido actualizada', - 'setting_is_demo_site' => 'Sitio de demostracion', - 'setting_is_demo_site_explain' => 'Si usted chequea esta casilla, esta instalación se comportara como si fuera el sitio de demostración, que puede tener efectos secundarios extraños.', - 'block_code_bounced' => 'Mensaje (s) de correo rebotados', - 'block_code_expired' => 'Cuenta de demostración vencida', - 'no_block_code' => 'No hay razón para el bloqueo o usuario bloqueado', - 'block_code_email_changed' => 'El usuario no ha confirmado aun una nueva dirección de correo', - 'admin_update_email' => 'Contrario a la pagina de perfil, El usuario NO se notificara que su dirección de correo ha cambiado!', - 'update_user' => 'Actualizar usuario', - 'updated_user' => 'Los datos del usuario han sido cambiados.', - 'delete_user' => 'Eliminar usuario :email', - 'user_deleted' => 'El usuario ha sido eliminado', - 'send_test_email' => 'Enviar mensaje de correo electrónico de prueba', - 'send_test_email_text' => 'Para ver si su instalación es capaz de enviar correos electrónicos, presione este botón. Usted no verá ningún error aquí (si los hubiera) los archivos de registro mostrarán cualquier error. Usted puede presionar este botón tantas veces como lo desee. No hay control de spam. El mensaje será enviado a :emaily debería llegar en breve.', - 'send_message' => 'Enviar mensaje', - 'send_test_triggered' => 'La prueba fue disparada. Chequee su bandeja de entrada y archivos de registro.', + 'administration' => 'Administración', + 'user_administration' => 'Administración de usuarios', + 'list_all_users' => 'Todos los usuarios', + 'all_users' => 'Todo usuario', + 'instance_configuration' => 'Configuracion', + 'firefly_instance_configuration' => 'Opciones de configuración de Firefly III', + 'setting_single_user_mode' => 'Modo de usuario único', + 'setting_single_user_mode_explain' => 'Por defecto, Firefly III solo acepta un (1) registro: Usted. Esta es una medida de seguridad que impide que otros utilicen su instancia a menos que usted lo permita. Los registros futuros están bloqueados. Cuando usted desbloquee esta casilla, otros pueden usar su instancia también, suponiendo que puedan alcanzarla ( cuando este conectada a Internet).', + 'store_configuration' => 'Configuración de tienda', + 'single_user_administration' => 'Administración de usuarios para :email', + 'edit_user' => 'Editar usuario :email', + 'hidden_fields_preferences' => 'You can enable more transaction options in your settings.', + 'user_data_information' => 'Datos del usuario', + 'user_information' => 'Información del usuario', + 'total_size' => 'tamaño total', + 'budget_or_budgets' => 'presupuesto(s)', + 'budgets_with_limits' => 'presupuesto(s) con el importe configurado', + 'nr_of_rules_in_total_groups' => ':count_rules reglas en :count_groups grupo de reglas', + 'tag_or_tags' => 'etiqueta (s)', + 'configuration_updated' => 'La configuración ha sido actualizada', + 'setting_is_demo_site' => 'Sitio de demostracion', + 'setting_is_demo_site_explain' => 'Si usted chequea esta casilla, esta instalación se comportara como si fuera el sitio de demostración, que puede tener efectos secundarios extraños.', + 'block_code_bounced' => 'Mensaje (s) de correo rebotados', + 'block_code_expired' => 'Cuenta de demostración vencida', + 'no_block_code' => 'No hay razón para el bloqueo o usuario bloqueado', + 'block_code_email_changed' => 'El usuario no ha confirmado aun una nueva dirección de correo', + 'admin_update_email' => 'Contrario a la pagina de perfil, El usuario NO se notificara que su dirección de correo ha cambiado!', + 'update_user' => 'Actualizar usuario', + 'updated_user' => 'Los datos del usuario han sido cambiados.', + 'delete_user' => 'Eliminar usuario :email', + 'user_deleted' => 'El usuario ha sido eliminado', + 'send_test_email' => 'Enviar mensaje de correo electrónico de prueba', + 'send_test_email_text' => 'Para ver si su instalación es capaz de enviar correos electrónicos, presione este botón. Usted no verá ningún error aquí (si los hubiera) los archivos de registro mostrarán cualquier error. Usted puede presionar este botón tantas veces como lo desee. No hay control de spam. El mensaje será enviado a :emaily debería llegar en breve.', + 'send_message' => 'Enviar mensaje', + 'send_test_triggered' => 'La prueba fue disparada. Chequee su bandeja de entrada y archivos de registro.', + + 'split_transaction_title' => 'Description of the split transaction', + 'split_title_help' => 'If you create a split transaction, there must be a global description for all splits of the transaction.', + 'transaction_information' => 'Transaction information', + 'you_create_transfer' => 'You\'re creating a transfer.', + 'you_create_withdrawal' => 'You\'re creating a withdrawal.', + 'you_create_deposit' => 'You\'re creating a deposit.', + // links 'journal_link_configuration' => 'Configuración de enlaces de transacción', @@ -1214,12 +1226,13 @@ return [ 'do_not_save_connection' => '(no guarde la conexión)', 'link_transaction' => 'Enlazar transacción', 'link_to_other_transaction' => 'Enlazar esta transacción con otra', - 'select_transaction_to_link' => 'Seleccione una transacción para enlazar esta transacción a', + 'select_transaction_to_link' => 'Select a transaction to link this transaction to. The links are currently unused in Firefly III (apart from being shown), but I plan to change this in the future. Use the search box to select a transaction either by title or by ID. If you want to add custom link types, check out the administration section.', 'this_transaction' => 'Esta transacción', 'transaction' => 'Transaccion', 'comments' => 'Comentarios', - 'to_link_not_found' => 'Si la transacción que usted desea vincular no aparece en lista, simplemente ingrese su ID.', + 'link_notes' => 'Any notes you wish to store with the link.', 'invalid_link_selection' => 'No se puede vincular esta transacción', + 'selected_transaction' => 'Selected transaction', 'journals_linked' => 'Las transacciones están vinculadas.', 'journals_error_linked' => 'Estas transacciones ya están vinculadas.', 'journals_link_to_self' => 'No puede relacionar una transacción consigo misma', @@ -1258,12 +1271,11 @@ return [ 'split_this_withdrawal' => 'Dividir este retiro', 'split_this_deposit' => 'Dividir este deposito', 'split_this_transfer' => 'Dividir esta transferencia', - 'cannot_edit_multiple_source' => 'Usted no puede editar transacciones divididas #:id con descripción ":description" porque contiene múltiples cuentas de origen.', - 'cannot_edit_multiple_dest' => 'Usted no puede editar transacciones divididas #:id con descripción ":description" porque contiene múltiples cuentas destino.', - 'cannot_edit_reconciled' => 'Usted no puede editar transacciones #:id con descripción ":description" porque se ha marcado como reconciliado.', 'cannot_edit_opening_balance' => 'Usted no puede editar el balance de apertura de una cuenta.', 'no_edit_multiple_left' => 'Usted no ha seleccionado transacciones válidas para editar.', - 'cannot_convert_split_journal' => 'No se puede convertir una transacción dividida', + 'breadcrumb_convert_group' => 'Convert transaction', + 'convert_invalid_source' => 'Source information is invalid for transaction #%d.', + 'convert_invalid_destination' => 'Destination information is invalid for transaction #%d.', // Import page (general strings only) 'import_index_title' => 'Importar transacciones a Firefly III', @@ -1389,4 +1401,15 @@ return [ 'will_jump_monday' => 'Se creará el lunes en lugar de los fines de semana.', 'except_weekends' => 'Excluir los fines de semana', 'recurrence_deleted' => 'Transacción recurrente ":title" eliminada', + + // new lines for summary controller. + 'box_balance_in_currency' => 'Balance (:currency)', + 'box_spent_in_currency' => 'Spent (:currency)', + 'box_earned_in_currency' => 'Earned (:currency)', + 'box_bill_paid_in_currency' => 'Bills paid (:currency)', + 'box_bill_unpaid_in_currency' => 'Bills unpaid (:currency)', + 'box_left_to_spend_in_currency' => 'Left to spend (:currency)', + 'box_net_worth_in_currency' => 'Net worth (:currency)', + 'box_spend_per_day' => 'Left to spend per day: :amount', + ]; diff --git a/resources/lang/es_ES/form.php b/resources/lang/es_ES/form.php index c69e2a8589..9b58a2d78f 100644 --- a/resources/lang/es_ES/form.php +++ b/resources/lang/es_ES/form.php @@ -57,20 +57,21 @@ return [ 'asset_source_account' => 'Cuenta de origen', 'journal_description' => 'Descripción', 'note' => 'Notas', + 'store_new_transaction' => 'Store new transaction', 'split_journal' => 'Dividir esta transacción', 'split_journal_explanation' => 'Dividir esta transacción en múltiples partes', 'currency' => 'Divisa', 'account_id' => 'Cuenta', 'budget_id' => 'Presupuesto', - 'openingBalance' => 'Saldo inicial', + 'opening_balance' => 'Opening balance', 'tagMode' => 'Modo de etiqueta', 'tag_position' => 'Etiquetar ubicación', - 'virtualBalance' => 'Saldo virtual', + 'virtual_balance' => 'Virtual balance', 'targetamount' => 'Cantidad objetivo', - 'accountRole' => 'Tipo de cuenta', - 'openingBalanceDate' => 'Fecha del saldo inicial', - 'ccType' => 'Plan de pagos con tarjeta de crédito', - 'ccMonthlyPaymentDate' => 'Fecha de pago mensual de la tarjeta de crédito', + 'account_role' => 'Account role', + 'opening_balance_date' => 'Opening balance date', + 'cc_type' => 'Credit card payment plan', + 'cc_monthly_payment_date' => 'Credit card monthly payment date', 'piggy_bank_id' => 'Hucha', 'returnHere' => 'Volver aquí', 'returnHereExplanation' => 'Después de guardar, vuelve aquí para crear otro.', @@ -118,16 +119,16 @@ return [ 'symbol' => 'Símbolo', 'code' => 'Código', 'iban' => 'IBAN', - 'accountNumber' => 'Número de cuenta', + 'account_number' => 'Account number', 'creditCardNumber' => 'Número de la tarjeta de crédito', 'has_headers' => 'Encabezados', 'date_format' => 'Formato de fecha', 'specifix' => 'Banco- o archivo de soluciones especificas', 'attachments[]' => 'Adjuntos', - 'store_new_withdrawal' => 'Guardar rueva retirada de efectivo', + 'store_new_withdrawal' => 'Guardar nuevo retiro', 'store_new_deposit' => 'Guardar nuevo depósito', 'store_new_transfer' => 'Guardar nueva transferencia', - 'add_new_withdrawal' => 'Añadir rueva retirada de efectivo', + 'add_new_withdrawal' => 'Añadir un nuevo retiro', 'add_new_deposit' => 'Añadir nuevo depósito', 'add_new_transfer' => 'Añadir nueva transferencia', 'title' => 'Título', @@ -139,12 +140,8 @@ return [ 'stop_processing' => 'Detener el procesamiento', 'start_date' => 'Inicio del rango', 'end_date' => 'Final del rango', - 'export_start_range' => 'Inicio del rango de exportación', - 'export_end_range' => 'Fin del rango de exportación', - 'export_format' => 'Formato del archivo', 'include_attachments' => 'Incluir archivos adjuntos subidos', 'include_old_uploads' => 'Incluir datos importados', - 'accounts' => 'Exportar transacciones de estas cuentas', 'delete_account' => 'Borrar cuenta ":name"', 'delete_bill' => 'Eliminar factura ":name"', 'delete_budget' => 'Eliminar presupuesto ":name"', @@ -256,4 +253,7 @@ return [ 'weekend' => 'Fin de semana', 'client_secret' => 'Secreto del cliente', + 'withdrawal_destination_id' => 'Destination account', + 'deposit_source_id' => 'Source account', + ]; diff --git a/resources/lang/es_ES/import.php b/resources/lang/es_ES/import.php index 48911ac27d..2fae6ee156 100644 --- a/resources/lang/es_ES/import.php +++ b/resources/lang/es_ES/import.php @@ -207,6 +207,10 @@ return [ 'specific_rabo_descr' => 'Corrige los posibles problemas con archivos de Rabobank', 'specific_pres_name' => 'President\'s Choice Financial CA', 'specific_pres_descr' => 'Soluciona problemas potenciales con archivos de PC', + 'specific_belfius_name' => 'Belfius BE', + 'specific_belfius_descr' => 'Corrige los posibles problemas con archivos de Belfius', + 'specific_ingbelgium_name' => 'ING BE', + 'specific_ingbelgium_descr' => 'Fixes potential problems with ING Belgium files', // job configuration for file provider (stage: roles) 'job_config_roles_title' => 'Configuración de importación (3/4) - Definir el rol de cada columna', 'job_config_roles_text' => 'Cada columna en su archivo CSV contiene ciertos datos. Indique qué tipo de datos debe esperar el importador. La opción de "mapear" datos significa que enlazará cada entrada encontrada en la columna con un valor en su base de datos. Una columna a menudo mapeada es la columna que contiene el IBAN de la cuenta de contrapartida. Eso puede enlazarse fácilmente con cuentas IBAN ya presentes en su base de datos.', @@ -291,14 +295,14 @@ return [ 'column_rabo-debit-credit' => 'Indicador especifico débito/crédito de Rabobank', 'column_ing-debit-credit' => 'Indicador especifico débito/crédito de ING', 'column_generic-debit-credit' => 'Indicador de débito/crédito de bancos genéricos', - 'column_sepa-ct-id' => 'Identificador de end-to-end SEPA', - 'column_sepa-ct-op' => 'Identificador de cuenta opuesta SEPA', - 'column_sepa-db' => 'Identificador de mandato SEPA', - 'column_sepa-cc' => 'Código de clearing SEPA', - 'column_sepa-ci' => 'Identificador de acreedor SEPA', - 'column_sepa-ep' => 'Propósito externo SEPA', - 'column_sepa-country' => 'Código del país SEPA', - 'column_sepa-batch-id' => 'ID de lote SEPA', + 'column_sepa_ct_id' => 'Identificador de extremo a extremo SEPA', + 'column_sepa_ct_op' => 'Identificador de cuenta opuesta SEPA', + 'column_sepa_db' => 'Identificador de mandato SEPA', + 'column_sepa_cc' => 'Código de limpieza SEPA', + 'column_sepa_ci' => 'Identificador de acreedor SEPA', + 'column_sepa_ep' => 'Propósito externo SEPA', + 'column_sepa_country' => 'Código del país SEPA', + 'column_sepa_batch_id' => 'ID de lote SEPA', 'column_tags-comma' => 'Etiquetas ( separadas por comas)', 'column_tags-space' => 'Etiquetas ( separadas por espacio)', 'column_account-number' => 'Cuenta de archivos ( numero de cuenta)', @@ -306,4 +310,7 @@ return [ 'column_note' => 'Nota (s)', 'column_internal-reference' => 'Referencia interna', + // error message + 'duplicate_row' => 'Row #:row (":description") could not be imported. It already exists.', + ]; diff --git a/resources/lang/es_ES/intro.php b/resources/lang/es_ES/intro.php index 9799a3ff2f..a0b46c1952 100644 --- a/resources/lang/es_ES/intro.php +++ b/resources/lang/es_ES/intro.php @@ -24,39 +24,61 @@ declare(strict_types=1); return [ // index - 'index_intro' => 'Bienvenido a la página de índice de Firefly III. Por favor tómate tu tiempo para revisar esta guía y que puedas hacerte una idea de cómo funciona Firefly III.', - 'index_accounts-chart' => 'Este gráfico muestra el saldo actual de tus cuentas. Puedes seleccionar las cuentas que se muestran en él desde tus preferencias.', - 'index_box_out_holder' => 'Esta pequeña caja y las cajas a continuación te darán una visión rápida de tu situación financiera.', - 'index_help' => 'Si alguna vez necesitas ayuda en una página o formulario, pulsa este botón.', - 'index_outro' => 'La mayoría de las páginas de Firefly III comenzarán con una pequeña introducción como ésta. Por favor, ponte en contacto conmigo si tienes preguntas o comentarios. ¡Disfruta!', - 'index_sidebar-toggle' => 'Para crear nuevas transacciones, cuentas u otros elementos, utiliza el menú bajo este icono.', + 'index_intro' => 'Bienvenido a la página de índice de Firefly III. Por favor tómate tu tiempo para revisar esta guía y que puedas hacerte una idea de cómo funciona Firefly III.', + 'index_accounts-chart' => 'Este gráfico muestra el saldo actual de tus cuentas. Puedes seleccionar las cuentas que se muestran en él desde tus preferencias.', + 'index_box_out_holder' => 'Esta pequeña caja y las cajas a continuación te darán una visión rápida de tu situación financiera.', + 'index_help' => 'Si alguna vez necesitas ayuda en una página o formulario, pulsa este botón.', + 'index_outro' => 'La mayoría de las páginas de Firefly III comenzarán con una pequeña introducción como ésta. Por favor, ponte en contacto conmigo si tienes preguntas o comentarios. ¡Disfruta!', + 'index_sidebar-toggle' => 'Para crear nuevas transacciones, cuentas u otros elementos, utiliza el menú bajo este icono.', + 'index_cash_account' => 'These are the accounts created so far. You can use the cash account to track cash expenses but it\'s not mandatory of course.', + + // transactions (withdrawal) + 'transactions_create_withdrawal_source' => 'Select your favorite asset account or liability from this dropdown.', + 'transactions_create_withdrawal_destination' => 'Select an expense account here. Leave it empty if you want to make a cash expense.', + 'transactions_create_withdrawal_foreign_currency' => 'Use this field to set a foreign currency and amount.', + 'transactions_create_withdrawal_more_meta' => 'Plenty of other meta data you set in these fields.', + 'transactions_create_withdrawal_split_add' => 'If you want to split a transaction, add more splits with this button', + + // transactions (deposit) + 'transactions_create_deposit_source' => 'Select or type the payee in this auto-completing dropdown/textbox. Leave it empty if you want to make a cash deposit.', + 'transactions_create_deposit_destination' => 'Select an asset or liability account here.', + 'transactions_create_deposit_foreign_currency' => 'Use this field to set a foreign currency and amount.', + 'transactions_create_deposit_more_meta' => 'Plenty of other meta data you set in these fields.', + 'transactions_create_deposit_split_add' => 'If you want to split a transaction, add more splits with this button', + + // transactions (transfer) + 'transactions_create_transfer_source' => 'Select the source asset account here.', + 'transactions_create_transfer_destination' => 'Select the destination asset account here.', + 'transactions_create_transfer_foreign_currency' => 'Use this field to set a foreign currency and amount.', + 'transactions_create_transfer_more_meta' => 'Plenty of other meta data you set in these fields.', + 'transactions_create_transfer_split_add' => 'If you want to split a transaction, add more splits with this button', // create account: - 'accounts_create_iban' => 'Indica un IBAN válido en tus cuentas. Esto facilitará la importación de datos en el futuro.', - 'accounts_create_asset_opening_balance' => 'Cuentas de ingreso deben tener un "saldo de apertura", indicando el inicio del historial de la cuenta en Firefly III.', - 'accounts_create_asset_currency' => 'Firefly III admite múltiples divisas. Las cuentas tienen una divisa principal, que debes indicar aquí.', - 'accounts_create_asset_virtual' => 'A veces puede ayudar el darle a tu cuenta un balance virtual: una cantidad extra que se añade o resta siempre del balance real.', + 'accounts_create_iban' => 'Indica un IBAN válido en tus cuentas. Esto facilitará la importación de datos en el futuro.', + 'accounts_create_asset_opening_balance' => 'Cuentas de ingreso deben tener un "saldo de apertura", indicando el inicio del historial de la cuenta en Firefly III.', + 'accounts_create_asset_currency' => 'Firefly III admite múltiples divisas. Las cuentas tienen una divisa principal, que debes indicar aquí.', + 'accounts_create_asset_virtual' => 'A veces puede ayudar el darle a tu cuenta un balance virtual: una cantidad extra que se añade o resta siempre del balance real.', // budgets index - 'budgets_index_intro' => 'Los presupuestos se utilizan para administrar sus finanzas y son una de las funciones básicas de Firefly III.', - 'budgets_index_set_budget' => 'Coloque su presupuesto total para cada período y así Firefly III puede decirle si usted ha presupuestado todo el dinero disponible.', - 'budgets_index_see_expenses_bar' => 'Gastar dinero irá llenando poco a poco esta barra.', - 'budgets_index_navigate_periods' => 'Navega a través de períodos para configurar fácilmente presupuestos con anticipación.', - 'budgets_index_new_budget' => 'Crea nuevos presupuestos como mejor te parezca.', - 'budgets_index_list_of_budgets' => 'Use esta tabla para establecer las cantidades para cada presupuesto y ver cómo lo está haciendo.', - 'budgets_index_outro' => 'Para aprender mas acerca de los presupuestos, revise el icono de ayuda en el tope de la esquina derecha.', + 'budgets_index_intro' => 'Los presupuestos se utilizan para administrar sus finanzas y son una de las funciones básicas de Firefly III.', + 'budgets_index_set_budget' => 'Coloque su presupuesto total para cada período y así Firefly III puede decirle si usted ha presupuestado todo el dinero disponible.', + 'budgets_index_see_expenses_bar' => 'Gastar dinero irá llenando poco a poco esta barra.', + 'budgets_index_navigate_periods' => 'Navega a través de períodos para configurar fácilmente presupuestos con anticipación.', + 'budgets_index_new_budget' => 'Crea nuevos presupuestos como mejor te parezca.', + 'budgets_index_list_of_budgets' => 'Use esta tabla para establecer las cantidades para cada presupuesto y ver cómo lo está haciendo.', + 'budgets_index_outro' => 'Para aprender mas acerca de los presupuestos, revise el icono de ayuda en el tope de la esquina derecha.', // reports (index) - 'reports_index_intro' => 'Utilice estos reportes para tener información detallada de sus finanzas.', - 'reports_index_inputReportType' => 'Escoja un tipo de reporte. Revise las páginas de ayuda para ver lo que le muestra cada reporte.', - 'reports_index_inputAccountsSelect' => 'Usted puede excluir o incluir cuentas de activos como mejor le cuadre.', - 'reports_index_inputDateRange' => 'El rango de fecha seleccionada depende completamente de usted: de un día a 10 años.', - 'reports_index_extra-options-box' => 'Dependiendo del informe que usted haya seleccionado, puede seleccionar filtros y opciones extras aquí. Mire este recuadro cuando cambie los tipos de informes.', + 'reports_index_intro' => 'Utilice estos reportes para tener información detallada de sus finanzas.', + 'reports_index_inputReportType' => 'Escoja un tipo de reporte. Revise las páginas de ayuda para ver lo que le muestra cada reporte.', + 'reports_index_inputAccountsSelect' => 'Usted puede excluir o incluir cuentas de activos como mejor le cuadre.', + 'reports_index_inputDateRange' => 'El rango de fecha seleccionada depende completamente de usted: de un día a 10 años.', + 'reports_index_extra-options-box' => 'Dependiendo del informe que usted haya seleccionado, puede seleccionar filtros y opciones extras aquí. Mire este recuadro cuando cambie los tipos de informes.', // reports (reports) - 'reports_report_default_intro' => 'Este informe le dará un rápido y comprensivo resumen de sus finanzas. Si usted desea ver algo mas, ¡por favor no dude en ponerse en contacto conmigo!', - 'reports_report_audit_intro' => 'Este informe le dará información detallada en sus cuentas de activos.', - 'reports_report_audit_optionsBox' => 'Use estos recuadros de verificación para ver u ocultar las columnas que a usted le interesan.', + 'reports_report_default_intro' => 'Este informe le dará un rápido y comprensivo resumen de sus finanzas. Si usted desea ver algo mas, ¡por favor no dude en ponerse en contacto conmigo!', + 'reports_report_audit_intro' => 'Este informe le dará información detallada en sus cuentas de activos.', + 'reports_report_audit_optionsBox' => 'Use estos recuadros de verificación para ver u ocultar las columnas que a usted le interesan.', 'reports_report_category_intro' => 'Este informe le dará una idea en una o múltiples categorías.', 'reports_report_category_pieCharts' => 'Estos gráficos le darán una idea de sus gastos e ingresos por categoría o por cuenta.', diff --git a/resources/lang/es_ES/list.php b/resources/lang/es_ES/list.php index f1b4d471a5..a2d2f866fe 100644 --- a/resources/lang/es_ES/list.php +++ b/resources/lang/es_ES/list.php @@ -23,71 +23,71 @@ declare(strict_types=1); return [ - 'buttons' => 'Botones', - 'icon' => 'Icono', - 'id' => 'ID', - 'create_date' => 'Fecha de creación', - 'update_date' => 'Fecha de modificación', - 'updated_at' => 'Actualizado en', - 'balance_before' => 'Balance antes de ', - 'balance_after' => 'Balance después de la', - 'name' => 'Nombre', - 'role' => 'Rol', - 'currentBalance' => 'Balance actual', - 'linked_to_rules' => 'Reglas asociadas', - 'active' => '¿Está Activo?', - 'lastActivity' => 'Actividad más reciente', - 'balanceDiff' => 'Diferencia de saldo', - 'matchesOn' => 'Encontrado en', - 'account_type' => 'Tipo de cuenta', - 'created_at' => 'Fecha de creación', - 'account' => 'Cuenta', - 'matchingAmount' => 'Cantidad', - 'split_number' => 'División #', - 'destination' => 'Destino', - 'source' => 'Origen', - 'next_expected_match' => 'Próxima coincidencia esperada', - 'automatch' => '¿Buscar coincidencia automaticamente?', - 'repeat_freq' => 'Repetición:', - 'description' => 'Descripción', - 'amount' => 'Monto', - 'internal_reference' => 'Referencia interna', - 'date' => 'Fecha', - 'interest_date' => 'Tasa de interés', - 'book_date' => 'Libro fecha', - 'process_date' => 'Fecha de procesamiento', - 'due_date' => 'Fecha de vencimiento', - 'payment_date' => 'Fecha de pago', - 'invoice_date' => 'Fecha de facturación', - 'interal_reference' => 'Referencia interna', - 'notes' => 'Notas', - 'from' => 'Desde', - 'piggy_bank' => 'Alcancilla', - 'to' => 'Hasta', - 'budget' => 'Presupuesto', - 'category' => 'Categoría', - 'bill' => 'Factura', - 'withdrawal' => 'Retiro', - 'deposit' => 'Depósito', - 'transfer' => 'Trasferencia', - 'type' => 'Tipo', - 'completed' => 'Completado', - 'iban' => 'IBAN', - 'paid_current_period' => 'Pagado este período', - 'email' => 'Correo electrónico', - 'registered_at' => 'Registrado el', - 'is_blocked' => 'Está bloqueado', - 'is_admin' => '¿Es el administrador?', - 'has_two_factor' => 'Tiene 2FA', - 'blocked_code' => 'Bloque de código', - 'source_account' => 'Cuenta origen', + 'buttons' => 'Botones', + 'icon' => 'Icono', + 'id' => 'ID', + 'create_date' => 'Fecha de creación', + 'update_date' => 'Fecha de modificación', + 'updated_at' => 'Actualizado en', + 'balance_before' => 'Balance antes de ', + 'balance_after' => 'Balance después de la', + 'name' => 'Nombre', + 'role' => 'Rol', + 'currentBalance' => 'Balance actual', + 'linked_to_rules' => 'Reglas asociadas', + 'active' => '¿Está Activo?', + 'transaction_type' => 'Type', + 'lastActivity' => 'Actividad más reciente', + 'balanceDiff' => 'Diferencia de saldo', + 'matchesOn' => 'Encontrado en', + 'account_type' => 'Tipo de cuenta', + 'created_at' => 'Fecha de creación', + 'account' => 'Cuenta', + 'matchingAmount' => 'Cantidad', + 'split_number' => 'División #', + 'destination' => 'Destino', + 'source' => 'Origen', + 'next_expected_match' => 'Próxima coincidencia esperada', + 'automatch' => '¿Buscar coincidencia automaticamente?', + 'repeat_freq' => 'Repetición:', + 'description' => 'Descripción', + 'amount' => 'Monto', + 'internal_reference' => 'Referencia interna', + 'date' => 'Fecha', + 'interest_date' => 'Tasa de interés', + 'book_date' => 'Libro fecha', + 'process_date' => 'Fecha de procesamiento', + 'due_date' => 'Fecha de vencimiento', + 'payment_date' => 'Fecha de pago', + 'invoice_date' => 'Fecha de facturación', + 'interal_reference' => 'Referencia interna', + 'notes' => 'Notas', + 'from' => 'Desde', + 'piggy_bank' => 'Alcancilla', + 'to' => 'Hasta', + 'budget' => 'Presupuesto', + 'category' => 'Categoría', + 'bill' => 'Factura', + 'withdrawal' => 'Retiro', + 'deposit' => 'Depósito', + 'transfer' => 'Trasferencia', + 'type' => 'Tipo', + 'completed' => 'Completado', + 'iban' => 'IBAN', + 'paid_current_period' => 'Pagado este período', + 'email' => 'Correo electrónico', + 'registered_at' => 'Registrado el', + 'is_blocked' => 'Está bloqueado', + 'is_admin' => '¿Es el administrador?', + 'has_two_factor' => 'Tiene 2FA', + 'blocked_code' => 'Bloque de código', + 'source_account' => 'Cuenta origen', 'destination_account' => 'Cuenta destino', 'accounts_count' => 'Número de cuentas', 'journals_count' => 'Número de transacciones', 'attachments_count' => 'Núm. de datos adjuntos', 'bills_count' => 'Número de facturas', 'categories_count' => 'Número de categorías', - 'export_jobs_count' => 'Número de operaciones de exportación', 'import_jobs_count' => 'Número de operaciones de importación', 'budget_count' => 'Número de presupuestos', 'rule_and_groups_count' => 'Número de reglas y grupos de reglas', @@ -106,14 +106,14 @@ return [ 'account_on_spectre' => 'Cuenta (espectro)', 'account_on_ynab' => 'Cuenta (YNAB)', 'do_import' => 'Importar desde esta cuenta', - 'sepa-ct-id' => 'Identificador de extremo a extremo SEPA', - 'sepa-ct-op' => 'Identificador de cuenta opuesta SEPA', - 'sepa-db' => 'Identificador de mandato SEPA', - 'sepa-country' => 'País SEPA', - 'sepa-cc' => 'Código de limpieza SEPA', - 'sepa-ep' => 'Propósito externo SEPA', - 'sepa-ci' => 'Identificador de acreedor SEPA', - 'sepa-batch-id' => 'ID de lote SEPA', + 'sepa_ct_id' => 'Identificador de extremo a extremo SEPA', + 'sepa_ct_op' => 'Identificador de cuenta opuesta SEPA', + 'sepa_db' => 'Identificador de mandato SEPA', + 'sepa_country' => 'País SEPA', + 'sepa_cc' => 'Código de limpieza SEPA', + 'sepa_ep' => 'Propósito externo SEPA', + 'sepa_ci' => 'Identificador de acreedor SEPA', + 'sepa_batch_id' => 'ID de lote SEPA', 'external_id' => 'ID Externo', 'account_at_bunq' => 'Cuenta con bunq', 'file_name' => 'Nombre de fichero', diff --git a/resources/lang/es_ES/validation.php b/resources/lang/es_ES/validation.php index 8845556901..9bb6f19f37 100644 --- a/resources/lang/es_ES/validation.php +++ b/resources/lang/es_ES/validation.php @@ -33,15 +33,19 @@ return [ 'rule_trigger_value' => 'Este valor es incorrecto para el disparador seleccionado.', 'rule_action_value' => 'Este valor es incorrecto para la acción seleccionada.', 'file_already_attached' => 'El archivo ":name" ya ha sido añadido a este objeto.', - 'file_attached' => 'Archivo subido con éxito ":name".', + 'file_attached' => 'Archivo ":name" subido con éxito.', 'must_exist' => 'El ID introducido en :attribute no existe en la base de datos.', 'all_accounts_equal' => 'Todas las cuentas en este campo deben ser iguales.', + 'group_title_mandatory' => 'Un título de grupo es obligatorio cuando hay más de una transacción.', + 'transaction_types_equal' => 'Todas las divisiones deben ser del mismo tipo.', + 'invalid_transaction_type' => 'Tipo de transacción inválido.', 'invalid_selection' => 'Tu selección no es válida.', 'belongs_user' => 'Este valor no es válido para este campo.', 'at_least_one_transaction' => 'Se necesita al menos una transacción.', 'at_least_one_repetition' => 'Se necesita al menos una repetición.', 'require_repeat_until' => 'Se precisa un número de repeticiones o una fecha de finalización (repeat_until). No ambas.', 'require_currency_info' => 'El contenido de este campo no es válido sin la información montearia.', + 'require_currency_amount' => 'The content of this field is invalid without foreign amount information.', 'equal_description' => 'La descripción de la transacción no debería ser igual a la descripción global.', 'file_invalid_mime' => 'El archivo ":name" es de tipo ":mime", el cual no se acepta.', 'file_too_large' => 'El archivo ":name" es demasiado grande.', @@ -135,8 +139,8 @@ return [ 'name' => 'nombre', 'piggy_bank_id' => 'ID de hucha', 'targetamount' => 'cantidad objetivo', - 'openingBalanceDate' => 'fecha de balance de apertura', - 'openingBalance' => 'balance de apertura', + 'opening_balance_date' => 'opening balance date', + 'opening_balance' => 'opening balance', 'match' => 'emparejar', 'amount_min' => 'cantidad mínima', 'amount_max' => 'cantidad máxima', @@ -164,4 +168,28 @@ return [ 'rule-trigger.4' => 'disparo de regla #4', 'rule-trigger.5' => 'disparo de regla#5', ], + + // validation of accounts: + 'withdrawal_source_need_data' => 'Necesita obtener un ID de cuenta de origen válido y/o nombre de cuenta de origen válido para continuar.', + 'withdrawal_source_bad_data' => 'No se pudo encontrar una cuenta de origen válida para ID ":id" o nombre ":name".', + 'withdrawal_dest_need_data' => 'Necesita obtener un ID de cuenta de destino válido y/o nombre de cuenta de destino válido para continuar.', + 'withdrawal_dest_bad_data' => 'Could not find a valid destination account when searching for ID ":id" or name ":name".', + + 'deposit_source_need_data' => 'Necesita obtener un ID de cuenta de origen válido y/o nombre de cuenta de origen válido para continuar.', + 'deposit_source_bad_data' => 'No se pudo encontrar una cuenta de origen válida para ID ":id" o nombre ":name".', + 'deposit_dest_need_data' => 'Necesita obtener un ID de cuenta de destino válido y/o nombre de cuenta de destino válido para continuar.', + 'deposit_dest_bad_data' => 'No se pudo encontrar una cuenta de destino válida buscando ID ":id" o nombre ":name".', + + 'transfer_source_need_data' => 'Necesita obtener un ID de cuenta de origen válido y/o nombre de cuenta de origen válido para continuar.', + 'transfer_source_bad_data' => 'No se pudo encontrar una cuenta de origen válida para ID ":id" o nombre ":name".', + 'transfer_dest_need_data' => 'Necesita obtener un ID de cuenta de destino válido y/o nombre de cuenta de destino válido para continuar.', + 'transfer_dest_bad_data' => 'No se pudo encontrar una cuenta de destino válida buscando ID ":id" o nombre ":name".', + 'need_id_in_edit' => 'Each split must have transaction_journal_id (either valid ID or 0).', + + 'ob_source_need_data' => 'Need to get a valid source account ID and/or valid source account name to continue.', + 'ob_dest_need_data' => 'Need to get a valid destination account ID and/or valid destination account name to continue.', + 'ob_dest_bad_data' => 'Could not find a valid destination account when searching for ID ":id" or name ":name".', + + 'generic_invalid_source' => 'You can\'t use this account as the source account.', + 'generic_invalid_destination' => 'You can\'t use this account as the destination account.', ]; diff --git a/resources/lang/fr_FR/breadcrumbs.php b/resources/lang/fr_FR/breadcrumbs.php index f3d5a5747a..5e8c946820 100644 --- a/resources/lang/fr_FR/breadcrumbs.php +++ b/resources/lang/fr_FR/breadcrumbs.php @@ -23,35 +23,37 @@ declare(strict_types=1); return [ - 'home' => 'Accueil', - 'edit_currency' => 'Modifier la devise "%name"', - 'delete_currency' => 'Supprimer la devise ":name"', - 'newPiggyBank' => 'Créer une nouvelle tirelire', - 'edit_piggyBank' => 'Modifier la tirelire ":name"', - 'preferences' => 'Préférences', - 'profile' => 'Profil', - 'changePassword' => 'Modifier le mot de passe', - 'change_email' => 'Changer votre adresse e-mail', - 'bills' => 'Factures', - 'newBill' => 'Nouvelle facture', - 'edit_bill' => 'Modifier la facture ":name"', - 'delete_bill' => 'Supprimer la facture ":name"', - 'reports' => 'Rapport', - 'search_result' => 'Résultats de recherche pour ":query"', - 'withdrawal_list' => 'Dépenses', - 'deposit_list' => 'Revenu, salaire et versements', - 'transfer_list' => 'Virements', - 'transfers_list' => 'Virements', - 'reconciliation_list' => 'Rapprochements', - 'create_withdrawal' => 'Créer un nouveau retrait', - 'create_deposit' => 'Créer un nouveau versement', - 'create_transfer' => 'Créer un nouveau virement', - 'edit_journal' => 'Modifier la transaction ":description"', - 'edit_reconciliation' => 'Éditer ":description"', - 'delete_journal' => 'Supprimer la transaction ":description"', - 'tags' => 'Tags', - 'createTag' => 'Créer un nouveau mot-clé', - 'edit_tag' => 'Modifier le tag ":tag"', - 'delete_tag' => 'Supprimer le tag ":tag"', - 'delete_journal_link' => 'Supprimer le lien entre les transactions', + 'home' => 'Accueil', + 'edit_currency' => 'Modifier la devise "%name"', + 'delete_currency' => 'Supprimer la devise ":name"', + 'newPiggyBank' => 'Créer une nouvelle tirelire', + 'edit_piggyBank' => 'Modifier la tirelire ":name"', + 'preferences' => 'Préférences', + 'profile' => 'Profil', + 'changePassword' => 'Modifier le mot de passe', + 'change_email' => 'Changer votre adresse e-mail', + 'bills' => 'Factures', + 'newBill' => 'Nouvelle facture', + 'edit_bill' => 'Modifier la facture ":name"', + 'delete_bill' => 'Supprimer la facture ":name"', + 'reports' => 'Rapport', + 'search_result' => 'Résultats de recherche pour ":query"', + 'withdrawal_list' => 'Dépenses', + 'Withdrawal_list' => 'Expenses', + 'deposit_list' => 'Revenu, salaire et versements', + 'transfer_list' => 'Virements', + 'transfers_list' => 'Virements', + 'reconciliation_list' => 'Rapprochements', + 'create_withdrawal' => 'Créer un nouveau retrait', + 'create_deposit' => 'Créer un nouveau versement', + 'create_transfer' => 'Créer un nouveau virement', + 'create_new_transaction' => 'Créer une nouvelle opération', + 'edit_journal' => 'Modifier la transaction ":description"', + 'edit_reconciliation' => 'Éditer ":description"', + 'delete_journal' => 'Supprimer la transaction ":description"', + 'tags' => 'Tags', + 'createTag' => 'Créer un nouveau mot-clé', + 'edit_tag' => 'Modifier le tag ":tag"', + 'delete_tag' => 'Supprimer le tag ":tag"', + 'delete_journal_link' => 'Supprimer le lien entre les transactions', ]; diff --git a/resources/lang/fr_FR/firefly.php b/resources/lang/fr_FR/firefly.php index ce6bd50716..f06e6f8df5 100644 --- a/resources/lang/fr_FR/firefly.php +++ b/resources/lang/fr_FR/firefly.php @@ -33,7 +33,7 @@ return [ 'last_seven_days' => '7 Derniers Jours', 'last_thirty_days' => 'Trente derniers jours', 'welcomeBack' => 'Que se passe-t-il ?', - 'welcome_back' => 'Que se passe-t-il ?', + 'welcome_back' => 'Que se passe-t-il ?', 'everything' => 'Tout', 'today' => 'aujourd\'hui', 'customRange' => 'Etendue personnalisée', @@ -54,6 +54,7 @@ return [ 'create_new_stuff' => 'Créer de nouvelles choses', 'new_withdrawal' => 'Nouvelle dépense', 'create_new_transaction' => 'Crée une nouvelle transaction', + 'new_transaction' => 'Nouvelle opération', 'go_to_asset_accounts' => 'Afficher vos comptes d\'actifs', 'go_to_budgets' => 'Gérer vos budgets', 'go_to_categories' => 'Gérer vos catégories', @@ -82,28 +83,32 @@ return [ 'help_for_this_page' => 'Aide pour cette page', 'no_help_could_be_found' => 'Aucun texte d\'aide n\'a pu être trouvé.', 'no_help_title' => 'Toutes nos excuses, une erreur est survenue.', - 'two_factor_welcome' => 'Bonjour, :user !', - 'two_factor_enter_code' => 'Pour continuer, veuillez entrer votre code d’authentification à deux facteurs. Votre application peut la générer pour vous.', - 'two_factor_code_here' => 'Entrez votre code ici', - 'two_factor_title' => 'Authentification à deux facteurs', - 'authenticate' => 'S\'authentifier', - 'two_factor_forgot_title' => 'Perte de l’authentification à deux facteurs', - 'two_factor_forgot' => 'J’ai oublié mon code d\'identification à deux facteurs.', - 'two_factor_lost_header' => 'Perdu votre authentification à deux facteurs ?', - 'two_factor_lost_intro' => 'Malheureusement, ce n’est pas quelque chose que vous pouvez réinitialiser depuis l’interface web. Vous avez deux choix.', - 'two_factor_lost_fix_self' => 'Si vous exécutez votre propre instance de Firefly III, vérifiez les logs dans storage/logs pour obtenir des instructions.', - 'two_factor_lost_fix_owner' => 'Dans le cas contraire, contactez le propriétaire du site par courriel :site_owner et demandez-lui de réinitialiser votre authentification à deux facteurs.', - 'warning_much_data' => ':days jours de données peuvent prendre un certain temps à charger.', - 'registered' => 'Vous avez été enregistré avec succès !', - 'Default asset account' => 'Compte d’actif par défaut', - 'no_budget_pointer' => 'Vous semblez n’avoir encore aucun budget. Vous devriez en créer un sur la page des budgets. Les budgets peuvent vous aider à garder une trace des dépenses.', - 'Savings account' => 'Compte d’épargne', - 'Credit card' => 'Carte de Crédit', - 'source_accounts' => 'Compte(s) source', - 'destination_accounts' => 'Compte(s) de destination', - 'user_id_is' => 'Votre identifiant d’utilisateur est :user', - 'field_supports_markdown' => 'Ce champ prend en charge la syntaxe Markdown.', - 'need_more_help' => 'Si vous désirez plus de renseignements sur l\'utilisation de Firefly III, merci d\'ouvrir un ticket sur Github.', + 'two_factor_welcome' => 'Hello!', + 'two_factor_enter_code' => 'Pour continuer, veuillez entrer votre code d’authentification à deux facteurs. Votre application peut la générer pour vous.', + 'two_factor_code_here' => 'Entrez votre code ici', + 'two_factor_title' => 'Authentification à deux facteurs', + 'authenticate' => 'S\'authentifier', + 'two_factor_forgot_title' => 'Perte de l’authentification à deux facteurs', + 'two_factor_forgot' => 'J’ai oublié mon code d\'identification à deux facteurs.', + 'two_factor_lost_header' => 'Perdu votre authentification à deux facteurs ?', + 'two_factor_lost_intro' => 'If you lost your backup codes as well, you have bad luck. This is not something you can fix from the web interface. You have two choices.', + 'two_factor_lost_fix_self' => 'If you run your own instance of Firefly III, check the logs in storage/logs for instructions, or run docker logs <container_id> to see the instructions (refresh this page).', + 'two_factor_lost_fix_owner' => 'Dans le cas contraire, contactez le propriétaire du site par courriel :site_owner et demandez-lui de réinitialiser votre authentification à deux facteurs.', + 'mfa_backup_code' => 'You have used a backup code to login to Firefly III. It can\'t be used again, so cross it from your list.', + 'pref_two_factor_new_backup_codes' => 'Get new backup codes', + 'pref_two_factor_backup_code_count' => 'You have :count valid backup code(s).', + '2fa_i_have_them' => 'I stored them!', + 'warning_much_data' => ':days jours de données peuvent prendre un certain temps à charger.', + 'registered' => 'Vous avez été enregistré avec succès !', + 'Default asset account' => 'Compte d’actif par défaut', + 'no_budget_pointer' => 'Vous semblez n’avoir encore aucun budget. Vous devriez en créer un sur la page des budgets. Les budgets peuvent vous aider à garder une trace des dépenses.', + 'Savings account' => 'Compte d’épargne', + 'Credit card' => 'Carte de Crédit', + 'source_accounts' => 'Compte(s) source', + 'destination_accounts' => 'Compte(s) de destination', + 'user_id_is' => 'Votre identifiant d’utilisateur est :user', + 'field_supports_markdown' => 'Ce champ prend en charge la syntaxe Markdown.', + 'need_more_help' => 'Si vous désirez plus de renseignements sur l\'utilisation de Firefly III, merci d\'ouvrir un ticket sur Github.', 'reenable_intro_text' => 'Vous pouvez également réactiver le guide d\'introduction.', 'intro_boxes_after_refresh' => 'Les boîtes d\'introduction réapparaîtront lorsque vous actualiserez la page.', 'show_all_no_filter' => 'Montrer toutes les transactions sans les classer par date.', @@ -220,21 +225,23 @@ return [ 'search_query' => 'Requête', 'search_found_transactions' => 'Firefly III a trouvé :count opération(s) en :time secondes.', 'search_for_query' => 'Firefly III recherche des opérations contenant tous ces mots : :query', - 'search_modifier_amount_is' => 'Le montant est exactement :value', - 'search_modifier_amount' => 'Le montant est exactement :value', - 'search_modifier_amount_max' => 'Le montant est au plus :value', - 'search_modifier_amount_min' => 'Le montant est au moins :value', - 'search_modifier_amount_less' => 'Le montant est inférieur à :value', - 'search_modifier_amount_more' => 'Le montant est supérieur à :value', - 'search_modifier_source' => 'Le compte source est :value', - 'search_modifier_destination' => 'Le compte de destination est :value', - 'search_modifier_category' => 'La catégorie est :value', - 'search_modifier_budget' => 'Le budget est :value', - 'search_modifier_bill' => 'La facture est :value', - 'search_modifier_type' => 'Le type de l\'opération est :value', - 'search_modifier_date' => 'La date de l\'opération est :value', - 'search_modifier_date_before' => 'La date de l\'opération est avant :value', - 'search_modifier_date_after' => 'La date de l\'opération est après :value', + 'search_modifier_amount_is' => 'Le montant est exactement :value', + 'search_modifier_amount' => 'Le montant est exactement :value', + 'search_modifier_amount_max' => 'Le montant est au plus :value', + 'search_modifier_amount_min' => 'Le montant est au moins :value', + 'search_modifier_amount_less' => 'Le montant est inférieur à :value', + 'search_modifier_amount_more' => 'Le montant est supérieur à :value', + 'search_modifier_source' => 'Le compte source est :value', + 'search_modifier_from' => 'Source account is :value', + 'search_modifier_destination' => 'Le compte de destination est :value', + 'search_modifier_to' => 'Destination account is :value', + 'search_modifier_category' => 'La catégorie est :value', + 'search_modifier_budget' => 'Le budget est :value', + 'search_modifier_bill' => 'La facture est :value', + 'search_modifier_type' => 'Le type de l\'opération est :value', + 'search_modifier_date' => 'La date de l\'opération est :value', + 'search_modifier_date_before' => 'La date de l\'opération est avant :value', + 'search_modifier_date_after' => 'La date de l\'opération est après :value', 'search_modifier_on' => 'La date de l\'opération est :value', 'search_modifier_before' => 'La date de l\'opération est avant :value', 'search_modifier_after' => 'La date de l\'opération est après :value', @@ -257,33 +264,6 @@ return [ 'half-year' => 'semestriel', 'yearly' => 'annuel', - // export data: - 'import_and_export' => 'Importer et Exporter', - 'export_data' => 'Exporter les données', - 'export_and_backup_data' => 'Exporter les données', - 'export_data_intro' => 'Utilisez les données exportées pour passer à une nouvelle application financière. Veuillez noter que ces fichiers ne sont pas destinés à être sauvegardés. Ils ne contiennent pas assez de métadonnées pour restaurer complètement une nouvelle installation de Firefly III. Si vous souhaitez effectuer une sauvegarde de vos données, veuillez sauvegarder la base de données directement.', - 'export_format' => 'Format d\'export', - 'export_format_csv' => 'Valeurs séparées par des virgules (fichier CSV)', - 'export_format_mt940' => 'Format compatible MT940', - 'include_old_uploads_help' => 'Firefly III ne détruit pas les fichiers CSV originaux que vous avez déjà importé dans le passé. Vous pouvez les inclure dans votre exportation.', - 'do_export' => 'Exporter', - 'export_status_never_started' => 'L’exportation n’a pas encore commencé', - 'export_status_make_exporter' => 'Créer un export...', - 'export_status_collecting_journals' => 'Collecte de vos opérations...', - 'export_status_collected_journals' => 'Vos opérations sont collectées !', - 'export_status_converting_to_export_format' => 'Conversion de vos opérations...', - 'export_status_converted_to_export_format' => 'Vos opérations sont converties !', - 'export_status_creating_journal_file' => 'Création du fichier d\'export...', - 'export_status_created_journal_file' => 'Fichier d\'export créé !', - 'export_status_collecting_attachments' => 'Collecte toutes vos pièces jointes...', - 'export_status_collected_attachments' => 'Toutes vos pièces jointes sont collectées !', - 'export_status_collecting_old_uploads' => 'Tous vos précédents uploads sont en cours de collecte...', - 'export_status_collected_old_uploads' => 'Tous vos précédents uploads sont collectés !', - 'export_status_creating_zip_file' => 'Création d’un fichier zip...', - 'export_status_created_zip_file' => 'Fichier zip créé !', - 'export_status_finished' => 'L\'export s\'est terminé avec succès ! Yay !', - 'export_data_please_wait' => 'Veuillez patienter...', - // rules 'rules' => 'Règles', 'rule_name' => 'Nom de la règle', @@ -502,30 +482,33 @@ return [ 'pref_custom_fiscal_year_help' => 'Dans les pays qui utilisent une année financière autre que du 1er janvier au 31 décembre, vous pouvez la changer en spécifiant le jour de début et de fin de l\'année fiscale', 'pref_fiscal_year_start_label' => 'Date du début de l\'année fiscale', 'pref_two_factor_auth' => 'Validation en 2 étapes', - 'pref_two_factor_auth_help' => 'Lorsque vous activez la validation en 2 étapes (également connu sous le nom de deux facteurs d’authentification), vous ajoutez une couche de sécurité supplémentaire à votre compte. Vous vous connectez avec quelque chose que vous connaissez (votre mot de passe) et quelque chose que vous avez (un code de vérification). Les codes de vérification sont générés par une application sur votre téléphone, comme par exemple Authy ou Google Authenticator.', - 'pref_enable_two_factor_auth' => 'Activez la validation en 2 étapes', - 'pref_two_factor_auth_disabled' => 'Le code de vérification en deux étapes a été enlevé et désactivé', - 'pref_two_factor_auth_remove_it' => 'N’oubliez pas de supprimer ce compte de votre application d’authentification !', - 'pref_two_factor_auth_code' => 'Vérifier le code', - 'pref_two_factor_auth_code_help' => 'Scanner le code QR avec une application sur votre téléphone comme Authy ou Google Authenticator et entrez le code généré.', - 'pref_two_factor_auth_reset_code' => 'Réinitialiser le code de vérification', - 'pref_two_factor_auth_disable_2fa' => 'Désactiver l\'authentification en deux étapes', - '2fa_use_secret_instead' => 'Si vous ne pouvez pas scanner le code QR, vous pouvez utiliser le secret : :secret.', - 'pref_save_settings' => 'Enregistrer les paramètres', - 'saved_preferences' => 'Préférences enregistrées !', - 'preferences_general' => 'Général', - 'preferences_frontpage' => 'Écran d\'accueil', - 'preferences_security' => 'Sécurité', - 'preferences_layout' => 'Mise en Page', - 'pref_home_show_deposits' => 'Afficher les dépôts sur l\'écran d\'accueil', - 'pref_home_show_deposits_info' => 'L\'écran d\'accueil affiche déjà vos comptes de dépenses. Devrait-il aussi afficher vos comptes de revenus ?', - 'pref_home_do_show_deposits' => 'Oui, montrez-les', - 'successful_count' => 'dont :count avec succès', - 'list_page_size_title' => 'Taille de la page', - 'list_page_size_help' => 'Les listes d\'éléments (comptes, transactions, etc) affichent un nombre maximum d\'élément par page.', - 'list_page_size_label' => 'Nombre d\'élément par page', - 'between_dates' => '(:start et :end)', - 'pref_optional_fields_transaction' => 'Champs optionnels pour les transactions', + 'pref_two_factor_auth_help' => 'Lorsque vous activez la validation en 2 étapes (également connu sous le nom de deux facteurs d’authentification), vous ajoutez une couche de sécurité supplémentaire à votre compte. Vous vous connectez avec quelque chose que vous connaissez (votre mot de passe) et quelque chose que vous avez (un code de vérification). Les codes de vérification sont générés par une application sur votre téléphone, comme par exemple Authy ou Google Authenticator.', + 'pref_enable_two_factor_auth' => 'Activez la validation en 2 étapes', + 'pref_two_factor_auth_disabled' => 'Le code de vérification en deux étapes a été enlevé et désactivé', + 'pref_two_factor_auth_remove_it' => 'N’oubliez pas de supprimer ce compte de votre application d’authentification !', + 'pref_two_factor_auth_code' => 'Vérifier le code', + 'pref_two_factor_auth_code_help' => 'Scanner le code QR avec une application sur votre téléphone comme Authy ou Google Authenticator et entrez le code généré.', + 'pref_two_factor_auth_reset_code' => 'Réinitialiser le code de vérification', + 'pref_two_factor_auth_disable_2fa' => 'Désactiver l\'authentification en deux étapes', + '2fa_use_secret_instead' => 'If you cannot scan the QR code, feel free to use the secret instead: :secret.', + '2fa_backup_codes' => 'Store these backup codes for access in case you lose your device.', + '2fa_already_enabled' => '2-step verification is already enabled.', + 'wrong_mfa_code' => 'This MFA code is not valid.', + 'pref_save_settings' => 'Enregistrer les paramètres', + 'saved_preferences' => 'Préférences enregistrées !', + 'preferences_general' => 'Général', + 'preferences_frontpage' => 'Écran d\'accueil', + 'preferences_security' => 'Sécurité', + 'preferences_layout' => 'Mise en Page', + 'pref_home_show_deposits' => 'Afficher les dépôts sur l\'écran d\'accueil', + 'pref_home_show_deposits_info' => 'L\'écran d\'accueil affiche déjà vos comptes de dépenses. Devrait-il aussi afficher vos comptes de revenus ?', + 'pref_home_do_show_deposits' => 'Oui, montrez-les', + 'successful_count' => 'dont :count avec succès', + 'list_page_size_title' => 'Taille de la page', + 'list_page_size_help' => 'Les listes d\'éléments (comptes, transactions, etc) affichent un nombre maximum d\'élément par page.', + 'list_page_size_label' => 'Nombre d\'élément par page', + 'between_dates' => '(:start et :end)', + 'pref_optional_fields_transaction' => 'Champs optionnels pour les transactions', 'pref_optional_fields_transaction_help' => 'Par défaut, tous les champs ne sont pas disponibles lors de la création d\'une nouvelle transaction (pour éviter de surcharger la page). Vous pouvez activer les champs suivants si vous pensez qu\'ils peuvent vous être utiles. Bien sûr, tout champ désactivé mais précédemment rempli restera visible quel que soit son paramétrage.', 'optional_tj_date_fields' => 'Champ date', 'optional_tj_business_fields' => 'Champs professionnels', @@ -579,24 +562,25 @@ return [ 'login_with_new_email' => 'Vous pouvez désormais vous connecter avec votre nouvelle adresse e-mail.', 'login_with_old_email' => 'Vous pouvez à nouveau vous connecter à l\'aide de votre ancienne adresse e-mail.', 'login_provider_local_only' => 'Cette action n’est pas disponible lors de l’authentification par ":login_provider".', - 'delete_local_info_only' => 'Comme vous vous authentifiez via ":login_provider", cela ne supprimera que les informations locales de Firefly III.', + 'delete_local_info_only' => 'Comme vous vous authentifiez via ":login_provider", cela ne supprimera que les informations locales de Firefly III.', // attachments - 'nr_of_attachments' => 'Une pièce jointe|:count pièces jointes', - 'attachments' => 'Pièces jointes', - 'edit_attachment' => 'Modifier la pièce jointe ":name"', - 'update_attachment' => 'Mettre à jour la pièce jointe', - 'delete_attachment' => 'Supprimer la pièce jointe ":name"', - 'attachment_deleted' => 'Pièce jointe ":name" supprimée', - 'attachment_updated' => 'Pièce jointe ":name" mise à jour', - 'upload_max_file_size' => 'Taille maximum du fichier : :size', - 'list_all_attachments' => 'Liste de toutes les pièces jointes', + 'nr_of_attachments' => 'Une pièce jointe|:count pièces jointes', + 'attachments' => 'Pièces jointes', + 'edit_attachment' => 'Modifier la pièce jointe ":name"', + 'update_attachment' => 'Mettre à jour la pièce jointe', + 'delete_attachment' => 'Supprimer la pièce jointe ":name"', + 'attachment_deleted' => 'Pièce jointe ":name" supprimée', + 'liabilities_deleted' => 'Deleted liability ":name"', + 'attachment_updated' => 'Pièce jointe ":name" mise à jour', + 'upload_max_file_size' => 'Taille maximum du fichier : :size', + 'list_all_attachments' => 'Liste de toutes les pièces jointes', // transaction index - 'title_expenses' => 'Dépenses', - 'title_withdrawal' => 'Dépenses', - 'title_revenue' => 'Recette / revenu', - 'title_deposit' => 'Recette / revenu', + 'title_expenses' => 'Dépenses', + 'title_withdrawal' => 'Dépenses', + 'title_revenue' => 'Recette / revenu', + 'title_deposit' => 'Recette / revenu', 'title_transfer' => 'Transferts', 'title_transfers' => 'Transferts', @@ -634,6 +618,9 @@ return [ 'converted_to_Transfer' => 'La transaction a été convertie en transfert', 'invalid_convert_selection' => 'Le compte que vous avez sélectionné est déjà utilisé dans cette transaction ou n\'existe pas.', 'source_or_dest_invalid' => 'Impossible de trouver les détails de transaction corrects. La conversion n\'est pas possible.', + 'convert_to_withdrawal' => 'Convertir en retrait', + 'convert_to_deposit' => 'Convertir en dépôt', + 'convert_to_transfer' => 'Convertir en transfert', // create new stuff: 'create_new_withdrawal' => 'Créer une nouvelle dépense', @@ -695,7 +682,9 @@ return [ 'available_amount_indication' => 'Utilisez ces montants pour avoir une indication de ce que pourrait être votre budget total.', 'suggested' => 'Suggéré', 'average_between' => 'Moyenne entre :start et :end', - 'over_budget_warn' => ' Normalement vous budgétez :amount par jour. Là c\'est :over_amount par jour.', + 'over_budget_warn' => ' Généralement vous budgétez environ :amount par jour. Cette fois, c\'est :over_amount par jour. Êtes-vous sûr ?', + 'transferred_in' => 'Transféré (entrant)', + 'transferred_away' => 'Transféré (sortant)', // bills: 'match_between_amounts' => 'La facture correspond à des transactions entre :low et :high.', @@ -779,6 +768,7 @@ return [ 'reconcile_options' => 'Options de rapprochement', 'reconcile_range' => 'Plage de rapprochement', 'start_reconcile' => 'Commencer le rapprochement', + 'cash_account_type' => 'Cash', 'cash' => 'espèces', 'account_type' => 'Type de compte', 'save_transactions_by_moving' => 'Enregistrer ces opération(s) en les déplaçant vers un autre compte :', @@ -803,7 +793,9 @@ return [ 'reconcile_go_back' => 'Vous pouvez toujours modifier ou supprimer une correction ultérieurement.', 'must_be_asset_account' => 'Vous pouvez uniquement rapprocher les comptes d\'actifs', 'reconciliation_stored' => 'Rapprochement stocké', - 'reconcilliation_transaction_title' => 'Rapprochement (:from vers :to)', + 'reconciliation_error' => 'En raison d\'une erreur, les transactions ont été marquées comme réconciliées, mais la correction n\'a pas été enregistrée : :error.', + 'reconciliation_transaction_title' => 'Rapprochement (:from vers :to)', + 'sum_of_reconciliation' => 'Total des rapprochements', 'reconcile_this_account' => 'Rapprocher ce compte', 'confirm_reconciliation' => 'Confirmer le rapprochement', 'submitted_start_balance' => 'Solde initial soumis', @@ -815,7 +807,7 @@ return [ 'interest_calc_daily' => 'Par jour', 'interest_calc_monthly' => 'Par mois', 'interest_calc_yearly' => 'Par an', - 'initial_balance_account' => 'Solde initial du compte :name', + 'initial_balance_account' => 'Solde initial du compte :account', // categories: 'new_category' => 'Nouvelle catégorie', @@ -847,6 +839,8 @@ return [ 'deleted_deposit' => 'Dépôt ":name" correctement supprimé', 'deleted_transfer' => 'Opération ":name" correctement supprimée', 'stored_journal' => 'Opération ":description" créée avec succès', + 'stored_journal_no_descr' => 'Nouvelle transaction créée avec succès', + 'updated_journal_no_descr' => 'Votre transaction a été mise à jour avec succès', 'select_transactions' => 'Sélectionner des opérations', 'rule_group_select_transactions' => 'Appliquer le groupe de règles ":title" sur les transactions', 'rule_select_transactions' => 'Appliquer la règle ":title" sur les transactions', @@ -855,26 +849,33 @@ return [ 'mass_delete_journals' => 'Supprimer un certain nombre de transactions', 'mass_edit_journals' => 'Modifier un certain nombre d’opérations', 'mass_bulk_journals' => 'Modifier un certain nombre d’opérations en masse', - 'mass_bulk_journals_explain' => 'Si vous ne souhaitez pas modifier vos transactions une à une en utilisant la fonction d\'édition en masse, vous pouvez les mettre à jour en une seule fois. Sélectionnez simplement la catégorie, le·s tag·s ou le budget préféré dans les champs ci-dessous, et toutes les transactions dans le tableau seront mises à jour.', + 'mass_bulk_journals_explain' => 'This form allows you to change properties of the transactions listed below in one sweeping update. All the transactions in the table will be updated when you change the parameters you see here.', + 'part_of_split' => 'This transaction is part of a split transaction. If you have not selected all the splits, you may end up with changing only half the transaction.', 'bulk_set_new_values' => 'Utilisez les entrées ci-dessous pour définir de nouvelles valeurs. Si vous les laissez vides, ils seront vides pour tous. Notez également que seuls les retraits recevront un budget.', 'no_bulk_category' => 'Ne pas mettre à jour la catégorie', 'no_bulk_budget' => 'Ne pas mettre à jour le budget', - 'no_bulk_tags' => 'Ne pas mettre à jour le·s tag·s', - 'bulk_edit' => 'Modification en masse', - 'cannot_edit_other_fields' => 'Vous ne pouvez pas modifier en masse d\'autres champs que ceux-ci, car il n’y a pas de place pour tous les afficher. S’il vous plaît suivez le lien et modifiez les un par un si vous devez modifier ces champs.', - 'no_budget' => '(pas de budget)', - 'no_budget_squared' => '(pas de budget)', - 'perm-delete-many' => 'Supprimer de nombreux éléments en une seule fois peut engendrer des erreurs. Soyez prudent.', - 'mass_deleted_transactions_success' => 'Montant des opérations supprimées : :amount.', - 'mass_edited_transactions_success' => 'Montant des opérations mises à jour : :amount', - 'opt_group_' => '(aucun type de compte)', + 'no_bulk_tags' => 'Ne pas mettre à jour le·s tag·s', + 'mass_edit' => 'Edit selected individually', + 'bulk_edit' => 'Edit selected in bulk', + 'mass_delete' => 'Delete selected', + 'cannot_edit_other_fields' => 'Vous ne pouvez pas modifier en masse d\'autres champs que ceux-ci, car il n’y a pas de place pour tous les afficher. S’il vous plaît suivez le lien et modifiez les un par un si vous devez modifier ces champs.', + 'cannot_change_amount_reconciled' => 'You can\'t change the amount of reconciled transactions.', + 'no_budget' => '(pas de budget)', + 'no_budget_squared' => '(pas de budget)', + 'perm-delete-many' => 'Deleting many items in one go can be very disruptive. Please be cautious. You can delete part of a split transaction from this page, so take care.', + 'mass_deleted_transactions_success' => 'Montant des opérations supprimées : :amount.', + 'mass_edited_transactions_success' => 'Montant des opérations mises à jour : :amount', + 'opt_group_' => '(aucun type de compte)', 'opt_group_no_account_type' => '(aucun type de compte)', 'opt_group_defaultAsset' => 'Comptes d\'actifs par défaut', 'opt_group_savingAsset' => 'Comptes d\'épargne', 'opt_group_sharedAsset' => 'Comptes d\'actifs partagés', 'opt_group_ccAsset' => 'Cartes de crédit', 'opt_group_cashWalletAsset' => 'Porte-monnaie', + 'opt_group_expense_account' => 'Expense accounts', + 'opt_group_revenue_account' => 'Revenue accounts', 'opt_group_l_Loan' => 'Passif: Prêt', + 'opt_group_cash_account' => 'Cash account', 'opt_group_l_Debt' => 'Passif: Dette', 'opt_group_l_Mortgage' => 'Passif: Prêt hypothécaire', 'opt_group_l_Credit card' => 'Passif: Carte de crédit', @@ -973,7 +974,7 @@ return [ 'errors' => 'Erreurs', 'debt_start_date' => 'Date de début de la dette', 'debt_start_amount' => 'Montant initial de la dette', - 'debt_start_amount_help' => 'Veuillez saisir le montant d\'origine de ce passif en tant que nombre positif. Vous pouvez également saisir son montant actuel. Assurez-vous de modifier la date ci-dessous pour que cela corresponde.', + 'debt_start_amount_help' => 'Si vous devez une somme, il est préférable d\'entrer un montant négatif parce que cela influence votre avoir net. Si vous êtes redevable d\'une somme, la même chose s\'applique. Consultez les pages d\'aide pour plus d\'informations.', 'store_new_liabilities_account' => 'Enregistrer un nouveau passif', 'edit_liabilities_account' => 'Modifier le passif ":name"', @@ -1145,6 +1146,7 @@ return [ 'deleted_piggy_bank' => 'Tirelire ":name" supprimée', 'added_amount_to_piggy' => 'Ajouté :amount à ":name"', 'removed_amount_from_piggy' => 'Supprimé :amount du ":name"', + 'piggy_events' => 'Tirelires associées', // tags 'delete_tag' => 'Supprimer le tag ":tag"', @@ -1154,47 +1156,57 @@ return [ 'updated_tag' => 'Mise à jour de la balise ":tag"', 'created_tag' => 'Tag ":tag" a été créé !', - 'transaction_journal_information' => 'Informations sur les transactions', - 'transaction_journal_meta' => 'Méta informations', - 'total_amount' => 'Montant total', - 'number_of_decimals' => 'Nombre de décimales', + 'transaction_journal_information' => 'Informations sur les transactions', + 'transaction_journal_meta' => 'Méta informations', + 'transaction_journal_more' => 'Plus d\'informations', + 'att_part_of_journal' => 'Stocké dans ":journal"', + 'total_amount' => 'Montant total', + 'number_of_decimals' => 'Nombre de décimales', // administration - 'administration' => 'Administration', - 'user_administration' => 'Gestion des utilisateurs', - 'list_all_users' => 'Tous les utilisateurs', - 'all_users' => 'Tous les utilisateurs', - 'instance_configuration' => 'Configuration', - 'firefly_instance_configuration' => 'Options de configuration pour Firefly III', - 'setting_single_user_mode' => 'Mode utilisateur unique', - 'setting_single_user_mode_explain' => 'Par défaut, Firefly III accepte uniquement une (1) inscription : vous. Il s\'agit d\'une mesure de sécurité qui empêche les inconnus d\'utiliser votre instance, à moins que vous ne les y autorisiez. Les inscriptions futures sont bloquées. Lorsque vous désactivez cette case, d\'autres personnes peuvent utiliser votre instance, en supposant qu\'elles puissent l\'atteindre (quand elle est connectée à Internet).', - 'store_configuration' => 'Sauvegarder la configuration', - 'single_user_administration' => 'Gestion de l\'utilisateur pour :email', - 'edit_user' => 'Modifier l\'utilisateur :email', - 'hidden_fields_preferences' => 'Tous les champs ne sont pas visibles dès maintenant. Vous devez les activer dans vos paramètres.', - 'user_data_information' => 'Données utilisateur', - 'user_information' => 'Informations utilisateur', - 'total_size' => 'taille totale', - 'budget_or_budgets' => 'budget(s)', - 'budgets_with_limits' => 'budget(s) avec montant configuré', - 'nr_of_rules_in_total_groups' => ':count_rules règle(s) dans :count_groups groupe(s) de règles', - 'tag_or_tags' => 'tag·s', - 'configuration_updated' => 'La configuration a été mise à jour', - 'setting_is_demo_site' => 'Site de démonstration', - 'setting_is_demo_site_explain' => 'Si vous cochez cette case, cette installation se comportera comme si c\'était le site de démonstration, ce qui comporte certaines limitations.', - 'block_code_bounced' => 'Rebond des messages emails', - 'block_code_expired' => 'Compte démo expiré', - 'no_block_code' => 'Aucune raison pour le blocage ou utilisateur non bloqué', - 'block_code_email_changed' => 'L\'utilisateur n\'a pas encore confirmé sa nouvelle adresse e-mail', - 'admin_update_email' => 'Contrairement à la page de profil, l\'utilisateur NE SERA PAS informé que son adresse email a changé !', - 'update_user' => 'Utilisateur mis à jour', - 'updated_user' => 'Les données utilisateur ont bien été modifiées.', - 'delete_user' => 'Supprimer l\'utilisateur :email', - 'user_deleted' => 'L\'utilisateur a été supprimé', - 'send_test_email' => 'Envoyer un message de test', - 'send_test_email_text' => 'Pour vérifier que votre installation est capable d\'envoyer des emails, utilisez ce bouton. Vous ne verrez pas d\'erreur ici (s\'il y en a), elles seront enregistrées dans les logs. Utilisez ce bouton autant de fois que vous le voulez, il n\'y a pas de contrôle de spam. Le message sera envoyé à :email et devrait arriver sous peu.', - 'send_message' => 'Envoyer le message', - 'send_test_triggered' => 'Le test a été initié. Vérifiez votre boîte de réception (et les logs si nécessaire).', + 'administration' => 'Administration', + 'user_administration' => 'Gestion des utilisateurs', + 'list_all_users' => 'Tous les utilisateurs', + 'all_users' => 'Tous les utilisateurs', + 'instance_configuration' => 'Configuration', + 'firefly_instance_configuration' => 'Options de configuration pour Firefly III', + 'setting_single_user_mode' => 'Mode utilisateur unique', + 'setting_single_user_mode_explain' => 'Par défaut, Firefly III accepte uniquement une (1) inscription : vous. Il s\'agit d\'une mesure de sécurité qui empêche les inconnus d\'utiliser votre instance, à moins que vous ne les y autorisiez. Les inscriptions futures sont bloquées. Lorsque vous désactivez cette case, d\'autres personnes peuvent utiliser votre instance, en supposant qu\'elles puissent l\'atteindre (quand elle est connectée à Internet).', + 'store_configuration' => 'Sauvegarder la configuration', + 'single_user_administration' => 'Gestion de l\'utilisateur pour :email', + 'edit_user' => 'Modifier l\'utilisateur :email', + 'hidden_fields_preferences' => 'Vous pouvez activer plus d\'options de transaction dans vos paramètres.', + 'user_data_information' => 'Données utilisateur', + 'user_information' => 'Informations utilisateur', + 'total_size' => 'taille totale', + 'budget_or_budgets' => 'budget(s)', + 'budgets_with_limits' => 'budget(s) avec montant configuré', + 'nr_of_rules_in_total_groups' => ':count_rules règle(s) dans :count_groups groupe(s) de règles', + 'tag_or_tags' => 'tag·s', + 'configuration_updated' => 'La configuration a été mise à jour', + 'setting_is_demo_site' => 'Site de démonstration', + 'setting_is_demo_site_explain' => 'Si vous cochez cette case, cette installation se comportera comme si c\'était le site de démonstration, ce qui comporte certaines limitations.', + 'block_code_bounced' => 'Rebond des messages emails', + 'block_code_expired' => 'Compte démo expiré', + 'no_block_code' => 'Aucune raison pour le blocage ou utilisateur non bloqué', + 'block_code_email_changed' => 'L\'utilisateur n\'a pas encore confirmé sa nouvelle adresse e-mail', + 'admin_update_email' => 'Contrairement à la page de profil, l\'utilisateur NE SERA PAS informé que son adresse email a changé !', + 'update_user' => 'Utilisateur mis à jour', + 'updated_user' => 'Les données utilisateur ont bien été modifiées.', + 'delete_user' => 'Supprimer l\'utilisateur :email', + 'user_deleted' => 'L\'utilisateur a été supprimé', + 'send_test_email' => 'Envoyer un message de test', + 'send_test_email_text' => 'Pour vérifier que votre installation est capable d\'envoyer des emails, utilisez ce bouton. Vous ne verrez pas d\'erreur ici (s\'il y en a), elles seront enregistrées dans les logs. Utilisez ce bouton autant de fois que vous le voulez, il n\'y a pas de contrôle de spam. Le message sera envoyé à :email et devrait arriver sous peu.', + 'send_message' => 'Envoyer le message', + 'send_test_triggered' => 'Le test a été initié. Vérifiez votre boîte de réception (et les logs si nécessaire).', + + 'split_transaction_title' => 'Description de l\'opération ventilée', + 'split_title_help' => 'Si vous créez une opération ventilée, il doit y avoir une description globale pour chaque fractions de l\'opération.', + 'transaction_information' => 'Informations sur l\'opération', + 'you_create_transfer' => 'Vous êtes en train de créer un transfert.', + 'you_create_withdrawal' => 'Vous êtes en train de créer un retrait.', + 'you_create_deposit' => 'Vous êtes en train de créer un dépôt.', + // links 'journal_link_configuration' => 'Configuration des liens de transaction', @@ -1214,12 +1226,13 @@ return [ 'do_not_save_connection' => '(ne pas enregistrer la connexion)', 'link_transaction' => 'Lier la transaction', 'link_to_other_transaction' => 'Lier cette transaction à une autre transaction', - 'select_transaction_to_link' => 'Sélectionnez une transaction à laquelle lier cette transaction', + 'select_transaction_to_link' => 'Select a transaction to link this transaction to. The links are currently unused in Firefly III (apart from being shown), but I plan to change this in the future. Use the search box to select a transaction either by title or by ID. If you want to add custom link types, check out the administration section.', 'this_transaction' => 'Cette transaction', 'transaction' => 'Transaction', 'comments' => 'Commentaires', - 'to_link_not_found' => 'Si la transaction que vous souhaitez lier n\'est pas listée, entrez simplement son identifiant.', + 'link_notes' => 'Any notes you wish to store with the link.', 'invalid_link_selection' => 'Impossible de lier ces transactions', + 'selected_transaction' => 'Selected transaction', 'journals_linked' => 'Ces transactions sont liées.', 'journals_error_linked' => 'Ces transactions sont déjà liées.', 'journals_link_to_self' => 'Vous ne pouvez pas lier une transaction à elle-même', @@ -1258,12 +1271,11 @@ return [ 'split_this_withdrawal' => 'Ventiler ce retrait', 'split_this_deposit' => 'Ventiler ce dépôt', 'split_this_transfer' => 'Ventiler ce transfert', - 'cannot_edit_multiple_source' => 'Vous ne pouvez pas modifier la transaction scindée #:id avec la description ":description" car elle contient plusieurs comptes sources.', - 'cannot_edit_multiple_dest' => 'Vous ne pouvez pas modifier la transaction scindée #:id avec la description ":description" car elle contient plusieurs comptes de destination.', - 'cannot_edit_reconciled' => 'Vous ne pouvez pas modifier transaction #:id avec la description ":description" car elle a été marquée comme rapprochée.', 'cannot_edit_opening_balance' => 'Vous ne pouvez pas modifier le solde d\'ouverture d\'un compte.', 'no_edit_multiple_left' => 'Vous n\'avez sélectionné aucune transaction valide à éditer.', - 'cannot_convert_split_journal' => 'Vous ne pouvez pas convertir une transaction ventilée', + 'breadcrumb_convert_group' => 'Convert transaction', + 'convert_invalid_source' => 'Source information is invalid for transaction #%d.', + 'convert_invalid_destination' => 'Destination information is invalid for transaction #%d.', // Import page (general strings only) 'import_index_title' => 'Importer des opérations dans Firefly III', @@ -1389,4 +1401,15 @@ return [ 'will_jump_monday' => 'Sera créé le lundi plutôt que les week-ends.', 'except_weekends' => 'Sauf les week-ends', 'recurrence_deleted' => 'Opération périodique ":title" supprimée', + + // new lines for summary controller. + 'box_balance_in_currency' => 'Solde (:currency)', + 'box_spent_in_currency' => 'Dépensé (:currency)', + 'box_earned_in_currency' => 'Gagné (:currency)', + 'box_bill_paid_in_currency' => 'Factures payées (:currency)', + 'box_bill_unpaid_in_currency' => 'Factures impayées (:currency)', + 'box_left_to_spend_in_currency' => 'Reste à dépenser (:currency)', + 'box_net_worth_in_currency' => 'Avoir net (:currency)', + 'box_spend_per_day' => 'Reste à dépenser par jour: :amount', + ]; diff --git a/resources/lang/fr_FR/form.php b/resources/lang/fr_FR/form.php index fdbf89cfa6..eb54848b9e 100644 --- a/resources/lang/fr_FR/form.php +++ b/resources/lang/fr_FR/form.php @@ -57,20 +57,21 @@ return [ 'asset_source_account' => 'Compte d\'origine', 'journal_description' => 'Description', 'note' => 'Notes', + 'store_new_transaction' => 'Créer une nouvelle opération', 'split_journal' => 'Ventiler cette opération', 'split_journal_explanation' => 'Ventiler cette opération en plusieurs parties', 'currency' => 'Devise', 'account_id' => 'Compte d’actif', 'budget_id' => 'Budget', - 'openingBalance' => 'Solde initial', + 'opening_balance' => 'Solde initial', 'tagMode' => 'Mode du tag', 'tag_position' => 'Localisation du tag', - 'virtualBalance' => 'Solde virtuel', + 'virtual_balance' => 'Solde virtuel', 'targetamount' => 'Montant cible', - 'accountRole' => 'Rôle du compte', - 'openingBalanceDate' => 'Date du solde initial', - 'ccType' => 'Plan de paiement de carte de crédit', - 'ccMonthlyPaymentDate' => 'Date de paiement mensuelle de carte de crédit', + 'account_role' => 'Rôle du compte', + 'opening_balance_date' => 'Date du solde initial', + 'cc_type' => 'Plan de paiement de carte de crédit', + 'cc_monthly_payment_date' => 'Date de paiement mensuelle de la carte de crédit', 'piggy_bank_id' => 'Tirelire', 'returnHere' => 'Retourner ici', 'returnHereExplanation' => 'Après enregistrement, revenir ici pour en créer un nouveau.', @@ -118,7 +119,7 @@ return [ 'symbol' => 'Symbole', 'code' => 'Code', 'iban' => 'Numéro IBAN', - 'accountNumber' => 'N° de compte', + 'account_number' => 'Numéro de compte', 'creditCardNumber' => 'Numéro de carte de crédit', 'has_headers' => 'Entêtes ', 'date_format' => 'Format de la date', @@ -139,12 +140,8 @@ return [ 'stop_processing' => 'Arrêter le traitement', 'start_date' => 'Début de l\'étendue', 'end_date' => 'Fin de l\'étendue', - 'export_start_range' => 'Début de l’étendue d’exportation', - 'export_end_range' => 'Fin de l’étendue d\'exportation', - 'export_format' => 'Format de fichier', 'include_attachments' => 'Inclure les pièces jointes téléchargées', 'include_old_uploads' => 'Inclure les données importées', - 'accounts' => 'Exporter les opérations depuis ces comptes', 'delete_account' => 'Supprimer le compte ":name"', 'delete_bill' => 'Supprimer la facture ":name"', 'delete_budget' => 'Supprimer le budget ":name"', @@ -256,4 +253,7 @@ return [ 'weekend' => 'Week-end', 'client_secret' => 'Clé secrète', + 'withdrawal_destination_id' => 'Destination account', + 'deposit_source_id' => 'Source account', + ]; diff --git a/resources/lang/fr_FR/import.php b/resources/lang/fr_FR/import.php index 1d3c63459d..1b077615b4 100644 --- a/resources/lang/fr_FR/import.php +++ b/resources/lang/fr_FR/import.php @@ -207,6 +207,10 @@ return [ 'specific_rabo_descr' => 'Corrige d\'éventuels problèmes avec les fichiers Rabobank', 'specific_pres_name' => 'President\'s Choice Financial CA', 'specific_pres_descr' => 'Corrige d\'éventuels problèmes avec les fichiers PC', + 'specific_belfius_name' => 'Belfius BE', + 'specific_belfius_descr' => 'Corrige d\'éventuels problèmes avec les fichiers Belfius', + 'specific_ingbelgium_name' => 'ING BE', + 'specific_ingbelgium_descr' => 'Fixes potential problems with ING Belgium files', // job configuration for file provider (stage: roles) 'job_config_roles_title' => 'Configuration de l\'importation (3/4) - Définir le rôle de chaque colonne', 'job_config_roles_text' => 'Chaque colonne de votre fichier CSV contient des données différentes. Veuillez indiquer quel type de données l’importateur doit attendre. L’option de « mapper » les données signifie que vous allez lier chaque entrée trouvée dans la colonne à une valeur dans votre base de données. Une colonne souvent mappée est celle contenant l\'IBAN du compte opposé. Il est facile de le faire correspondre avec un IBAN déjà présent dans votre base de données.', @@ -291,14 +295,14 @@ return [ 'column_rabo-debit-credit' => 'Indicateur de débit/crédit spécifique à Rabobank', 'column_ing-debit-credit' => 'Indicateur de débit/crédit spécifique à ING', 'column_generic-debit-credit' => 'Indicateur générique de débit/crédit bancaire', - 'column_sepa-ct-id' => 'Référence de bout en bout SEPA', - 'column_sepa-ct-op' => 'Référence SEPA du compte opposé', - 'column_sepa-db' => 'Référence Unique de Mandat SEPA', - 'column_sepa-cc' => 'Code de rapprochement SEPA', - 'column_sepa-ci' => 'Identifiant Créancier SEPA', - 'column_sepa-ep' => 'Objectif externe SEPA', - 'column_sepa-country' => 'Code de pays SEPA', - 'column_sepa-batch-id' => 'ID de lot SEPA', + 'column_sepa_ct_id' => 'Identificateur de bout en bout SEPA', + 'column_sepa_ct_op' => 'Identifiant de compte SEPA opposable', + 'column_sepa_db' => 'Identifiant de mandat SEPA', + 'column_sepa_cc' => 'Code de rapprochement SEPA', + 'column_sepa_ci' => 'Identifiant Créancier SEPA', + 'column_sepa_ep' => 'Usage externe SEPA', + 'column_sepa_country' => 'Pays SEPA', + 'column_sepa_batch_id' => 'ID de lot SEPA', 'column_tags-comma' => 'Tags (séparés par des virgules)', 'column_tags-space' => 'Tags (séparés par un espace)', 'column_account-number' => 'Compte d’actif (numéro de compte)', @@ -306,4 +310,7 @@ return [ 'column_note' => 'Note(s)', 'column_internal-reference' => 'Référence interne', + // error message + 'duplicate_row' => 'Row #:row (":description") could not be imported. It already exists.', + ]; diff --git a/resources/lang/fr_FR/intro.php b/resources/lang/fr_FR/intro.php index 610fcb46fa..bb27bc9743 100644 --- a/resources/lang/fr_FR/intro.php +++ b/resources/lang/fr_FR/intro.php @@ -24,39 +24,61 @@ declare(strict_types=1); return [ // index - 'index_intro' => 'Bienvenue sur la page d\'accueil de Firefly III. Veuillez prendre le temps de parcourir l\'introduction pour comprendre comment Firefly III fonctionne.', - 'index_accounts-chart' => 'Ce tableau montre le solde actuel de vos comptes d\'actifs. Vous pouvez sélectionner les comptes visibles ici dans vos préférences.', - 'index_box_out_holder' => 'Cette petite boîte et les cases à côté de celle-ci vous donneront un rapide aperçu de votre situation financière.', - 'index_help' => 'Si vous avez besoin d’aide avec une page ou un formulaire, appuyez sur ce bouton.', - 'index_outro' => 'La plupart des pages de Firefly III vont commencer avec un petit tour comme celui-ci. Merci de me contacter si vous avez des questions ou des commentaires. Profitez-en !', - 'index_sidebar-toggle' => 'Pour créer de nouvelles transactions, comptes ou autres choses, utilisez le menu sous cette icône.', + 'index_intro' => 'Bienvenue sur la page d\'accueil de Firefly III. Veuillez prendre le temps de parcourir l\'introduction pour comprendre comment Firefly III fonctionne.', + 'index_accounts-chart' => 'Ce tableau montre le solde actuel de vos comptes d\'actifs. Vous pouvez sélectionner les comptes visibles ici dans vos préférences.', + 'index_box_out_holder' => 'Cette petite boîte et les cases à côté de celle-ci vous donneront un rapide aperçu de votre situation financière.', + 'index_help' => 'Si vous avez besoin d’aide avec une page ou un formulaire, appuyez sur ce bouton.', + 'index_outro' => 'La plupart des pages de Firefly III vont commencer avec un petit tour comme celui-ci. Merci de me contacter si vous avez des questions ou des commentaires. Profitez-en !', + 'index_sidebar-toggle' => 'Pour créer de nouvelles transactions, comptes ou autres choses, utilisez le menu sous cette icône.', + 'index_cash_account' => 'These are the accounts created so far. You can use the cash account to track cash expenses but it\'s not mandatory of course.', + + // transactions (withdrawal) + 'transactions_create_withdrawal_source' => 'Select your favorite asset account or liability from this dropdown.', + 'transactions_create_withdrawal_destination' => 'Select an expense account here. Leave it empty if you want to make a cash expense.', + 'transactions_create_withdrawal_foreign_currency' => 'Use this field to set a foreign currency and amount.', + 'transactions_create_withdrawal_more_meta' => 'Plenty of other meta data you set in these fields.', + 'transactions_create_withdrawal_split_add' => 'If you want to split a transaction, add more splits with this button', + + // transactions (deposit) + 'transactions_create_deposit_source' => 'Select or type the payee in this auto-completing dropdown/textbox. Leave it empty if you want to make a cash deposit.', + 'transactions_create_deposit_destination' => 'Select an asset or liability account here.', + 'transactions_create_deposit_foreign_currency' => 'Use this field to set a foreign currency and amount.', + 'transactions_create_deposit_more_meta' => 'Plenty of other meta data you set in these fields.', + 'transactions_create_deposit_split_add' => 'If you want to split a transaction, add more splits with this button', + + // transactions (transfer) + 'transactions_create_transfer_source' => 'Select the source asset account here.', + 'transactions_create_transfer_destination' => 'Select the destination asset account here.', + 'transactions_create_transfer_foreign_currency' => 'Use this field to set a foreign currency and amount.', + 'transactions_create_transfer_more_meta' => 'Plenty of other meta data you set in these fields.', + 'transactions_create_transfer_split_add' => 'If you want to split a transaction, add more splits with this button', // create account: - 'accounts_create_iban' => 'Donnez à vos comptes un IBAN valide. Cela pourrait rendre une importation de données très facile à l\'avenir.', - 'accounts_create_asset_opening_balance' => 'Les comptes d\'actifs peuvent avoir un «solde d\'ouverture», indiquant le début de l\'historique de ce compte dans Firefly III.', - 'accounts_create_asset_currency' => 'Firefly III prend en charge plusieurs devises. Les comptes d\'actifs ont une devise principale, que vous devez définir ici.', - 'accounts_create_asset_virtual' => 'Il peut parfois être utile de donner à votre compte un solde virtuel : un montant supplémentaire toujours ajouté ou soustrait du solde réel.', + 'accounts_create_iban' => 'Donnez à vos comptes un IBAN valide. Cela pourrait rendre une importation de données très facile à l\'avenir.', + 'accounts_create_asset_opening_balance' => 'Les comptes d\'actifs peuvent avoir un «solde d\'ouverture», indiquant le début de l\'historique de ce compte dans Firefly III.', + 'accounts_create_asset_currency' => 'Firefly III prend en charge plusieurs devises. Les comptes d\'actifs ont une devise principale, que vous devez définir ici.', + 'accounts_create_asset_virtual' => 'Il peut parfois être utile de donner à votre compte un solde virtuel : un montant supplémentaire toujours ajouté ou soustrait du solde réel.', // budgets index - 'budgets_index_intro' => 'Les budgets sont utilisés pour gérer vos finances et forment l\'une des principales fonctions de Firefly III.', - 'budgets_index_set_budget' => 'Définissez votre budget total pour chaque période afin que Firefly III puisse vous dire si vous avez budgétisé tout l\'argent disponible.', - 'budgets_index_see_expenses_bar' => 'Dépenser de l\'argent va lentement remplir cette barre.', - 'budgets_index_navigate_periods' => 'Parcourez des périodes pour régler facilement les budgets à l\'avance.', - 'budgets_index_new_budget' => 'Créez de nouveaux budgets comme bon vous semble.', - 'budgets_index_list_of_budgets' => 'Utilisez ce tableau pour définir les montants pour chaque budget et voir comment vous vous en sortez.', - 'budgets_index_outro' => 'Pour en savoir plus sur la budgétisation, utilisez l\'icône d\'aide en haut à droite.', + 'budgets_index_intro' => 'Les budgets sont utilisés pour gérer vos finances et forment l\'une des principales fonctions de Firefly III.', + 'budgets_index_set_budget' => 'Définissez votre budget total pour chaque période afin que Firefly III puisse vous dire si vous avez budgétisé tout l\'argent disponible.', + 'budgets_index_see_expenses_bar' => 'Dépenser de l\'argent va lentement remplir cette barre.', + 'budgets_index_navigate_periods' => 'Parcourez des périodes pour régler facilement les budgets à l\'avance.', + 'budgets_index_new_budget' => 'Créez de nouveaux budgets comme bon vous semble.', + 'budgets_index_list_of_budgets' => 'Utilisez ce tableau pour définir les montants pour chaque budget et voir comment vous vous en sortez.', + 'budgets_index_outro' => 'Pour en savoir plus sur la budgétisation, utilisez l\'icône d\'aide en haut à droite.', // reports (index) - 'reports_index_intro' => 'Utilisez ces rapports pour obtenir des informations détaillées sur vos finances.', - 'reports_index_inputReportType' => 'Choisissez un type de rapport. Consultez les pages d\'aide pour voir ce que vous présente chaque rapport.', - 'reports_index_inputAccountsSelect' => 'Vous pouvez exclure ou inclure les comptes d\'actifs comme bon vous semble.', - 'reports_index_inputDateRange' => 'La plage de dates sélectionnée est entièrement libre : de un jour à 10 ans.', - 'reports_index_extra-options-box' => 'Selon le rapport que vous avez sélectionné, vous pouvez sélectionner des filtres et options supplémentaires ici. Regardez cette case lorsque vous modifiez les types de rapport.', + 'reports_index_intro' => 'Utilisez ces rapports pour obtenir des informations détaillées sur vos finances.', + 'reports_index_inputReportType' => 'Choisissez un type de rapport. Consultez les pages d\'aide pour voir ce que vous présente chaque rapport.', + 'reports_index_inputAccountsSelect' => 'Vous pouvez exclure ou inclure les comptes d\'actifs comme bon vous semble.', + 'reports_index_inputDateRange' => 'La plage de dates sélectionnée est entièrement libre : de un jour à 10 ans.', + 'reports_index_extra-options-box' => 'Selon le rapport que vous avez sélectionné, vous pouvez sélectionner des filtres et options supplémentaires ici. Regardez cette case lorsque vous modifiez les types de rapport.', // reports (reports) - 'reports_report_default_intro' => 'Ce rapport vous donnera un aperçu rapide et complet de vos finances. Si vous souhaitez y voir autre chose, n\'hésitez pas à me contacter !', - 'reports_report_audit_intro' => 'Ce rapport vous donnera des informations détaillées sur vos comptes d\'actifs.', - 'reports_report_audit_optionsBox' => 'Utilisez ces cases à cocher pour afficher ou masquer les colonnes qui vous intéressent.', + 'reports_report_default_intro' => 'Ce rapport vous donnera un aperçu rapide et complet de vos finances. Si vous souhaitez y voir autre chose, n\'hésitez pas à me contacter !', + 'reports_report_audit_intro' => 'Ce rapport vous donnera des informations détaillées sur vos comptes d\'actifs.', + 'reports_report_audit_optionsBox' => 'Utilisez ces cases à cocher pour afficher ou masquer les colonnes qui vous intéressent.', 'reports_report_category_intro' => 'Ce rapport vous donnera un aperçu d\'une ou de plusieurs catégories.', 'reports_report_category_pieCharts' => 'Ces tableaux vous donneront un aperçu des dépenses et du revenu par catégorie ou par compte.', diff --git a/resources/lang/fr_FR/list.php b/resources/lang/fr_FR/list.php index b53ccc8f5b..6d8c67033d 100644 --- a/resources/lang/fr_FR/list.php +++ b/resources/lang/fr_FR/list.php @@ -23,71 +23,71 @@ declare(strict_types=1); return [ - 'buttons' => 'Boutons', - 'icon' => 'Icône', - 'id' => 'Identifiant', - 'create_date' => 'Créé le', - 'update_date' => 'Mis à jour le', - 'updated_at' => 'Mis à jour le', - 'balance_before' => 'Solde avant', - 'balance_after' => 'Solde après', - 'name' => 'Nom', - 'role' => 'Rôle', - 'currentBalance' => 'Solde courant', - 'linked_to_rules' => 'Règles applicables', - 'active' => 'Actif ?', - 'lastActivity' => 'Activité récente', - 'balanceDiff' => 'Différence d\'équilibre', - 'matchesOn' => 'Correspond à', - 'account_type' => 'Type de compte', - 'created_at' => 'Créé le', - 'account' => 'Compte', - 'matchingAmount' => 'Montant', - 'split_number' => 'Ventilation n°', - 'destination' => 'Destination', - 'source' => 'Source', - 'next_expected_match' => 'Prochaine association attendue', - 'automatch' => 'Correspondance automatique ?', - 'repeat_freq' => 'Répétitions', - 'description' => 'Description', - 'amount' => 'Montant', - 'internal_reference' => 'Référence interne', - 'date' => 'Date', - 'interest_date' => 'Date des intérêts', - 'book_date' => 'Date de réservation', - 'process_date' => 'Date de traitement', - 'due_date' => 'Échéance', - 'payment_date' => 'Date de paiement', - 'invoice_date' => 'Date de facturation', - 'interal_reference' => 'Référence interne', - 'notes' => 'Notes', - 'from' => 'Depuis', - 'piggy_bank' => 'Tirelire', - 'to' => 'À', - 'budget' => 'Budget', - 'category' => 'Catégorie', - 'bill' => 'Facture', - 'withdrawal' => 'Retrait', - 'deposit' => 'Dépôt', - 'transfer' => 'Transfert', - 'type' => 'Type', - 'completed' => 'Terminé', - 'iban' => 'Numéro IBAN', - 'paid_current_period' => 'Payé cette période', - 'email' => 'E-mail', - 'registered_at' => 'Enregistré le', - 'is_blocked' => 'Est bloqué', - 'is_admin' => 'Est admin', - 'has_two_factor' => 'A 2FA', - 'blocked_code' => 'Code de blocage', - 'source_account' => 'Compte d\'origine', + 'buttons' => 'Boutons', + 'icon' => 'Icône', + 'id' => 'Identifiant', + 'create_date' => 'Créé le', + 'update_date' => 'Mis à jour le', + 'updated_at' => 'Mis à jour le', + 'balance_before' => 'Solde avant', + 'balance_after' => 'Solde après', + 'name' => 'Nom', + 'role' => 'Rôle', + 'currentBalance' => 'Solde courant', + 'linked_to_rules' => 'Règles applicables', + 'active' => 'Actif ?', + 'transaction_type' => 'Type', + 'lastActivity' => 'Activité récente', + 'balanceDiff' => 'Différence d\'équilibre', + 'matchesOn' => 'Correspond à', + 'account_type' => 'Type de compte', + 'created_at' => 'Créé le', + 'account' => 'Compte', + 'matchingAmount' => 'Montant', + 'split_number' => 'Ventilation n°', + 'destination' => 'Destination', + 'source' => 'Source', + 'next_expected_match' => 'Prochaine association attendue', + 'automatch' => 'Correspondance automatique ?', + 'repeat_freq' => 'Répétitions', + 'description' => 'Description', + 'amount' => 'Montant', + 'internal_reference' => 'Référence interne', + 'date' => 'Date', + 'interest_date' => 'Date des intérêts', + 'book_date' => 'Date de réservation', + 'process_date' => 'Date de traitement', + 'due_date' => 'Échéance', + 'payment_date' => 'Date de paiement', + 'invoice_date' => 'Date de facturation', + 'interal_reference' => 'Référence interne', + 'notes' => 'Notes', + 'from' => 'Depuis', + 'piggy_bank' => 'Tirelire', + 'to' => 'À', + 'budget' => 'Budget', + 'category' => 'Catégorie', + 'bill' => 'Facture', + 'withdrawal' => 'Retrait', + 'deposit' => 'Dépôt', + 'transfer' => 'Transfert', + 'type' => 'Type', + 'completed' => 'Terminé', + 'iban' => 'Numéro IBAN', + 'paid_current_period' => 'Payé cette période', + 'email' => 'E-mail', + 'registered_at' => 'Enregistré le', + 'is_blocked' => 'Est bloqué', + 'is_admin' => 'Est admin', + 'has_two_factor' => 'A 2FA', + 'blocked_code' => 'Code de blocage', + 'source_account' => 'Compte d\'origine', 'destination_account' => 'Compte destinataire', 'accounts_count' => 'Nombre de comptes', 'journals_count' => 'Nombre d\'opérations', 'attachments_count' => 'Nombre de pièces jointes', 'bills_count' => 'Nombre de factures', 'categories_count' => 'Nombre de catégories', - 'export_jobs_count' => 'Nombre de travaux exportés', 'import_jobs_count' => 'Nombre de travaux importés', 'budget_count' => 'Nombre de budgets', 'rule_and_groups_count' => 'Nombre de règles et de groupes de règles', @@ -106,14 +106,14 @@ return [ 'account_on_spectre' => 'Compte (Spectre)', 'account_on_ynab' => 'Compte (YNAB)', 'do_import' => 'Importer depuis ce compte', - 'sepa-ct-id' => 'Identificateur de bout en bout SEPA', - 'sepa-ct-op' => 'Identifiant de compte SEPA opposable', - 'sepa-db' => 'Identifiant de mandat SEPA', - 'sepa-country' => 'Pays SEPA', - 'sepa-cc' => 'Code de compensation SEPA', - 'sepa-ep' => 'Objectif externe SEPA', - 'sepa-ci' => 'Identifiant Créancier SEPA', - 'sepa-batch-id' => 'ID de lot SEPA', + 'sepa_ct_id' => 'Identificateur de bout en bout SEPA', + 'sepa_ct_op' => 'Identifiant de compte SEPA opposable', + 'sepa_db' => 'Identifiant de mandat SEPA', + 'sepa_country' => 'Pays SEPA', + 'sepa_cc' => 'Code de rapprochement SEPA', + 'sepa_ep' => 'Usage externe SEPA', + 'sepa_ci' => 'Identifiant Créancier SEPA', + 'sepa_batch_id' => 'ID de lot SEPA', 'external_id' => 'ID externe', 'account_at_bunq' => 'Compte avec bunq', 'file_name' => 'Nom du fichier', diff --git a/resources/lang/fr_FR/validation.php b/resources/lang/fr_FR/validation.php index a553d3fddf..335ab504b9 100644 --- a/resources/lang/fr_FR/validation.php +++ b/resources/lang/fr_FR/validation.php @@ -33,15 +33,19 @@ return [ 'rule_trigger_value' => 'Cette valeur n’est pas valide pour le déclencheur sélectionné.', 'rule_action_value' => 'Cette valeur n’est pas valide pour l’action sélectionnée.', 'file_already_attached' => 'Le fichier téléchargé ":name" est déjà attaché à cet objet.', - 'file_attached' => 'Envoi du fichier ":name" avec succès.', + 'file_attached' => 'Fichier ":name" téléchargé avec succès.', 'must_exist' => 'L\'ID dans le champ :attribute n\'existe pas dans la base de données.', 'all_accounts_equal' => 'Tous les comptes dans ce champ doivent être égaux.', + 'group_title_mandatory' => 'Un titre de groupe est obligatoire lorsqu\'il y a plus d\'une transaction.', + 'transaction_types_equal' => 'Toutes les ventilations doivent être de même type.', + 'invalid_transaction_type' => 'Type de transaction non valide.', 'invalid_selection' => 'Votre sélection est invalide.', 'belongs_user' => 'Cette valeur n\'est pas valide pour ce champ.', 'at_least_one_transaction' => 'Besoin d\'au moins une transaction.', 'at_least_one_repetition' => 'Besoin d\'au moins une répétition.', 'require_repeat_until' => 'Besoin d’un certain nombre de répétitions ou d\'une date de fin (repeat_until). Pas les deux.', 'require_currency_info' => 'Le contenu de ce champ n\'est pas valide sans informations sur la devise.', + 'require_currency_amount' => 'Le contenu de ce champ est invalide sans informations sur le montant étranger.', 'equal_description' => 'La description de la transaction ne doit pas être identique à la description globale.', 'file_invalid_mime' => 'Le fichier ":name" est du type ":mime" ce qui n\'est pas accepté pour un nouvel envoi.', 'file_too_large' => 'Le fichier ":name" est trop grand.', @@ -135,8 +139,8 @@ return [ 'name' => 'nom', 'piggy_bank_id' => 'ID de tirelire', 'targetamount' => 'montant cible', - 'openingBalanceDate' => 'date du solde initial', - 'openingBalance' => 'solde initial', + 'opening_balance_date' => 'date du solde initial', + 'opening_balance' => 'solde initial', 'match' => 'correspondance', 'amount_min' => 'montant minimum', 'amount_max' => 'montant maximum', @@ -164,4 +168,28 @@ return [ 'rule-trigger.4' => 'déclencheur de règle #4', 'rule-trigger.5' => 'déclencheur de règle #4', ], + + // validation of accounts: + 'withdrawal_source_need_data' => 'Vous devez obtenir un ID de compte source valide et/ou un nom de compte source valide pour continuer.', + 'withdrawal_source_bad_data' => 'Impossible de trouver un compte source valide lors de la recherche de l\'ID ":id" ou du nom ":name".', + 'withdrawal_dest_need_data' => 'Vous devez obtenir un ID de compte de destination valide et/ou un nom de compte de destination valide pour continuer.', + 'withdrawal_dest_bad_data' => 'Impossible de trouver un compte de destination valide lors de la recherche de l\'ID ":id" ou du nom ":name".', + + 'deposit_source_need_data' => 'Vous devez obtenir un ID de compte source valide et/ou un nom de compte source valide pour continuer.', + 'deposit_source_bad_data' => 'Impossible de trouver un compte source valide lors de la recherche de l\'ID ":id" ou du nom ":name".', + 'deposit_dest_need_data' => 'Vous devez obtenir un ID de compte de destination valide et/ou un nom de compte de destination valide pour continuer.', + 'deposit_dest_bad_data' => 'Impossible de trouver un compte de destination valide lors de la recherche de l\'ID ":id" ou du nom ":name".', + + 'transfer_source_need_data' => 'Vous devez obtenir un ID de compte source valide et/ou un nom de compte source valide pour continuer.', + 'transfer_source_bad_data' => 'Impossible de trouver un compte source valide lors de la recherche de l\'ID ":id" ou du nom ":name".', + 'transfer_dest_need_data' => 'Vous devez obtenir un ID de compte de destination valide et/ou un nom de compte de destination valide pour continuer.', + 'transfer_dest_bad_data' => 'Impossible de trouver un compte de destination valide lors de la recherche de l\'ID ":id" ou du nom ":name".', + 'need_id_in_edit' => 'Chaque ventilation doit avoir transaction_journal_id (ID valide ou 0).', + + 'ob_source_need_data' => 'Vous devez obtenir un ID de compte source valide et/ou un nom de compte source valide pour continuer.', + 'ob_dest_need_data' => 'Vous devez obtenir un ID de compte de destination valide et/ou un nom de compte de destination valide pour continuer.', + 'ob_dest_bad_data' => 'Impossible de trouver un compte de destination valide lors de la recherche de l\'ID ":id" ou du nom ":name".', + + 'generic_invalid_source' => 'You can\'t use this account as the source account.', + 'generic_invalid_destination' => 'You can\'t use this account as the destination account.', ]; diff --git a/resources/lang/hu_HU/auth.php b/resources/lang/hu_HU/auth.php new file mode 100644 index 0000000000..50ea643a08 --- /dev/null +++ b/resources/lang/hu_HU/auth.php @@ -0,0 +1,28 @@ +. + */ + +declare(strict_types=1); + +return [ + 'failed' => 'Ezek a hitelesítő adatok nem egyeznek meg a tárolt adatokkal.', + 'throttle' => 'Túl sok belépési próbálkozás. :seconds másodperc múlva újra meg lehet próbálni.', +]; diff --git a/resources/lang/hu_HU/bank.php b/resources/lang/hu_HU/bank.php new file mode 100644 index 0000000000..5d00b1e685 --- /dev/null +++ b/resources/lang/hu_HU/bank.php @@ -0,0 +1,26 @@ +. + */ + +declare(strict_types=1); + +return [ +]; diff --git a/resources/lang/hu_HU/breadcrumbs.php b/resources/lang/hu_HU/breadcrumbs.php new file mode 100644 index 0000000000..277c57af84 --- /dev/null +++ b/resources/lang/hu_HU/breadcrumbs.php @@ -0,0 +1,59 @@ +. + */ + +declare(strict_types=1); + +return [ + 'home' => 'Főoldal', + 'edit_currency' => '":name" pénznem szerkesztése', + 'delete_currency' => '":name" pénznem törlése', + 'newPiggyBank' => 'Új malacpersely létrehozása', + 'edit_piggyBank' => '":name" malacpersely szerkesztése', + 'preferences' => 'Beállítások', + 'profile' => 'Profil', + 'changePassword' => 'Jelszó módosítása', + 'change_email' => 'Email cím módosítása', + 'bills' => 'Számlák', + 'newBill' => 'Új számla', + 'edit_bill' => '":name" számla szerkesztése', + 'delete_bill' => '":name" számla törlése', + 'reports' => 'Jelentések', + 'search_result' => '":query" keresési eredményei', + 'withdrawal_list' => 'Kiadások', + 'Withdrawal_list' => 'Expenses', + 'deposit_list' => 'Bevételek, jövedelem és betétek', + 'transfer_list' => 'Átvezetések', + 'transfers_list' => 'Átvezetések', + 'reconciliation_list' => 'Egyeztetések', + 'create_withdrawal' => 'Új költség létrehozása', + 'create_deposit' => 'Új bevétel létrehozása', + 'create_transfer' => 'Új átvezetés létrehozása', + 'create_new_transaction' => 'Új tranzakció létrehozása', + 'edit_journal' => '":description" tranzakció szerkesztése', + 'edit_reconciliation' => '":description" szerkesztése', + 'delete_journal' => '":description" tranzakció törlése', + 'tags' => 'Cimkék', + 'createTag' => 'Új címke létrehozása', + 'edit_tag' => '":tag" címke szerkesztése', + 'delete_tag' => '":tag" címke törlése', + 'delete_journal_link' => 'Tranzakciók közötti kapcsolat törlése', +]; diff --git a/resources/lang/hu_HU/components.php b/resources/lang/hu_HU/components.php new file mode 100644 index 0000000000..ca1efb1763 --- /dev/null +++ b/resources/lang/hu_HU/components.php @@ -0,0 +1,32 @@ +. + */ + +declare(strict_types=1); + +return [ + // profile + 'personal_access_tokens' => 'Személyes hozzáférési token', + + // bills: + 'not_expected_period' => 'Nem várható, ebben az időszakban', + 'not_or_not_yet' => 'Nincs (még)', +]; diff --git a/resources/lang/hu_HU/config.php b/resources/lang/hu_HU/config.php new file mode 100644 index 0000000000..790aafa0bc --- /dev/null +++ b/resources/lang/hu_HU/config.php @@ -0,0 +1,51 @@ +. + */ + +declare(strict_types=1); + +return [ + 'html_language' => 'hu', + 'locale' => 'hu, Hungarian, hu_HU.utf8, hu_HU.UTF-8', + 'month' => '%B %Y', + 'month_and_day' => '%B %e, %Y', + 'month_and_date_day' => '%Y %e %B, %A', + 'month_and_day_no_year' => '%e %B', + 'date_time' => '%Y %B %e @ %T', + 'specific_day' => '%Y %e %B', + 'week_in_year' => 'Hét %W, %Y', + 'year' => '%Y', + 'half_year' => '%Y %B', + 'month_js' => 'YYYY MMMM', + 'month_and_day_js' => 'YYYY MMMM DD', + 'date_time_js' => 'YYYY MMMM DD @ HH:mm:ss', + 'specific_day_js' => 'YYYY MMMM DD', + 'week_in_year_js' => 'YYYY, w [Week]', + 'year_js' => 'YYYY', + 'half_year_js' => 'YYYY Q', + 'dow_1' => 'Hétfő', + 'dow_2' => 'Kedd', + 'dow_3' => 'Szerda', + 'dow_4' => 'Csütörtök', + 'dow_5' => 'Péntek', + 'dow_6' => 'Szombat', + 'dow_7' => 'Vasárnap', +]; diff --git a/resources/lang/hu_HU/csv.php b/resources/lang/hu_HU/csv.php new file mode 100644 index 0000000000..aae109a40a --- /dev/null +++ b/resources/lang/hu_HU/csv.php @@ -0,0 +1,26 @@ +. + */ + +declare(strict_types=1); + +return [ +]; diff --git a/resources/lang/hu_HU/demo.php b/resources/lang/hu_HU/demo.php new file mode 100644 index 0000000000..43920dfc9c --- /dev/null +++ b/resources/lang/hu_HU/demo.php @@ -0,0 +1,38 @@ +. + */ + +declare(strict_types=1); + +return [ + 'no_demo_text' => 'Sajnos ehhez az oldalhoz nincs külön bemutató magyarázó szöveg.', + 'see_help_icon' => 'Azonban a jobb felső sarokban található ikon több információval szolgálhat.', + 'index' => 'Üdvözöli Önt a Firefly III! Ezen az oldalon gyors áttekintést nyerhet a pénzügyeiről. További információért nézze meg a → Fiókok és természetesen a Költségvetések valamint a Kimutatások oldalakat. Vagy egyszerűen csak nézzen körül, és majd eldől hová is lyukad ki.', + 'accounts-index' => 'Az eszközszámlák az Ön személyes bankszámlái. A költségszámlák azok a számlák amikre a kiadások kerülnek, ilyen például boltban való vásárlás. A jövedelemszámlák olyan számlák amikre a bevételek érkeznek a munkahelyről, a kormányzati szervektől vagy más bevételi forrásokból. Ezeket a számlákat ezen az oldalon lehet szerkeszteni vagy eltávolítani.', + 'budgets-index' => 'Ez az oldal a költségkeretekről nyújt áttekintést. A felső sáv mutatja azt az összeget ami a költségkeretben rendelkezésre áll. Ezt a jobb oldalon az összegre kattintva bármilyen időszakra testre lehet szabni. Az aktuálisan elköltött összeg a lenti sávban látható. Ez alatt a költségkeretenkénti költségek és az előirányzott összegek láthatóak.', + 'reports-index-start' => 'A Firefly III számos jelentéstípust támogat. Ezekről további információt a jobb felső sarokban található ikonra kattintva lehet szerezni.', + 'reports-index-examples' => 'Javasolt megtekinteni ezeket a példákat: havi pénzügyi áttekintés, éves pénzügyi áttekintés és költségkeret áttekintés.', + 'currencies-index' => 'A Firefly III több pénznemet támogat. Bár alapértelmezett az euró, az amerikai dollárra és számos más valutára is átállítható. Amint látható, néhány pénznem már szerepel a rendszerben, de hozzáadható bármilyen saját pénznem is. Az alapértelmezett pénznem módosítása nem változtatja meg a meglévő tranzakciók pénznemét: a Firefly III egyidejűleg több pénznem használatát is támogatja.', + 'transactions-index' => 'Ezek a kiadások, bevételek és átvezetések nem különösebben érdekesek. Automatikusan keletkeztek.', + 'piggy-banks-index' => 'Mint látható, három malacpersely van. A plusz és a mínusz jelekkel lehet a pénzmennyiséget állítani a malacperselyekben. A malacperselyek beállításait a nevükre kattintva lehet elvégezni.', + 'import-index' => 'A Firefly III-ba bármilyen CSV fájl importálható. Támogatja az adatok importálását bunq és Spectre rendszerekből. A más bankok és pénzügyi szolgáltatóktól való importálás a jövőben elérhető lesz. Bemutató felhasználóként azonban csak a "hamis"-szolgáltatókat láthatja működés közben. Ez létrehoz néhány véletlenszerű ügyletek, hogy szemléltesse, hogy hogyan működik a folyamat.', + 'profile-index' => 'Ne feledje, hogy a bemutató oldal adatai négyóránként automatikusan visszaállnak. A hozzáférése bármikor visszavonható. Ez automatikusan történik, és nem egy hiba.', +]; diff --git a/resources/lang/hu_HU/firefly.php b/resources/lang/hu_HU/firefly.php new file mode 100644 index 0000000000..5a73853d56 --- /dev/null +++ b/resources/lang/hu_HU/firefly.php @@ -0,0 +1,1415 @@ +. + */ + +declare(strict_types=1); + +return [ + // general stuff: + 'close' => 'Bezárás', + 'actions' => 'Műveletek', + 'edit' => 'Szerkesztés', + 'delete' => 'Törlés', + 'split' => 'Felosztás', + 'clone' => 'Másolás', + 'last_seven_days' => 'Utolsó hét nap', + 'last_thirty_days' => 'Elmúlt harminc nap', + 'welcomeBack' => 'Mi a helyzet?', + 'welcome_back' => 'Mi a helyzet?', + 'everything' => 'Minden', + 'today' => 'ma', + 'customRange' => 'Egyéni tartomány', + 'apply' => 'Alkalmaz', + 'select_date' => 'Dátum kiválasztása..', + 'cancel' => 'Mégse', + 'from' => 'Honnan', + 'to' => 'Hova', + 'structure' => 'Struktúra', + 'help_translating' => 'Ez a súgószöveg még nem elérhető az oldal nyelvén. Itt lehet segíteni a fordításban.', + 'showEverything' => 'Összes megjelenítése', + 'never' => 'Soha', + 'no_results_for_empty_search' => 'A keresés üres volt, így nincs találat.', + 'removed_amount' => ':amount eltávolítva', + 'added_amount' => ':amount hozzáadva', + 'asset_account_role_help' => 'A választásoktól függő további lehetőségeket később lehet beállítani.', + 'Opening balance' => 'Nyitó egyenleg', + 'create_new_stuff' => 'Új dolog létrehozása', + 'new_withdrawal' => 'Új költség', + 'create_new_transaction' => 'Új tranzakció létrehozása', + 'new_transaction' => 'Új tranzakció', + 'go_to_asset_accounts' => 'Eszközszámlák megtekintése', + 'go_to_budgets' => 'Ugrás a költségkeretekhez', + 'go_to_categories' => 'Ugrás a kategóriákhoz', + 'go_to_bills' => 'Ugrás a számlákhoz', + 'go_to_expense_accounts' => 'Költségszámlák megtekintése', + 'go_to_revenue_accounts' => 'Jövedelemszámlák megtekintése', + 'go_to_piggies' => 'Ugrás a malacperselyekhez', + 'new_deposit' => 'Új bevétel', + 'new_transfer' => 'Új átvezetés', + 'new_transfers' => 'Új átvezetés', + 'new_asset_account' => 'Új eszközszámla', + 'new_expense_account' => 'Új költségszámla', + 'new_revenue_account' => 'Új jövedelemszámla', + 'new_liabilities_account' => 'Új kötelezettség', + 'new_budget' => 'Új költségkeret', + 'new_bill' => 'Új számla', + 'block_account_logout' => 'Ön kijelentkezett. A blokkolt fiókok nem használhatják ezt a webhelyet. Érvényes e-mail címmel regisztrált?', + 'flash_success' => 'Siker!', + 'flash_info' => 'Üzenet', + 'flash_warning' => 'Figyelmeztetés!', + 'flash_error' => 'Hiba!', + 'flash_info_multiple' => 'Egy üzenet érkezett | :count üzenet érkezett', + 'flash_error_multiple' => 'Egy hiba történt|:count hiba történt', + 'net_worth' => 'Nettó érték', + 'route_has_no_help' => 'Ehhez az útvonalhoz nincs súgó.', + 'help_for_this_page' => 'Az oldal súgója', + 'no_help_could_be_found' => 'Nem található súgószöveg.', + 'no_help_title' => 'Elnézést, hiba történt.', + 'two_factor_welcome' => 'Hello!', + 'two_factor_enter_code' => 'A folytatáshoz meg kell adni a kétlépcsős azonosítás kódját. Az alkalmazás létre tudja hozni ezt a kódot.', + 'two_factor_code_here' => 'Kód beírása', + 'two_factor_title' => 'Kétlépcsős hitelesítés', + 'authenticate' => 'Hitelesítés', + 'two_factor_forgot_title' => 'Elvesztett kétlépcsős hitelesítés', + 'two_factor_forgot' => 'Elfelejtett kétlépcsős azonosítás.', + 'two_factor_lost_header' => 'Elvesztett kétlépcsős hitelesítés?', + 'two_factor_lost_intro' => 'If you lost your backup codes as well, you have bad luck. This is not something you can fix from the web interface. You have two choices.', + 'two_factor_lost_fix_self' => 'If you run your own instance of Firefly III, check the logs in storage/logs for instructions, or run docker logs <container_id> to see the instructions (refresh this page).', + 'two_factor_lost_fix_owner' => 'Ellenkező esetben emailt kell küldeni a webhely tulajdonosának a :site_owner címre, és meg kell kérni, hogy állítsa vissza a kétfaktoros hitelesítést.', + 'mfa_backup_code' => 'You have used a backup code to login to Firefly III. It can\'t be used again, so cross it from your list.', + 'pref_two_factor_new_backup_codes' => 'Get new backup codes', + 'pref_two_factor_backup_code_count' => 'You have :count valid backup code(s).', + '2fa_i_have_them' => 'I stored them!', + 'warning_much_data' => ':days napi adat betöltése eltarthat egy ideig.', + 'registered' => 'A regisztráció sikeres!', + 'Default asset account' => 'Alapértelmezett eszközszámla', + 'no_budget_pointer' => 'Úgy tűnik, még nincsenek költségkeretek. Költségkereteket a költségkeretek oldalon lehet létrehozni. A költségkeretek segítenek nyomon követni a költségeket.', + 'Savings account' => 'Megtakarítási számla', + 'Credit card' => 'Hitelkártya', + 'source_accounts' => 'Forrás számlák', + 'destination_accounts' => 'Cél számlák', + 'user_id_is' => 'A felhasználói azonosító :user', + 'field_supports_markdown' => 'Ez a mező támogatja a Markdown használatát.', + 'need_more_help' => 'A Firefly III használatához további segítség kérhető a a Githubon egy hibajegy nyitásával.', + 'reenable_intro_text' => 'A bevezető útmutatót újra lehet engedélyezni.', + 'intro_boxes_after_refresh' => 'A bevezető dobozok újra megjelennek az oldal frissítésekor.', + 'show_all_no_filter' => 'Az összes tranzakció megjelenítése dátum szerinti csoportosítás nélkül.', + 'expenses_by_category' => 'Költségek kategóriák szerint', + 'expenses_by_budget' => 'Költségek költségkeretek szerint', + 'income_by_category' => 'Jövedelem kategória szerint', + 'expenses_by_asset_account' => 'Költségek eszközszámla szerint', + 'expenses_by_expense_account' => 'Költségek költségszámla szerint', + 'cannot_redirect_to_account' => 'A Firefly III nem tud átirányítani a megfelelő oldalra. Elnézést.', + 'sum_of_expenses' => 'A költségek összege', + 'sum_of_income' => 'Jövedelem összege', + 'liabilities' => 'Kötelezettségek', + 'spent_in_specific_budget' => 'Költés ":budget" költségkeretben', + 'sum_of_expenses_in_budget' => 'Összes költés ":budget" költségkeretben', + 'left_in_budget_limit' => 'A költségvetés szerinti elkölthető összeg', + 'current_period' => 'Jelenlegi időszak', + 'show_the_current_period_and_overview' => 'Az aktuális időszak és az áttekintés megjelenítése', + 'pref_languages_locale' => 'Ahhoz, hogy egy angoltól eltérő nyelv megfelelően működjön, az operációs rendszernek a helyes helyspecifikus információkkal kell rendelkeznie. Ha ezek nincsenek jelen, akkor a pénznem adat, a dátumok és az összegek formázása hibás lehet.', + 'budget_in_period' => '":name" költségkeret összes tranzakciója :start és :end között', + 'chart_budget_in_period' => 'Diagram ":name" költségkeret minden tranzakciójáról :start és :end között', + 'chart_account_in_period' => 'Diagram ":name" bankszámla minden tranzakciójáról :start és :end között', + 'chart_category_in_period' => 'Diagram ":name" kategória minden tranzakciójáról :start és :end között', + 'chart_category_all' => 'Diagram ":name" kategória minden tranzakciójáról', + 'clone_withdrawal' => 'Költség klónozása', + 'clone_deposit' => 'Bevétel klónozása', + 'clone_transfer' => 'Átvezetés klónozása', + 'multi_select_no_selection' => 'Egy sincs kiválasztva', + 'multi_select_select_all' => 'Összes kijelölése', + 'multi_select_n_selected' => 'kijelölt', + 'multi_select_all_selected' => 'Minden kiválasztott', + 'multi_select_filter_placeholder' => 'Keresés..', + 'intro_next_label' => 'Következő', + 'intro_prev_label' => 'Előző', + 'intro_skip_label' => 'Kihagyás', + 'intro_done_label' => 'Kész', + 'between_dates_breadcrumb' => ':start és :end között', + 'all_journals_without_budget' => 'Minden költségkeret nélküli tranzakció', + 'journals_without_budget' => 'Költségkeret nélküli tranzakciók', + 'all_journals_without_category' => 'Minden kategória nélküli tranzakció', + 'journals_without_category' => 'Kategória nélküli tranzakciók', + 'all_journals_for_account' => ':name bankszámla összes tranzakciója', + 'chart_all_journals_for_account' => 'Diagram :name bankszámla minden tranzakciójáról', + 'journals_in_period_for_account' => '":name" bankszámla összes tranzakciója :start és :end között', + 'transferred' => 'Átvezetett', + 'all_withdrawal' => 'Minden költség', + 'all_transactions' => 'Minden tranzakció', + 'title_withdrawal_between' => 'Minden költség :start és :end között', + 'all_deposit' => 'Összes jövedelem', + 'title_deposit_between' => 'Összes jövedelem :start és :end között', + 'all_transfers' => 'Minden átvezetés', + 'title_transfers_between' => 'Minden átvezetés :start és :end között', + 'all_transfer' => 'Minden átvezetés', + 'all_journals_for_tag' => '":tag" címke összes tranzakciója', + 'title_transfer_between' => 'Minden átvezetés :start és :end között', + 'all_journals_for_category' => ':name kategória összes tranzakciója', + 'all_journals_for_budget' => ':name költségkeret összes tranzakciója', + 'chart_all_journals_for_budget' => 'Diagram :name költségkeret minden tranzakciójáról', + 'journals_in_period_for_category' => ':name kategória összes tranzakciója :start és :end között', + 'journals_in_period_for_tag' => ':tag címke összes tranzakciója :start és :end között', + 'not_available_demo_user' => 'A használni kívánt szolgáltatás nem érhető el a bemutató felhasználók számára.', + 'exchange_rate_instructions' => 'A "@name" vagyonszámla csak a @native_currency tranzakciókat fogadja el. Ha inkább @foreign_currency pénznemet szeretne használni, győződjön meg róla, hogy a @native_currency összege is ismert:', + 'transfer_exchange_rate_instructions' => 'A "@source_name" forrás vagyonszámla csak a @source_currency tranzakciókat fogadja el. A "@dest_name" cél vagyonszámla csak a @dest_currency tranzakciókat fogadja el. Mindkét pénznemben helyesen kell megadnia az átutalt összeget.', + 'transaction_data' => 'Tranzakciós adatok', + 'invalid_server_configuration' => 'Érvénytelen kiszolgálóbeállítás', + 'invalid_locale_settings' => 'A Firefly III nem tudja megfelelően formázva megjeleníteni a pénzösszegeket, mert a kiszolgálóról hiányoznak az ehhez szükséges csomagok. A következő linket követve találhatók instrukciók a megoldáshoz.', + 'quickswitch' => 'Gyorsváltó', + 'sign_in_to_start' => 'A munkamenet megkezdéséhez be kell jelentkezni', + 'sign_in' => 'Bejelentkezés', + 'register_new_account' => 'Új fiók regisztrációja', + 'forgot_my_password' => 'Elfelejtett jelszó', + 'problems_with_input' => 'Volt néhány probléma a bevitt adatokkal.', + 'reset_password' => 'Jelszó visszaállítása', + 'button_reset_password' => 'Jelszó visszaállítása', + 'reset_button' => 'Visszaállítás', + 'want_to_login' => 'Bejelentkezés', + 'login_page_title' => 'Bejelentkezés a Firefly III-ba', + 'register_page_title' => 'Regisztrálás a Firefly III-ba', + 'forgot_pw_page_title' => 'Elfelejtette a jelszavát a Firefly III-hoz', + 'reset_pw_page_title' => 'Firefly III jelszó visszaállítása', + 'cannot_reset_demo_user' => 'A bemutató felhasználónak nem nem lehet visszaállítani a jelszavát.', + 'button_register' => 'Regisztráció', + 'authorization' => 'Hitelesítés', + 'active_bills_only' => 'csak az aktív számlák', + 'average_per_bill' => 'számlánkénti átlag', + 'expected_total' => 'várható teljes összeg', + // API access + 'authorization_request' => 'Firefly III v:version engedély kérelem', + 'authorization_request_intro' => ':client hozzáférést kért az Ön pénzügyi adminisztrációjához. Szeretne hozzáférést ezekhez adatokhoz :client részére?', + 'scopes_will_be_able' => 'Ez az alkalmazás képes lesz arra, hogy:', + 'button_authorize' => 'Engedélyezés', + 'none_in_select_list' => '(nincs)', + 'name_in_currency' => ':name - :currency', + 'paid_in_currency' => 'Fizetve :currency -ban', + 'unpaid_in_currency' => 'Be nem fizetett :currency -ban', + + // check for updates: + 'update_check_title' => 'Frissítések ellenőrzése', + 'admin_update_check_title' => 'Frissítés automatikus ellenőrzése', + 'admin_update_check_explain' => 'A Firefly III képes automatikusan ellenőrizni a frissítéseket. Ha engedélyezi ezt a beállítást, akkor a Github-bal kommunikálva ellenőrzésre kerül, hogy rendelkezésre áll-e a Firefly III új verziója. Ha igen, értesítést kap. Ezt az értesítést a jobb oldalon található gombra kattintva tesztelheti. Kérjük jelezze alább, ha szeretné, hogy a Firefly III ellenőrizze a frissítéseket.', + 'check_for_updates_permission' => 'A Firefly III automatikusan ellenőrizheti a frissítéseket, ha Ön ezt engedélyezi. Kérjük, keresse meg a adminisztráció menüpontot, ha engedélyezni szeretné ezt a funkciót.', + 'updates_ask_me_later' => 'Kérdezze később', + 'updates_do_not_check' => 'Ne ellenőrizze a frissítéseket', + 'updates_enable_check' => 'Frissítések ellenőrzésének engedélyezése', + 'admin_update_check_now_title' => 'Frissítések keresése most', + 'admin_update_check_now_explain' => 'Ha megnyomja a gombot, a Firefly III ellenőrinzi fogja, hogy a legfrissebb változatot használja -e.', + 'check_for_updates_button' => 'Ellenőrzés most!', + 'update_new_version_alert' => 'A Firefly III új verziója elérhető. A jelenleg használt verzió v:your_version, a legújabb verzió pedig v:new_version, amely megjelent: :date.', + 'update_current_version_alert' => 'A jelenlegi verzió v:version, ez egyben a legfrissebb elérhető verzió is.', + 'update_newer_version_alert' => 'A jelenlegi verzió v:your_version, amely újabb, mint a legfrissebb kiadott verzió, ami a v:new_version.', + 'update_check_error' => 'Hiba történt a frissítések ellenőrzése során. További információ a naplófájlokban található.', + + // search + 'search' => 'Keresés', + 'search_query' => 'Lekérdezés', + 'search_found_transactions' => 'A Firefly III :count tranzakciót talált :time másodperc alatt.', + 'search_for_query' => 'A Firefly III tranzakciókat keres amelyben ez a kifejezés található: :query', + 'search_modifier_amount_is' => 'Összeg pontosan :value', + 'search_modifier_amount' => 'Összeg pontosan :value', + 'search_modifier_amount_max' => 'Összeg legfeljebb :value', + 'search_modifier_amount_min' => 'Összeg legalább :value', + 'search_modifier_amount_less' => 'Összeg kevesebb mint: :value', + 'search_modifier_amount_more' => 'Összeg több, mint: :value', + 'search_modifier_source' => 'Forrásszámla: :value', + 'search_modifier_from' => 'Source account is :value', + 'search_modifier_destination' => 'Célszámla: :value', + 'search_modifier_to' => 'Destination account is :value', + 'search_modifier_category' => 'Kategória: :value', + 'search_modifier_budget' => 'Költségkeret: :value', + 'search_modifier_bill' => 'Számla: :value', + 'search_modifier_type' => 'Tranzakció típusa a :value', + 'search_modifier_date' => 'Tranzakció dátuma: :value', + 'search_modifier_date_before' => 'Tranzakció dátuma :value előtt van', + 'search_modifier_date_after' => 'Tranzakció dátuma :value után van', + 'search_modifier_on' => 'Tranzakció dátuma: :value', + 'search_modifier_before' => 'Tranzakció dátuma :value előtt van', + 'search_modifier_after' => 'Tranzakció dátuma :value után van', + 'modifiers_applies_are' => 'A következő módosítókat a keresésre is alkalmaztuk:', + 'general_search_error' => 'Hiba történt keresés közben. További információ a naplófájlokban található.', + 'search_box' => 'Keresés', + 'search_box_intro' => 'Üdvözöljük a Firefly III kereső funkciójában. Írja be a keresési kifejezését a mezőbe. Győződjön meg róla, hogy alaposan tanulmányozta a súgót, mert a keresés meglhetősen összetett funkció.', + 'search_error' => 'Hiba történt keresés közben', + 'search_searching' => 'Keresés ...', + 'search_results' => 'Keresési eredmények', + + // repeat frequencies: + 'repeat_freq_yearly' => 'éves', + 'repeat_freq_half-year' => 'félévente', + 'repeat_freq_quarterly' => 'negyedéves', + 'repeat_freq_monthly' => 'havi', + 'repeat_freq_weekly' => 'heti', + 'weekly' => 'heti', + 'quarterly' => 'negyedéves', + 'half-year' => 'félévente', + 'yearly' => 'éves', + + // rules + 'rules' => 'Szabályok', + 'rule_name' => 'Szabály neve', + 'rule_triggers' => 'A szabály életbe lép amikor', + 'rule_actions' => 'A szabály ez lesz', + 'new_rule' => 'Új szabály', + 'new_rule_group' => 'Új szabálycsoport', + 'rule_priority_up' => 'Nagyobb prioritás a szabálynak', + 'rule_priority_down' => 'Kissebb prioritás a szabálynak', + 'make_new_rule_group' => 'Új szabálycsoport létrehozása', + 'store_new_rule_group' => 'Új szabálycsoport tárolása', + 'created_new_rule_group' => '":title" szabálycsoport tárolva!', + 'updated_rule_group' => '":title" szabálycsoport sikeresen frissítve.', + 'edit_rule_group' => '":title" szabálycsoport szerkesztése', + 'delete_rule_group' => '":title" szabálycsoport törlése', + 'deleted_rule_group' => '":title" szabálycsoport törölve', + 'update_rule_group' => 'Szabálycsoport frissítése', + 'no_rules_in_group' => 'Ebben a csoportban nincsenek szabályok', + 'move_rule_group_up' => 'Szabálycsoport felfelé mozgatása', + 'move_rule_group_down' => 'Szabálycsoport lefelé mozgatása', + 'save_rules_by_moving' => 'Szabályok mentése más szabálycsoportba mozgatással:', + 'make_new_rule' => 'Új szabály létrehozása a ":title" szabálycsoportban', + 'make_new_rule_no_group' => 'Szabály létrehozás', + 'instructions_rule_from_bill' => 'Annak érdekében, hogy a tranzakciók egyeztetve legyenek az új ":name" számlával, a Firefly III létrehoz egy szabályt, ami automatikusan ellenőrzi az összes letárolt tranzakciót. Ellenőrizd a lent található részleteket, és tárold el a szabályt, hogy a Firefly III automatikusan egyeztesse a tranzakciókat az új számlával.', + 'rule_is_strict' => 'szigorú szabály', + 'rule_is_not_strict' => 'nem szigorú szabály', + 'rule_help_stop_processing' => 'Ha bejelölt, a csoport későbbi szabályai nem lesznek végrehajtva.', + 'rule_help_strict' => 'A szigorú szabályokban a műveletek végrehajtásához MINDEN eseményindítónak el kell indulnia. A nem szigorú szabályokban BÁRMELY eseményindító elég a műveletek végrehajtásához.', + 'rule_help_active' => 'Az inaktív szabályok sose fognak életbe lépni.', + 'stored_new_rule' => 'Új szabály ":title" címmel eltárolva', + 'deleted_rule' => '":title" című szabály törölve', + 'store_new_rule' => 'Új szabály tárolása', + 'updated_rule' => '":title" című szabály frissítve', + 'default_rule_group_name' => 'Alapértelmezés szerinti szabályok', + 'default_rule_group_description' => 'Csoporthoz nem rendelt szabályok.', + 'default_rule_name' => 'Az első saját alapértelmezett szabály', + 'default_rule_description' => 'Ez a szabály egy példa. Nyugodtan le lehet törölni.', + 'default_rule_trigger_description' => 'The Man Who Sold the World', + 'default_rule_trigger_from_account' => 'David Bowie', + 'default_rule_action_prepend' => 'Bought the world from ', + 'default_rule_action_set_category' => 'Magas költségek', + 'trigger' => 'Eseményindító', + 'trigger_value' => 'Eseményindító értéke', + 'stop_processing_other_triggers' => 'Egyéb eseményindítók feldolgozásának leállítása', + 'add_rule_trigger' => 'Új eseményindító hozzáadása', + 'action' => 'Művelet', + 'action_value' => 'Művelet érték', + 'stop_executing_other_actions' => 'Egyéb műveletek végrehajtásának leállítása', + 'add_rule_action' => 'Új művelet hozzáadása', + 'edit_rule' => '":title" szabály szerkesztése', + 'delete_rule' => '":title" szabály törlése', + 'update_rule' => 'Frissítési szabály', + 'test_rule_triggers' => 'Egyező tranzakciók mutatása', + 'warning_transaction_subset' => 'Teljesítmény javítási okokból ez a lista csak :max_num_transactions tranzakciót jelenít meg, ezért lehet, hogy az egyező tranzakcióknak csak egy része szerepel rajta', + 'warning_no_matching_transactions' => 'Nem találhatóak egyező tranzakciók. Teljesítmény javítási okokból csak az utolsó :num_transactions tranzakció lett ellenőrizve.', + 'warning_no_valid_triggers' => 'Nincs megadva érvényes eseményindító.', + 'apply_rule_selection' => '":title" szabály alkalmazása a tranzakciók egy csoportján', + 'apply_rule_selection_intro' => 'Az olyan szabályok mint a ":title" normális esetben csak az új vagy a frissített tranzakciókon lesznek alkalmazva, de meg lehet mondani a Firefly III-nak, hogy futtassa le a már létező tranzakciókon. Ez hasznos lehet, ha egy szabály frissítve lett és a módosításokat az összes tranzakción alkalmazni kell.', + 'include_transactions_from_accounts' => 'Beleértve a tranzakciókat ezekből a számlákból', + 'applied_rule_selection' => '":title" szabály alkalmazva a kiválasztásra.', + 'execute' => 'Végrehajtás', + 'apply_rule_group_selection' => '":title" szabálycsoport alkalmazása a tranzakciók egy csoportján', + 'apply_rule_group_selection_intro' => 'Az olyan szabálycsoportok mint a ":title" normális esetben csak az új vagy a frissített tranzakciókon lesznek alkalmazva, de meg lehet mondani a Firefly III-nak, hogy futtassa le a csoportban lévő összes szabályt a már létező tranzakciókon. Ez hasznos lehet, ha egy szabálycsoport frissítve lett és a módosításokat az összes tranzakción alkalmazni kell.', + 'applied_rule_group_selection' => '":title" szabálycsoport alkalmazva a kiválasztásra.', + + // actions and triggers + 'rule_trigger_user_action' => 'A felhasználói művelet ":trigger_value"', + 'rule_trigger_from_account_starts_choice' => 'Forrásszámla eleje..', + 'rule_trigger_from_account_starts' => 'Forrásszámla eleje: ":trigger_value"', + 'rule_trigger_from_account_ends_choice' => 'Forrásszámla vége..', + 'rule_trigger_from_account_ends' => 'Forrásszámla vége: ":trigger_value"', + 'rule_trigger_from_account_is_choice' => 'A forrásszámla..', + 'rule_trigger_from_account_is' => 'Forrásszámla: ":trigger_value"', + 'rule_trigger_from_account_contains_choice' => 'A forrásszámla tartalmazza..', + 'rule_trigger_from_account_contains' => 'Forrásszámla tartalmazza: ":trigger_value"', + 'rule_trigger_to_account_starts_choice' => 'Célszámla eleje..', + 'rule_trigger_to_account_starts' => 'Célszámla eleje: ":trigger_value"', + 'rule_trigger_to_account_ends_choice' => 'Célszámla vége..', + 'rule_trigger_to_account_ends' => 'Célszámla vége: ":trigger_value"', + 'rule_trigger_to_account_is_choice' => 'A célszámla..', + 'rule_trigger_to_account_is' => 'Célszámla: ":trigger_value"', + 'rule_trigger_to_account_contains_choice' => 'A célszámla tartalmazza..', + 'rule_trigger_to_account_contains' => 'Célszámla tartalmazza: ":trigger_value"', + 'rule_trigger_transaction_type_choice' => 'A tranzakció típusa..', + 'rule_trigger_transaction_type' => 'Tranzakció típusa ":trigger_value"', + 'rule_trigger_category_is_choice' => 'A kategória..', + 'rule_trigger_category_is' => 'A kategória ":trigger_value"', + 'rule_trigger_amount_less_choice' => 'Összeg kevesebb mint..', + 'rule_trigger_amount_less' => 'Mennyiség kevesebb mint :trigger_value', + 'rule_trigger_amount_exactly_choice' => 'Az összeg pontosan..', + 'rule_trigger_amount_exactly' => 'Az öszeg :trigger_value', + 'rule_trigger_amount_more_choice' => 'Összeg több, mint..', + 'rule_trigger_amount_more' => 'Összeg több mint :trigger_value', + 'rule_trigger_description_starts_choice' => 'Leírás eleje..', + 'rule_trigger_description_starts' => 'Leírás eleje: ":trigger_value"', + 'rule_trigger_description_ends_choice' => 'Leírás vége..', + 'rule_trigger_description_ends' => 'Leírás vége: ":trigger_value"', + 'rule_trigger_description_contains_choice' => 'A leírás tartalmazza..', + 'rule_trigger_description_contains' => 'Leírás tartalmazza: ":trigger_value"', + 'rule_trigger_description_is_choice' => 'A leírás pontosan..', + 'rule_trigger_description_is' => 'Leírás: ":trigger_value"', + 'rule_trigger_budget_is_choice' => 'A költségkeret..', + 'rule_trigger_budget_is' => 'Költségkeret: ":trigger_value"', + 'rule_trigger_tag_is_choice' => 'A címke..', + 'rule_trigger_tag_is' => 'Címke: ":trigger_value"', + 'rule_trigger_currency_is_choice' => 'A tranzakció pénzneme..', + 'rule_trigger_currency_is' => 'A tranzakció pénzneme: ":trigger_value"', + 'rule_trigger_has_attachments_choice' => 'Legalább ennyi melléklete van', + 'rule_trigger_has_attachments' => 'Legalább :trigger_value melléklete van', + 'rule_trigger_store_journal' => 'Tranzakció létrehozásakor', + 'rule_trigger_update_journal' => 'Tranzakció frissítésekor', + 'rule_trigger_has_no_category_choice' => 'Nincs kategóriája', + 'rule_trigger_has_no_category' => 'A tranzakcióhoz nincs kategóriája', + 'rule_trigger_has_any_category_choice' => 'Van kategóriája', + 'rule_trigger_has_any_category' => 'A tranzakciónak van kategóriája', + 'rule_trigger_has_no_budget_choice' => 'Nincs költségkerete', + 'rule_trigger_has_no_budget' => 'Költségkeret nélküli tranzakciók', + 'rule_trigger_has_any_budget_choice' => 'Van költségkerete', + 'rule_trigger_has_any_budget' => 'Költségkerettel rendelkező tranzakció', + 'rule_trigger_has_no_tag_choice' => 'Nincsenek címkéi', + 'rule_trigger_has_no_tag' => 'Címke nélküli tranzakció', + 'rule_trigger_has_any_tag_choice' => 'Van legalább egy címkéje', + 'rule_trigger_has_any_tag' => 'A tranzakciónak van legalább egy címkéje', + 'rule_trigger_any_notes_choice' => 'Van megjegyzése', + 'rule_trigger_any_notes' => 'A tranzakciónak van megjegyzése', + 'rule_trigger_no_notes_choice' => 'Nincsenek megjegyzései', + 'rule_trigger_no_notes' => 'A tranzakciónak nincs megjegyzése', + 'rule_trigger_notes_are_choice' => 'A megjegyzések..', + 'rule_trigger_notes_are' => 'Megjegyzések: ":trigger_value"', + 'rule_trigger_notes_contain_choice' => 'Jegyzetek tartalmazzák..', + 'rule_trigger_notes_contain' => 'Jegyzetek tartalmazzák: ":trigger_value"', + 'rule_trigger_notes_start_choice' => 'Megjegyzések kezdetén..', + 'rule_trigger_notes_start' => 'Megjegyzések kezdetén ":trigger_value"', + 'rule_trigger_notes_end_choice' => 'Megjegyzések a végén..', + 'rule_trigger_notes_end' => 'Megjegyzések végén ":trigger_value"', + 'rule_action_set_category' => 'Kategória beállítása ":action_value"', + 'rule_action_clear_category' => 'Kategória törlése', + 'rule_action_set_budget' => 'Költségvetés beállítása: ":action_value"', + 'rule_action_clear_budget' => 'Költségkeret törlése', + 'rule_action_add_tag' => 'Címke hozzáadása ":action_value"', + 'rule_action_remove_tag' => 'Címke eltávolítása ":action_value"', + 'rule_action_remove_all_tags' => 'Minden címke eltávolítása', + 'rule_action_set_description' => 'Leírást megadása: ":action_value"', + 'rule_action_append_description' => 'Hozzáfűzés a leírás végéhez ":action_value"', + 'rule_action_prepend_description' => 'Hozzáfűzés a leírás elejéhez ":action_value"', + 'rule_action_set_category_choice' => 'Kategória beállítás:', + 'rule_action_clear_category_choice' => 'Minden kategória törlése', + 'rule_action_set_budget_choice' => 'Költségkeret beállítása erre..', + 'rule_action_clear_budget_choice' => 'Minden költségvetés törlése', + 'rule_action_add_tag_choice' => 'Címke hozzáadása..', + 'rule_action_remove_tag_choice' => 'Címke eltávolítása..', + 'rule_action_remove_all_tags_choice' => 'Minden címke eltávolítása', + 'rule_action_set_description_choice' => 'Leírás megadása..', + 'rule_action_append_description_choice' => 'Hozzáfűzés a leíráshoz..', + 'rule_action_prepend_description_choice' => 'Hozzáfűzés a leírás elejéhez..', + 'rule_action_set_source_account_choice' => 'Forrásszámla beállítása...', + 'rule_action_set_source_account' => 'Forrásfiók beállítása :action_value', + 'rule_action_set_destination_account_choice' => 'Célszámla beállítása...', + 'rule_action_set_destination_account' => 'Célfiók beállítása :action_value', + 'rule_action_append_notes_choice' => 'Hozzáfűzés a jegyzetekhez..', + 'rule_action_append_notes' => 'Hozzáfűzés a jegyzetekhez ":action_value"', + 'rule_action_prepend_notes_choice' => 'Hozzáfűzés a jegyzetek elejéhez..', + 'rule_action_prepend_notes' => 'Hozzáfűzés a jegyzetek elejéhez ":action_value"', + 'rule_action_clear_notes_choice' => 'Megjegyzések eltávolítása', + 'rule_action_clear_notes' => 'Megjegyzések eltávolítása', + 'rule_action_set_notes_choice' => 'Megjegyzések beállítása..', + 'rule_action_link_to_bill_choice' => 'Számlához csatolás..', + 'rule_action_link_to_bill' => 'Számlához csatolás: ":action_value"', + 'rule_action_set_notes' => 'Jegyzetek megadása: ":action_value"', + 'rule_action_convert_deposit_choice' => 'A tranzakció bevétellé konvertálása', + 'rule_action_convert_deposit' => 'Tranzakció bevétellé konvertálása innen: ":action_value"', + 'rule_action_convert_withdrawal_choice' => 'A tranzakció költséggé konvertálása', + 'rule_action_convert_withdrawal' => 'Tranzakció költséggé konvertálása ide: ":action_value"', + 'rule_action_convert_transfer_choice' => 'A tranzakció átvezetéssé konvertálása', + 'rule_action_convert_transfer' => 'Tranzakció átvezetéssé konvertálása ezzel: ":action_value"', + + 'rules_have_read_warning' => 'Elolvasta a figyelmeztetést?', + 'apply_rule_warning' => 'Figyelmeztetés: egy szabály vagy szabálycsoport futtatása nagy mennyiségű tranzakción időtúllépést okozhat. Ebben az esetben a szabály vagy szabálycsoport a tranzakcióknak csak egy egy ismeretlen részén lesz alkalmazva. Ez tönkreteheti a pénzügyi adminisztrációt. Óvatosan kell eljárni.', + 'rulegroup_for_bills_title' => 'Szabálycsoport a számlákhoz', + 'rulegroup_for_bills_description' => 'Egy speciális szabálycsoport minden olyan szabálynak amibe számlák tartoznak.', + 'rule_for_bill_title' => 'Automatikusan generált szabály a számlához: ":name"', + 'rule_for_bill_description' => 'Ez a szabály automatikusan jön létre, hogy megpróbáljon egyezést találni ":name" számlával.', + 'create_rule_for_bill' => 'Új szabály létrehozás a számlához ":name"', + 'create_rule_for_bill_txt' => 'Gratulálunk! Épp most lett létrehozva egy új számla ":name" névvel. A Firefly III automatikusan képes összeegyeztetni az új költségeket ezzel a számlával. Például bérleti díj fizetésekor a "bérleti díj" számla hozzá lesz kapcsolva a költséghez. Ily módon a Firefly III pontosan megmutatja, hogy melyik számla esedékes és melyik nem. Ennek érdekében egy új szabályt kell létrehozni. A Firefly III előre kitölt néhány érzékeny alapértelmezés szerinti adatot. Le kell ellenőrizni, hogy ezek helyesek-e. Ha az adatok helyesek, a Firefly III a megfelelő kiadást automatikusan hozzácsatolja a megfelelő számlához. Ellenőrizni kell az eseményindítók helyességét és újakat hozzáadni ha nem helyesek.', + 'new_rule_for_bill_title' => 'Szabály a számlához: ":name"', + 'new_rule_for_bill_description' => 'Ez a szabály megjelöli a tranzakciókat ":name" számla számára.', + + // tags + 'store_new_tag' => 'Új címke tárolása', + 'update_tag' => 'Címke frissítése', + 'no_location_set' => 'Nincs hely beállítása.', + 'meta_data' => 'Metaadat', + 'location' => 'Hely', + 'without_date' => 'Dátum nélkül', + 'result' => 'Eredmény', + 'sums_apply_to_range' => 'Minden összeg alkalmazása a kiválasztott tartományra', + 'mapbox_api_key' => 'A térkép használatához be kell szerezni egy API kulcsot a Mapbox oldalról. A kódot a .env fájlba, a MAPBOX_API_KEY = után kell beírni.', + 'press_tag_location' => 'Jobb kattintással vagy az egérgomb hosszan nyomva tartásával lehet beállítani a címke helyét.', + 'clear_location' => 'Hely törlése', + + // preferences + 'pref_home_screen_accounts' => 'Kezdőoldali számlák', + 'pref_home_screen_accounts_help' => 'Melyik számlák legyenek megjelenítve a kezdőoldalon?', + 'pref_view_range' => 'Tartomány mutatása', + 'pref_view_range_help' => 'Néhány diagram automatikusan időszakokba lesz csoportosítva. A költségkeretek szintén időszakokba lesznek csoportosítva. Milyen időszakot részesít előnyben?', + 'pref_1D' => 'Egy nap', + 'pref_1W' => 'Egy hét', + 'pref_1M' => 'Egy hónap', + 'pref_3M' => 'Három hónap (negyedév)', + 'pref_6M' => 'Hat hónap', + 'pref_1Y' => 'Egy év', + 'pref_languages' => 'Nyelvek', + 'pref_languages_help' => 'A Firefly III több nyelven is elérhető. Melyiket szeretné használni?', + 'pref_custom_fiscal_year' => 'Költségvetési év beállításai', + 'pref_custom_fiscal_year_label' => 'Engedélyezett', + 'pref_custom_fiscal_year_help' => 'Azokban az országokban ahol a pénzügyi év nem Január 1 és December 31 közé esik, be lehet ezt kapcsolni és meg lehet adni a pénzügyi év kezdő- és végdátumát', + 'pref_fiscal_year_start_label' => 'Üzleti év kezdődátuma', + 'pref_two_factor_auth' => 'Kétlépcsős azonosítás', + 'pref_two_factor_auth_help' => 'Ha a kétlépéses igazolás (más néven kétfaktoros hitelesítés) engedélyezett, egy extra réteg lesz hozzáadva a fiók biztonságához. A bejelentkezéshez kell valami ismert (a jelszó) és valami meglévő (egy ellenőrző kód). Az ellenőrző kódokat olyan telefonos alkalmazások generálják, mint az Authy vagy a Google Authenticator.', + 'pref_enable_two_factor_auth' => 'Kétlépcsős azonosítás engedélyezése', + 'pref_two_factor_auth_disabled' => '2 lépcsős azonosítási kód eltávolítva és letilva', + 'pref_two_factor_auth_remove_it' => 'Ne felejtse el törölni a fiókot a hitelesítő alkalmazásából!', + 'pref_two_factor_auth_code' => 'Kód megerősítése', + 'pref_two_factor_auth_code_help' => 'Be kell olvasni a QR kódot a telefonon található Authy, Google Authenticator vagy egy ezekhez hasonló alkalmazással, majd be kell írni a létrehozott kódot.', + 'pref_two_factor_auth_reset_code' => 'Ellenőrző kód visszaállítása', + 'pref_two_factor_auth_disable_2fa' => '2FA kikapcsolása', + '2fa_use_secret_instead' => 'If you cannot scan the QR code, feel free to use the secret instead: :secret.', + '2fa_backup_codes' => 'Store these backup codes for access in case you lose your device.', + '2fa_already_enabled' => '2-step verification is already enabled.', + 'wrong_mfa_code' => 'This MFA code is not valid.', + 'pref_save_settings' => 'Beállítások mentése', + 'saved_preferences' => 'Beállítások elmentve!', + 'preferences_general' => 'Általános', + 'preferences_frontpage' => 'Kezdőoldal', + 'preferences_security' => 'Biztonság', + 'preferences_layout' => 'Elrendezés', + 'pref_home_show_deposits' => 'Bevételek mutatása a kezdőoldalon', + 'pref_home_show_deposits_info' => 'A kezdőoldalon már látszanak a költségszámlák. Szeretné a jövedelemszámlákat is megjeleníteni?', + 'pref_home_do_show_deposits' => 'Igen, mutasd', + 'successful_count' => 'ebből :count sikeres', + 'list_page_size_title' => 'Oldal mérete', + 'list_page_size_help' => 'Minden dologból (számlák, tranzakciók, stb) legfeljebb ennyi lesz megjelenítve oldalanként.', + 'list_page_size_label' => 'Oldal mérete', + 'between_dates' => '(:start és :end)', + 'pref_optional_fields_transaction' => 'Tranzakciók választható mezői', + 'pref_optional_fields_transaction_help' => 'Alapértelmezés szerint új tranzakció létrehozásakor nem minden mező engedélyezett (hogy elkerüljük a túlzsúfoltságot). Ezeket a mezőket lent lehet engedélyezni ha valamelyikre szükség van. Természetesen azok a letiltott mezők amelyek már ki vannak töltve, a beállítástól függetlenül láthatóak lesznek.', + 'optional_tj_date_fields' => 'Dátummezők', + 'optional_tj_business_fields' => 'Üzleti mezők', + 'optional_tj_attachment_fields' => 'Melléklet mezők', + 'pref_optional_tj_interest_date' => 'Kamatfizetési időpont', + 'pref_optional_tj_book_date' => 'Könyvelés dátuma', + 'pref_optional_tj_process_date' => 'Feldolgozás dátuma', + 'pref_optional_tj_due_date' => 'Esedékesség dátuma', + 'pref_optional_tj_payment_date' => 'Kifizetés dátuma', + 'pref_optional_tj_invoice_date' => 'Számla dátuma', + 'pref_optional_tj_internal_reference' => 'Belső hivatkozás', + 'pref_optional_tj_notes' => 'Megjegyzések', + 'pref_optional_tj_attachments' => 'Mellékletek', + 'optional_field_meta_dates' => 'Dátumok', + 'optional_field_meta_business' => 'Üzleti', + 'optional_field_attachments' => 'Mellékletek', + 'optional_field_meta_data' => 'Opcionális metaadat', + + // profile: + 'change_your_password' => 'Jelszó módosítása', + 'delete_account' => 'Fiók törlése', + 'current_password' => 'Jelenlegi jelszó', + 'new_password' => 'Új jelszó', + 'new_password_again' => 'Új jelszó megismétlése', + 'delete_your_account' => 'Saját fiók törlése', + 'delete_your_account_help' => 'A fiók törlése minden a Firefly III-ba elmentett számlát, tranzakciót, MINDENT törölni fog. Ezek EL FOGNAK TŰNNI.', + 'delete_your_account_password' => 'Meg kell adni a jelszót a folytatáshoz.', + 'password' => 'Jelszó', + 'are_you_sure' => 'Biztos? Nem vonható vissza.', + 'delete_account_button' => 'Saját fiók TÖRLÉSE', + 'invalid_current_password' => 'Érvénytelen jelenlegi jelszó!', + 'password_changed' => 'Jelszó módosítva!', + 'should_change' => 'Az ötlet az, hogy megváltoztasd a jelszavad.', + 'invalid_password' => 'Érvénytelen jelszó!', + 'what_is_pw_security' => 'Mit jelent a "jelszóbiztonság ellenőrzése"?', + 'secure_pw_title' => 'Hogyan válasszon biztonságos jelszót', + 'secure_pw_history' => 'Nincs olyan hét, hogy a hírekben ne lehetne arról olvasni, hogy egy weboldal elvesztette a felhasználói jelszavait. A hekkerek és a tolvajok a privát információk ellopására használják ezeket az jelszavakat. Ezek az információk értékesek.', + 'secure_pw_ff' => 'Ugyanazt a jelszót használod az egész interneten? Ha egy weboldal elveszti a jelszavadat a hekkerek minden adatodhoz hozzá fognak férni. A Firefly III rád bízza, hogy erős és egyedi jelszót válassz a pénzügyi adataid védelméhez.', + 'secure_pw_check_box' => 'A Firefly III úgy segít ebben, hogy ellenőrzi nem lopták-e el a múltban a megadott jelszót. Ha ez történt a Firefly III javasolni fogja, hogy NE használd ezt a jelszót.', + 'secure_pw_working_title' => 'Hogyan működik?', + 'secure_pw_working' => 'Bejelölve, a Firefly III elküldi a jelszó az SHA1 hashének első őt karakterét Troy Hunt weboldalának, hogy ellenőrizze szerepel-e a listában. Ez megakadályozza, hogy nem biztonságos jelszavakat használj ahogy az a legutolsó NIST Special Publication-ban javasolják.', + 'secure_pw_should' => 'Kipipáljam a négyzetet?', + 'secure_pw_long_password' => 'Igen. Mindig ellenőrizze, hogy a jelszavam biztonságos-e.', + 'command_line_token' => 'Parancssori token', + 'explain_command_line_token' => 'Erre a vezérjelre parancssori lehetőségek végrehajtásához van szükség, mint például adatok importálása vagy exportálása. Enélkül néhány érzékeny parancs nem fog működni. Ezt a vezérjelet senki sem fogja kérni tőled, még én sem. Ha félsz, hogy elveszted vagy paranoid vagy, a gombbal újra lehet generálni a vezérjelet.', + 'regenerate_command_line_token' => 'Parancssori token újragenerálása', + 'token_regenerated' => 'Az új parancssori token generálódott', + 'change_your_email' => 'Email cím módosítása', + 'email_verification' => 'Egy email üzenet lesz elküldve a régi ÉS az új email címre. Biztonsági okokból, nem lehet majd bejelentkezni amíg az új email cím nincs ellenőrizve. Ha nem biztos, hogy a Firefly III telepítés képes emaileket küldeni, nem szabad használni ezt a funkciót. Az adminisztrátor ellenőrizheti ezt az Adminisztráció oldalon.', + 'email_changed_logout' => 'Amíg nem ellenőrizted az email címed addig nem tudsz bejelentkezni.', + 'login_with_new_email' => 'Most már bejelentkezhet az új email címével.', + 'login_with_old_email' => 'Most már bejelentkezhet újra a régi email címével.', + 'login_provider_local_only' => 'Ez a művelet nem érhető el ":login_provider" általi hitelesítésekor.', + 'delete_local_info_only' => 'Mivel ":login_provider" a hitelesítőd,ezért ez csak a helyi Firefly III információt törli.', + + // attachments + 'nr_of_attachments' => 'Egy melléklet|:count melléklet', + 'attachments' => 'Mellékletek', + 'edit_attachment' => 'Melléklet szerkesztése ":name"', + 'update_attachment' => 'Melléklet frissítése', + 'delete_attachment' => '":name" melléklet törlése', + 'attachment_deleted' => 'Törölt melléklet ":name"', + 'liabilities_deleted' => 'Deleted liability ":name"', + 'attachment_updated' => 'Melléklet frissítve ":name"', + 'upload_max_file_size' => 'Maximális fájlméret: :size', + 'list_all_attachments' => 'Mellékletek listája', + + // transaction index + 'title_expenses' => 'Költségek', + 'title_withdrawal' => 'Költségek', + 'title_revenue' => 'Jövedelem / bevétel', + 'title_deposit' => 'Jövedelem / bevétel', + 'title_transfer' => 'Átvezetések', + 'title_transfers' => 'Átvezetések', + + // convert stuff: + 'convert_is_already_type_Withdrawal' => 'Ez a tranzakció már egy költség', + 'convert_is_already_type_Deposit' => 'Ez a tranzakció már egy bevétel', + 'convert_is_already_type_Transfer' => 'Ez a tranzakció már egy utalás', + 'convert_to_Withdrawal' => '":description" költséggé konvertálása', + 'convert_to_Deposit' => '":description" bevétellé alakítása', + 'convert_to_Transfer' => 'Átutalássá alakítás ":description"', + 'convert_options_WithdrawalDeposit' => 'Egy költség bevétellé alakítása', + 'convert_options_WithdrawalTransfer' => 'Egy költség átvezetéssé konvertálása', + 'convert_options_DepositTransfer' => 'Egy bevétel átvezetéssé alakítása', + 'convert_options_DepositWithdrawal' => 'Egy bevétel költséggé konvertálása', + 'convert_options_TransferWithdrawal' => 'Utalás kiadássá alakítása', + 'convert_options_TransferDeposit' => 'Egy átvezetés bevétellé alakítása', + 'convert_Withdrawal_to_deposit' => 'Költség bevétellé konvertálása', + 'convert_Withdrawal_to_transfer' => 'Költség átvezetéssé alakítása', + 'convert_Deposit_to_withdrawal' => 'Bevétel költséggé alakítása', + 'convert_Deposit_to_transfer' => 'Bevétel átvezetéssé alakítása', + 'convert_Transfer_to_deposit' => 'Átvezetés bevétellé alakítása', + 'convert_Transfer_to_withdrawal' => 'Átvezetés költséggé konvertálása', + 'convert_please_set_revenue_source' => 'Kérem válasszon ki egy bevételiszámlát, ahonnan a pénz fog érkezni.', + 'convert_please_set_asset_destination' => 'Kérem válasszon ki egy eszközszámlát, ahova a pénz fog menni.', + 'convert_please_set_expense_destination' => 'Ki kell választani egy költségszámlát, ahova a pénz fog menni.', + 'convert_please_set_asset_source' => 'Kérem válasszon ki egy eszközszámlát, ahonnan a pénz fog érkezni.', + 'convert_explanation_withdrawal_deposit' => 'Ennek a költségnek bevétellé konvertálása esetén :amount bevételként fog megjelenni :sourceName forrásszámlán ahelyett, hogy levonódna belőle.', + 'convert_explanation_withdrawal_transfer' => 'Ennek a költségnek átvezetéssé konvertálása esetén :amount át lesz vezetve :sourceName számláról egy új eszközszámlára ahelyett, hogy be lenne fizetve :destinationName számlára.', + 'convert_explanation_deposit_withdrawal' => 'Ennek a bevételnek költséggé konvertálása esetén :amount el lesz távolítva :destinationName számláról ahelyett, hogy hozzá lenne adva.', + 'convert_explanation_deposit_transfer' => 'Ennek a bevételnek átvezetéssé konvertálása esetén :amount át lesz vezetve egy kiválasztott eszközszámláról :destinationName számlára.', + 'convert_explanation_transfer_withdrawal' => 'Ennek az átvezetésnek költséggé konvertálása esetén :amount :sourceName számláról átkerül egy új célszámlára mint költség ahelyett, hogy átvezetésként szerepelne <:destinationName számlán.', + 'convert_explanation_transfer_deposit' => 'Ennek az átvezetésnek bevétellé konvertálása esetén :amount be lesz vételezve :destinationName számlára ahelyett, hogy át lenne rá vezetve.', + 'converted_to_Withdrawal' => 'Tranzakció költséggé konvertálva', + 'converted_to_Deposit' => 'A tranzakció bevétellé konvertálva', + 'converted_to_Transfer' => 'Tranzakció átvezetéssé konvertálva', + 'invalid_convert_selection' => 'A kiválasztott számla már használatban van ebben a tranzakcióban vagy nem létezik.', + 'source_or_dest_invalid' => 'Nem találhatóak a megfelelő tranzakció részletek. A konverzió nem lehetséges.', + 'convert_to_withdrawal' => 'Konvertálás költséggé', + 'convert_to_deposit' => 'Konvertálás egy bevétellé', + 'convert_to_transfer' => 'Konvertálás átvezetéssé', + + // create new stuff: + 'create_new_withdrawal' => 'Új költség létrehozása', + 'create_new_deposit' => 'Új bevétel létrehozása', + 'create_new_transfer' => 'Új átvezetés létrehozása', + 'create_new_asset' => 'Új eszközszámla létrehozása', + 'create_new_expense' => 'Új költségszámla létrehozása', + 'create_new_revenue' => 'Új jövedelemszámla létrehozása', + 'create_new_piggy_bank' => 'Új malacpersely létrehozása', + 'create_new_bill' => 'Új számla létrehozása', + + // currencies: + 'create_currency' => 'Új pénznem létrehozása', + 'store_currency' => 'Új pénznem tárolása', + 'update_currency' => 'Pénznem frissítése', + 'new_default_currency' => ':name lett az alapértelmezett pénznem.', + 'cannot_delete_currency' => ':name nem törölhető mert még használatban van.', + 'cannot_disable_currency' => ':name nem tiltható le, mert még használatban van.', + 'deleted_currency' => 'A pénznem :name törölve lett', + 'created_currency' => 'Pénznem :name létrehozva', + 'could_not_store_currency' => 'Az új pénznem nem tárolható.', + 'updated_currency' => 'Pénznem :name frissítve', + 'ask_site_owner' => ':owner tud pénznemeket hozzáadni, törölni vagy szerkeszteni.', + 'currencies_intro' => 'A Firefly III különböző pénznemeket támogat amelyeket itt lehet beállítani, és engedélyezni.', + 'make_default_currency' => 'Legyen alapértelmezett', + 'default_currency' => 'alapértelmezett', + 'currency_is_disabled' => 'Letiltva', + 'enable_currency' => 'Engedélyezés', + 'disable_currency' => 'Tiltás', + 'currencies_default_disabled' => 'A pénznemek többsége alapértelmezés szerint tiltva van. A használathoz először engedélyezni kell azokat.', + 'currency_is_now_enabled' => 'Pénznem ":name" engedélyezve', + 'currency_is_now_disabled' => 'Pénznem ":name" letiltva', + + // forms: + 'mandatoryFields' => 'Kötelező mezők', + 'optionalFields' => 'Nem kötelező mezők', + 'options' => 'Beállítások', + + // budgets: + 'create_new_budget' => 'Új költségkeret létrehozása', + 'store_new_budget' => 'Új költségkeret létrehozása', + 'stored_new_budget' => 'Új költségkeret létrehozva ":name"', + 'available_between' => ':start és :end között rendelkezésre áll', + 'transactionsWithoutBudget' => 'Költségkeret nélküli költségek', + 'transactions_no_budget' => 'Költségkeret nélküli költségek :start és :end között', + 'spent_between' => 'Költés :start and :end között', + 'createBudget' => 'Új költségkeret', + 'inactiveBudgets' => 'Inaktív költségkeretek', + 'without_budget_between' => 'Költségkeret nélküli tranzakció :start és :end között', + 'delete_budget' => '":name" költségkeret törlése', + 'deleted_budget' => 'Költségkeret törölve ":name"', + 'edit_budget' => '":name" költségkeret szerkesztése', + 'updated_budget' => 'Költségkeret frissítve ":name"', + 'update_amount' => 'Összeg frissítése', + 'update_budget' => 'Költségvetés frissítése', + 'update_budget_amount_range' => 'A (várt) rendelkezésre álló összeg frissítése :start és :end között', + 'budget_period_navigator' => 'Időszak navigátor', + 'info_on_available_amount' => 'What do I have available?', + 'available_amount_indication' => 'Ezeket az összeget felhasználva lehet megtudni, hogy mekkorának kéne lennie a teljes költségkeretnek.', + 'suggested' => 'Javasolt', + 'average_between' => 'Átlag :start és :end között', + 'over_budget_warn' => 'A költségkeret általában napi :amount körül van. Jelenleg napi :over_amount. Biztos?', + 'transferred_in' => 'Átvezetett (be)', + 'transferred_away' => 'Transferred (away)', + + // bills: + 'match_between_amounts' => 'Tranzakciókkal egyező számlák :low és :high között.', + 'bill_related_rules' => 'Erre a számlára vonatkozó szabályok', + 'repeats' => 'Ismétlődések', + 'connected_journals' => 'Kapcsolódó tranzakciók', + 'auto_match_on' => 'A Firefly III által automatikusan egyeztetett', + 'auto_match_off' => 'A Firefly III által nem automatikusan egyeztetett', + 'next_expected_match' => 'Következő várható egyezés', + 'delete_bill' => '":name" számla törlése', + 'deleted_bill' => '":name" számla törölve', + 'edit_bill' => '":name" számla szerkesztése', + 'more' => 'Több', + 'rescan_old' => 'Szabályok újrafuttatása az összes tranzakción', + 'update_bill' => 'Számla frissítése', + 'updated_bill' => '":name" számla frissítve', + 'store_new_bill' => 'Új számla tárolása', + 'stored_new_bill' => 'Új ":name" számla eltárolva', + 'cannot_scan_inactive_bill' => 'Inaktív számlákat nem lehet vizsgálni.', + 'rescanned_bill' => 'Minden újraolvasva, :total tranzakció lett a számlákhoz csatolva.', + 'average_bill_amount_year' => 'Átlagos számlaösszeg (:year)', + 'average_bill_amount_overall' => 'Átlagos számlaösszeg (összes)', + 'bill_is_active' => 'A számla aktív', + 'bill_expected_between' => 'Várható :start és :end között', + 'bill_will_automatch' => 'A számla automatikusan hozzá lesz csatolva az egyező tranzakciókhoz', + 'skips_over' => 'skips over', + 'bill_store_error' => 'Nem várt hiba történt az új számla tárolása közben. Ellenőrizd a naplófájlokat', + 'list_inactive_rule' => 'inaktív szabály', + + // accounts: + 'account_missing_transaction' => ':id (":name") számla nem tekinthető meg közvetlenül, de a Fireflyból hiányzik az átirányítási információ.', + 'details_for_asset' => '":name" eszközszámla részletei', + 'details_for_expense' => '":name" költségszámla részletei', + 'details_for_revenue' => '":name" jövedelemszámla részletei', + 'details_for_cash' => '":name" készpénzszámla részletei', + 'store_new_asset_account' => 'Új eszközszámla tárolása', + 'store_new_expense_account' => 'Új költségszámla tárolása', + 'store_new_revenue_account' => 'Új jövedelemszámla létrehozása', + 'edit_asset_account' => '":name" eszközszámla szerkesztése', + 'edit_expense_account' => '":name" költségszámla szerkesztése', + 'edit_revenue_account' => '":name" jövedelemszámla szerkesztése', + 'delete_asset_account' => '":name" eszközszámla törlése', + 'delete_expense_account' => '":name" költségszámla törlése', + 'delete_revenue_account' => '":name" jövedelemszámla törlése', + 'delete_liabilities_account' => '":name" kötelezettség törlése', + 'asset_deleted' => '":name" eszközszámla sikeresen törölve', + 'expense_deleted' => '":name" költségszámla sikeresen törölve', + 'revenue_deleted' => '":name" jövedelemszámla sikeresen törölve', + 'update_asset_account' => 'Eszközszámla frissítése', + 'update_liabilities_account' => 'Kötelezettség frissítése', + 'update_expense_account' => 'Költségszámla frissítése', + 'update_revenue_account' => 'Jövedelemszámla frissítése', + 'make_new_asset_account' => 'Új eszközszámla létrehozása', + 'make_new_expense_account' => 'Új költségszámla létrehozása', + 'make_new_revenue_account' => 'Új jövedelemszámla létrehozása', + 'make_new_liabilities_account' => 'Új kötelezettség létrehozása', + 'asset_accounts' => 'Eszközszámlák', + 'expense_accounts' => 'Költségszámlák', + 'revenue_accounts' => 'Jövedelemszámlák', + 'cash_accounts' => 'Készpénzszámlák', + 'Cash account' => 'Készpénzszámla', + 'liabilities_accounts' => 'Kötelezettségek', + 'reconcile_account' => '":account" számla egyeztetése', + 'overview_of_reconcile_modal' => 'Egyeztetés áttekintése', + 'delete_reconciliation' => 'Egyeztetés törlése', + 'update_reconciliation' => 'Egyeztetés frissítése', + 'amount_cannot_be_zero' => 'Az összeg nem lehet nulla', + 'end_of_reconcile_period' => 'Egyeztetési időszak vége: :period', + 'start_of_reconcile_period' => 'Egyeztetési időszak kezdete: :period', + 'start_balance' => 'Kezdő egyenleg', + 'end_balance' => 'Záró egyenleg', + 'update_balance_dates_instruction' => 'Ha a fenti összegek és dátumok összevetése a bankszámlakivonattal megtörtént, az "Egyeztetés indítása" gombra kell kattintani', + 'select_transactions_instruction' => 'A bankszámlakivonaton szereplő tranzakciók kiválasztása.', + 'select_range_and_balance' => 'Először ellenőrizd a dátum időszakot és az egyenlegeket. Utána nyomd meg az "Egyeztetés indítása" gombot', + 'date_change_instruction' => 'Az időintervallum megváltoztatásával minden előrehaladás el fog veszni.', + 'update_selection' => 'Kiválasztás frissítése', + 'store_reconcile' => 'Egyeztetés letárolása', + 'reconciliation_transaction' => 'Tranzakció egyeztetése', + 'Reconciliation' => 'Egyeztetés', + 'reconciliation' => 'Egyeztetés', + 'reconcile_options' => 'Egyeztetés beállításai', + 'reconcile_range' => 'Egyeztetés időszaka', + 'start_reconcile' => 'Egyeztetés indítása', + 'cash_account_type' => 'Cash', + 'cash' => 'készpénz', + 'account_type' => 'Bankszámla típusa', + 'save_transactions_by_moving' => 'Tranzakciók mentése más számlára mozgatással:', + 'stored_new_account' => '":name" új számla letárolva!', + 'updated_account' => '":name" számla frissítve', + 'credit_card_options' => 'Hitelkártya opciók', + 'no_transactions_account' => 'Nincsenek tranzakciók (ebben az időszakban) az eszközszámlához: ":name".', + 'no_transactions_period' => 'Nincsenek tranzakció ebben az időszakban.', + 'no_data_for_chart' => 'Nincs elegendő információ (még) a diagram létrehozásához.', + 'select_at_least_one_account' => 'Kérem, jelöljön ki legalább egy eszközszámlát', + 'select_at_least_one_category' => 'Legalább egy kategóriát ki kell választani', + 'select_at_least_one_budget' => 'Legalább egy költségkeretet ki kell választani', + 'select_at_least_one_tag' => 'Legalább egy címkét ki kell választani', + 'select_at_least_one_expense' => 'Legalább egy költség- jövedelemszámla kombinációt ki kell választani. Ha nincs egy sem (a lista üres), ez a jelenés nem érhető el.', + 'account_default_currency' => 'Ez az ehhez a számlához társított alapértelmezett pénznem.', + 'reconcile_has_more' => 'A Firefly III főkönyvben több pénz van mint amennyinek a bank szerint lennie kellene. Több lehetőség van. Ki kell választani mi történjen. Ezután az "Egyeztetés megerősítése" gombra kell kattintani.', + 'reconcile_has_less' => 'A Firefly III főkönyvben kevesebb pénz van mint amennyinek a bank szerint lennie kellene. Több lehetőség van. Ki kell választani mi történjen. Ezután az "Egyeztetés megerősítése" gombra kell kattintani.', + 'reconcile_is_equal' => 'A Firefly III főkönyv és a bankszámlakivonatok egyeznek. Nem kell semmit sem tenni. Az importálás megerősítéséhez az "Egyeztetés megerősítése" gombra kell kattintani.', + 'create_pos_reconcile_transaction' => 'A kiválasztott tranzakciók törlése és egy korrekció létrehozása :amount hozzáadásával ehhez az eszközszámlához.', + 'create_neg_reconcile_transaction' => 'A kiválasztott tranzakciók törlése, és egy korrekció létrehozása :amount eltávolításával az eszközszámláról.', + 'reconcile_do_nothing' => 'Clear the selected transactions, but do not correct.', + 'reconcile_go_back' => 'Egy korrekció később bármikor szerkeszthető vagy törölhető.', + 'must_be_asset_account' => 'Csak eszközszámlákat lehet egyeztetni', + 'reconciliation_stored' => 'Egyeztetés letárolva', + 'reconciliation_error' => 'Hiba miatt a tranzakciók meg lettek jelölve egyeztetésre, de az egyeztetés nem lett eltárolva: :error.', + 'reconciliation_transaction_title' => 'Egyeztetés (:from - :to)', + 'sum_of_reconciliation' => 'Egyeztetés összege', + 'reconcile_this_account' => 'Számla egyeztetése', + 'confirm_reconciliation' => 'Számla megerősítése', + 'submitted_start_balance' => 'Beküldött kezdő egyenleg', + 'selected_transactions' => 'Kiválasztott tranzakciók (:count)', + 'already_cleared_transactions' => 'Már törölt tranzakciók (:count)', + 'submitted_end_balance' => 'Beküldött záró egyenleg', + 'initial_balance_description' => '":account" kezdeti egyenlege', + 'interest_calc_' => 'ismeretlen', + 'interest_calc_daily' => 'Naponta', + 'interest_calc_monthly' => 'Havonta', + 'interest_calc_yearly' => 'Évente', + 'initial_balance_account' => ':account kezdeti egyenlegfiókja', + + // categories: + 'new_category' => 'Új kategória', + 'create_new_category' => 'Új kategória létrehozása', + 'without_category' => 'Kategória nélkül', + 'update_category' => 'Kategória frissítése', + 'updated_category' => '":name" kategória frissítve', + 'categories' => 'Kategóriák', + 'edit_category' => '":name" kategória szerkesztése', + 'no_category' => '(nincs kategória)', + 'category' => 'Kategória', + 'delete_category' => '":name" kategória törlése', + 'deleted_category' => '":name" kategória törlése', + 'store_category' => 'Új kategória tárolása', + 'stored_category' => 'Új kategória létrehozva ":name"', + 'without_category_between' => 'Kategória nélkül :start és :end között', + + // transactions: + 'update_withdrawal' => 'Költség frissítése', + 'update_deposit' => 'Bevétel szerkesztése', + 'update_transfer' => 'Utalás szerkesztése', + 'updated_withdrawal' => 'Költség frissítve ":description"', + 'updated_deposit' => '":description" bevétel frissítve', + 'updated_transfer' => 'Átvezetés frissítve ":description"', + 'delete_withdrawal' => 'Költség törölve ":description"', + 'delete_deposit' => '":description" bevétel törlése', + 'delete_transfer' => 'Átvezetés törlése ":description"', + 'deleted_withdrawal' => 'A költség sikeresen törölve ":description"', + 'deleted_deposit' => '":description" bevétel sikeresen törölve', + 'deleted_transfer' => '":description" átvezetés sikeresen törölve', + 'stored_journal' => '":description" új tranzakció sikeresen létrehozva', + 'stored_journal_no_descr' => 'Új tranzakció sikeresen létrehozva', + 'updated_journal_no_descr' => 'Tranzakció sikeresen frissítve', + 'select_transactions' => 'Tranzakciók kiválasztása', + 'rule_group_select_transactions' => '":title" alkalmazása tranzakciókon', + 'rule_select_transactions' => '":title" alkalmazása tranzakciókon', + 'stop_selection' => 'Tranzakciók kiválasztásának leállítása', + 'reconcile_selected' => 'Egyeztetés', + 'mass_delete_journals' => 'Több tranzakció törlése', + 'mass_edit_journals' => 'Több tranzakció szerkesztése', + 'mass_bulk_journals' => 'Több tranzakció tömeges szerkesztése', + 'mass_bulk_journals_explain' => 'This form allows you to change properties of the transactions listed below in one sweeping update. All the transactions in the table will be updated when you change the parameters you see here.', + 'part_of_split' => 'This transaction is part of a split transaction. If you have not selected all the splits, you may end up with changing only half the transaction.', + 'bulk_set_new_values' => 'Új értékeket a lenti mezőkben lehet megadni. Üresen hagyva mindig üresek lesznek. Fontos tudni, hogy csak a költségekhez lesz költségkeret rendelve.', + 'no_bulk_category' => 'Ne frissítse a kategóriát', + 'no_bulk_budget' => 'Ne frissítse a költségkeretet', + 'no_bulk_tags' => 'Ne frissítse a cimké(ke)t', + 'mass_edit' => 'Edit selected individually', + 'bulk_edit' => 'Edit selected in bulk', + 'mass_delete' => 'Delete selected', + 'cannot_edit_other_fields' => 'Ezeken kívül nem lehet más mezőket tömegesen szerkeszteni, mert nincs elég hely a megjelenítésükhöz. Ha szerkeszteni kell ezeket a mezőket, a hivatkozás használatával lehet megtenni egyesével.', + 'cannot_change_amount_reconciled' => 'You can\'t change the amount of reconciled transactions.', + 'no_budget' => '(nincs költségkeret)', + 'no_budget_squared' => '(nincs költségkeret)', + 'perm-delete-many' => 'Deleting many items in one go can be very disruptive. Please be cautious. You can delete part of a split transaction from this page, so take care.', + 'mass_deleted_transactions_success' => ':amount tranzakció törölve.', + 'mass_edited_transactions_success' => ':amount tranzakció frissítve', + 'opt_group_' => '(nincs számlatípus)', + 'opt_group_no_account_type' => '(nincs számlatípus)', + 'opt_group_defaultAsset' => 'Alapértelmezett eszközszámlák', + 'opt_group_savingAsset' => 'Megtakarítási számlák', + 'opt_group_sharedAsset' => 'Megosztott eszközszámlák', + 'opt_group_ccAsset' => 'Hitelkártyák', + 'opt_group_cashWalletAsset' => 'Készpénz', + 'opt_group_expense_account' => 'Expense accounts', + 'opt_group_revenue_account' => 'Revenue accounts', + 'opt_group_l_Loan' => 'Kötelezettség: hitel', + 'opt_group_cash_account' => 'Cash account', + 'opt_group_l_Debt' => 'Kötelezettség: adósság', + 'opt_group_l_Mortgage' => 'Kötelezettség: jelzálog', + 'opt_group_l_Credit card' => 'Kötelezettség: hitelkártya', + 'notes' => 'Megjegyzések', + 'unknown_journal_error' => 'Nem lehet letárolni a tranzakciót. Ellenőrizni kell a naplófájlokat.', + 'attachment_not_found' => 'Ez a melléklet nem található.', + 'journal_link_bill' => 'Ez a tranzakció :name számlához van csatolva. A kapcsolat eltávolításához ki kell venni a jelölést a jelölőnégyzetből. Szabályok használatával másik számlához lehet csatolni.', + + // new user: + 'welcome' => 'Üdvözöli a Firefly III!', + 'submit' => 'Beküldés', + 'submit_yes_really' => 'Beküldés (tudom mit csinálok)', + 'getting_started' => 'Első lépések', + 'to_get_started' => 'Gratulálunk a Firefly III sikeres telepítéséhez. A használat megkezdéséhez meg kell adni a bank nevét és a főszámla egyenlegét. Semmi probléma ha több bankszámlát is kezelni kell, ezeket később hozzá lehet adni. Most csak a Firefly III használatának elkezdéséhez szükséges néhány adatot kell megadni.', + 'savings_balance_text' => 'A Firefly III automatikusan létrehoz egy megtakarítási számlát. Alapértelmezés szerint pénz nem lesz a megtakarítási számlán, de megadható a Firefly III-nak egy mérleg, ami el lesz tárolva.', + 'finish_up_new_user' => 'Kész is! A továbblépéshez a Beküldés gombra kell kattintani. A gombra kattintás után megnyílik a Firefly III kezdőlapja.', + 'stored_new_accounts_new_user' => 'Hurrá! Az új bankszámlák tárolva lettek.', + 'set_preferred_language' => 'Ha más nyelven szeretnéd használni a Firefly III-at, kérjük jelezd itt.', + 'language' => 'Nyelv', + 'new_savings_account' => ':bank_name megtakarítási számla', + 'cash_wallet' => 'Készpénz', + 'currency_not_present' => 'Ne aggódj ha az általad normál esetben használt pénznem nincs a listában. Létrehozhatod a saját pénznemedet a Beállítások -> Pénznemek alatt.', + + // home page: + 'yourAccounts' => 'Bankszámlák', + 'your_accounts' => 'Számla áttekintése', + 'category_overview' => 'Kategória áttekintés', + 'expense_overview' => 'Költségszámla áttekintése', + 'revenue_overview' => 'Jövedelemszámla áttekintése', + 'budgetsAndSpending' => 'Költségkeretek és kiadások', + 'budgets_and_spending' => 'Költségkeretek és kiadások', + 'go_to_budget' => 'Ugrás költségkerethez "{budget}"', + 'savings' => 'Magtakarítások', + 'newWithdrawal' => 'Új költség', + 'newDeposit' => 'Új bevétel', + 'newTransfer' => 'Új átvezetés', + 'bills_to_pay' => 'Fizetendő számlák', + 'per_day' => 'Naponta', + 'left_to_spend_per_day' => 'Naponta elkölthető', + 'bills_paid' => 'Befizetett számlák', + + // menu and titles, should be recycled as often as possible: + 'currency' => 'Pénznem', + 'preferences' => 'Beállítások', + 'logout' => 'Kijelentkezés', + 'toggleNavigation' => 'Navigációs mód átkapcsolása', + 'searchPlaceholder' => 'Keresés...', + 'version' => 'Verizó', + 'dashboard' => 'Műszerfal', + 'available_budget' => 'Rendelkezésre álló költségkeret ({currency})', + 'currencies' => 'Pénznemek', + 'activity' => 'Tevékenység', + 'usage' => 'Használat', + 'accounts' => 'Számlák', + 'Asset account' => 'Eszközszámla', + 'Default account' => 'Eszközszámla', + 'Expense account' => 'Költségszámla', + 'Revenue account' => 'Jövedelemszámla', + 'Initial balance account' => 'Számla kezdeti egyenlege', + 'account_type_Debt' => 'Adósság', + 'account_type_Loan' => 'Hitel', + 'account_type_Mortgage' => 'Jelzálog', + 'account_type_Credit card' => 'Hitelkártya', + 'budgets' => 'Költségkeretek', + 'tags' => 'Címkék', + 'reports' => 'Jelentések', + 'transactions' => 'Tranzakciók', + 'expenses' => 'Költségek', + 'income' => 'Jövedelem / bevétel', + 'transfers' => 'Átvezetések', + 'moneyManagement' => 'Pénzkezelés', + 'money_management' => 'Pénzkezelés', + 'tools' => 'Eszközök', + 'piggyBanks' => 'Malacperselyek', + 'piggy_banks' => 'Malacperselyek', + 'amount_x_of_y' => '{current} / {total}', + 'bills' => 'Számlák', + 'withdrawal' => 'Költség', + 'opening_balance' => 'Nyitó egyenleg', + 'deposit' => 'Bevétel', + 'account' => 'Bankszámla', + 'transfer' => 'Átvezetés', + 'Withdrawal' => 'Költség', + 'Deposit' => 'Bevétel', + 'Transfer' => 'Átvezetés', + 'bill' => 'Számla', + 'yes' => 'Igen', + 'no' => 'Nem', + 'amount' => 'Összeg', + 'overview' => 'Áttekintés', + 'saveOnAccount' => 'Megtakarítás a számlán:', + 'unknown' => 'Ismeretlen', + 'daily' => 'Napi', + 'monthly' => 'Havi', + 'profile' => 'Profil', + 'errors' => 'Hibák', + 'debt_start_date' => 'Adósság kezdete', + 'debt_start_amount' => 'Adósság kezdeti összege', + 'debt_start_amount_help' => 'If you owe an amount its best to enter a negative amount, because it influences your net worth. If you\'re owed an amount the same applies. Check out the help pages for more information.', + 'store_new_liabilities_account' => 'Új kötelezettség eltárolása', + 'edit_liabilities_account' => '":name" kötelezettség szerkesztése', + + // reports: + 'report_default' => 'Alapértelmezett pénzügyi jelentés :start és :end között', + 'report_audit' => 'Tranzakciótörténet áttekintése :start és :end között', + 'report_category' => 'Ktegória jelentés :start és :end között', + 'report_account' => 'Költség- jövedelemszámla jelentés :start és :end között', + 'report_budget' => 'Költségkeret jelentés :start és :end között', + 'report_tag' => 'Címke jelentés :start és :end között', + 'quick_link_reports' => 'Gyorshivatkozások', + 'quick_link_examples' => 'Itt csak az indulást segítő néhány példa hivatkozás található. A súgó oldalakon, a (?) ikon alatt, további információ található a jelentésekről és a használható mágikus szavakról.', + 'quick_link_default_report' => 'Alapértelmezett pénzügyi jelentés', + 'quick_link_audit_report' => 'Tranzakciótörténeti áttekintés', + 'report_this_month_quick' => 'Aktuális hónap, minden bankszámla', + 'report_last_month_quick' => 'Utolsó hónap, minden számla', + 'report_this_year_quick' => 'Folyó év, minden bankszámla', + 'report_this_fiscal_year_quick' => 'Jelenlegi pénzügyi év, minden bankszámla', + 'report_all_time_quick' => 'Mindig, minden számla', + 'reports_can_bookmark' => 'A jelentéseket meg lehet jelölni könyvjelzővel.', + 'incomeVsExpenses' => 'Bevételek - Költségek', + 'accountBalances' => 'Számla egyenlegek', + 'balanceStart' => 'Egyenleg az időszak kezdetén', + 'balanceEnd' => 'Egyenleg az időszak végén', + 'splitByAccount' => 'Bankszámlákra osztva', + 'coveredWithTags' => 'Covered with tags', + 'leftInBudget' => 'Költségkeretből maradt', + 'sumOfSums' => 'Összegek összege', + 'noCategory' => '(nincs kategória)', + 'notCharged' => 'Nem változott (még)', + 'inactive' => 'Inaktív', + 'active' => 'Aktív', + 'difference' => 'Különbség', + 'money_flowing_in' => 'Be', + 'money_flowing_out' => 'Ki', + 'topX' => 'top :number', + 'show_full_list' => 'Teljes lista megjelenítése', + 'show_only_top' => 'Csak a top mutatása: :number', + 'report_type' => 'Jelentés típusa', + 'report_type_default' => 'Alapértelmezett pénzügyi jelentés', + 'report_type_audit' => 'Tranzakciótörténeti áttekintés (auditálás)', + 'report_type_category' => 'Kategória jelentés', + 'report_type_budget' => 'Költségkeret jelentés', + 'report_type_tag' => 'Címke jelentés', + 'report_type_account' => 'Költség- jövedelemszámla jelentés', + 'more_info_help' => 'Ezekkel a jelentéstípusokkal kapcsolatban további információ található a súgóoldalakon. A súgó a jobb felső sarokban található (?) ikonra kattintva érhető el.', + 'report_included_accounts' => 'Befoglalt számlák', + 'report_date_range' => 'Dátum intervallum', + 'report_preset_ranges' => 'Előre beállított intervallumok', + 'shared' => 'Megosztott', + 'fiscal_year' => 'Pénzügyi év', + 'income_entry' => '":name" bevételei :start és :end között', + 'expense_entry' => '":name" számla kiadásai :start és :end között', + 'category_entry' => '":name" kategória kiadásai :start és :end között', + 'budget_spent_amount' => '":budget" költségkeret kiadásai :start és :end között', + 'balance_amount' => '":budget" költségkeret ":account" számláról fizetett kiadásai :start és :end között', + 'no_audit_activity' => ':account_name számlán nem volt aktivitás :start és :end között.', + 'audit_end_balance' => ':account_name számlaegyenlege :end végén :balance volt', + 'reports_extra_options' => 'További beállítások', + 'report_has_no_extra_options' => 'Ennek a jelentésnek nincsenek további beállításai', + 'reports_submit' => 'Jelentés megtekintése', + 'end_after_start_date' => 'A jelentés végdátumának a kezdődátum után kell lennie.', + 'select_category' => 'Kategóriák kiválasztása', + 'select_budget' => 'Költségkeretek kiválasztása.', + 'select_tag' => 'Címkék kiválasztása.', + 'income_per_category' => 'Bevétel kategória szerint', + 'expense_per_category' => 'Költség kategória szerint', + 'expense_per_budget' => 'Költség költségkeret szerint', + 'income_per_account' => 'Bevétel bankszámla szerint', + 'expense_per_account' => 'Költség bankszámlák szerint', + 'expense_per_tag' => 'Költség címke szerint', + 'income_per_tag' => 'Bevétel címke szerint', + 'include_expense_not_in_budget' => 'A kiválasztott költségkeretekben nem szereplő költségeket is beleértve', + 'include_expense_not_in_account' => 'A kiválasztott számlákban nem szereplő költségeket is beleértve', + 'include_expense_not_in_category' => 'A kiválasztott kategóriákban nem szereplő költségeket is beleértve', + 'include_income_not_in_category' => 'A kiválasztott kategóriákban nem szereplő bevételeket is beleértve', + 'include_income_not_in_account' => 'A kiválasztott számlákban nem szereplő bevételeket is beleértve', + 'include_income_not_in_tags' => 'A kiválasztott címkékben nem szereplő bevételeket is beleértve', + 'include_expense_not_in_tags' => 'A kiválasztott címkékben nem szereplő költségeket is beleértve', + 'everything_else' => 'Minden más', + 'income_and_expenses' => 'Bevételek és költségek', + 'spent_average' => 'Költés (átlag)', + 'income_average' => 'Bevétel (átlag)', + 'transaction_count' => 'Tranzakciók száma', + 'average_spending_per_account' => 'Átlagos kiadások bankszámlák szerint', + 'average_income_per_account' => 'Átlagos bevétel címke szerint', + 'total' => 'Össszesen', + 'description' => 'Leírás', + 'sum_of_period' => 'Időszak összege', + 'average_in_period' => 'Átlag az időszakban', + 'account_role_defaultAsset' => 'Alapértelmezett eszközszámla', + 'account_role_sharedAsset' => 'Megosztott eszközszámla', + 'account_role_savingAsset' => 'Megtakarítási számla', + 'account_role_ccAsset' => 'Hitelkártya', + 'account_role_cashWalletAsset' => 'Készpénz', + 'budget_chart_click' => 'A diagram megjelenítéséhez egy költségkeret nevére kell kattintani a fenti táblázatban.', + 'category_chart_click' => 'A diagram megjelenítéséhez egy kategória nevére kell kattintani a fenti táblázatban.', + 'in_out_accounts' => 'Megkeresett és költött kombinációként', + 'in_out_per_category' => 'Megkeresett és költött kategóriaként', + 'out_per_budget' => 'Kiadás költségkeret szerint', + 'select_expense_revenue' => 'Költség- jövedelemszámla kiválasztása', + 'multi_currency_report_sum' => 'Mivel a lista több eltérő pénznemet használó számlát tartalmaz, az összegeknek nem lesz értelme. A jelentés mindig az alapértelmezés szerinti pénznemet fogja használni.', + 'sum_in_default_currency' => 'Az összeg mindig az alapértelmezett pénznemben lesz.', + 'net_filtered_prefs' => 'Ezen a diagramon sose fognak szerepelni azok a számlák melyeknél a "Befoglalva a nettó értékbe" beállítás nincs bekapcsolva.', + + // charts: + 'chart' => 'Diagram', + 'month' => 'Hónap', + 'budget' => 'Költségkeret', + 'spent' => 'Elköltött', + 'spent_in_budget' => 'Költségkeretből elköltve', + 'left_to_spend' => 'Elkölthető', + 'earned' => 'Megkeresett', + 'overspent' => 'Túlköltött', + 'left' => 'Maradvány', + 'max-amount' => 'Maximális összeg', + 'min-amount' => 'Minimális összeg', + 'journal-amount' => 'Jelenlegi számla bejegyzés', + 'name' => 'Név', + 'date' => 'Dátum', + 'paid' => 'Kifizetve', + 'unpaid' => 'Nincs fizetve', + 'day' => 'Nap', + 'budgeted' => 'Betervezett', + 'period' => 'Időszak', + 'balance' => 'Egyenleg', + 'sum' => 'Összesen', + 'summary' => 'Összefoglaló', + 'average' => 'Átlag', + 'balanceFor' => 'Egyenleg: :name', + 'no_tags_for_cloud' => 'Nincs címke felhő létrehozásához', + 'tag_cloud' => 'Címkefelhő', + + // piggy banks: + 'add_money_to_piggy' => 'Pénz hozzáadása ":name" malacperselyhez', + 'piggy_bank' => 'Malacpersely', + 'new_piggy_bank' => 'Új malacpersely', + 'store_piggy_bank' => 'Új malacpersely tárolása', + 'stored_piggy_bank' => 'Új ":name" malacpersely eltárolva', + 'account_status' => 'Számla állapota', + 'left_for_piggy_banks' => 'Maradt a malacperselyeknek', + 'sum_of_piggy_banks' => 'Malacperselyek összesen', + 'saved_so_far' => 'Megtakarítás eddig', + 'left_to_save' => 'Megtakarítható', + 'suggested_amount' => 'Javasolt havi megtakarítási összeg', + 'add_money_to_piggy_title' => 'Pénz elhelyezése ":name" malacperselyben', + 'remove_money_from_piggy_title' => 'Pénz kivétele ":name" malacperselyből', + 'add' => 'Hozzáadás', + 'no_money_for_piggy' => 'Nincs pénz amit be lehetne tenni ebbe a malacperselybe.', + 'suggested_savings_per_month' => 'Havonként javasolt', + + 'remove' => 'Eltávolítás', + 'max_amount_add' => 'A maximálisan hozzáadható összeg', + 'max_amount_remove' => 'A maximálisan kivehető összeg', + 'update_piggy_button' => 'Malacpersely frissítése', + 'update_piggy_title' => '":name" malacpersely frissítése', + 'updated_piggy_bank' => '":name" malacpersely frissítve', + 'details' => 'Részletek', + 'events' => 'Események', + 'target_amount' => 'Cél összeg', + 'start_date' => 'Kezdő dátum', + 'no_start_date' => 'Nincs kezdő dátum', + 'target_date' => 'Cél dátum', + 'no_target_date' => 'Nincs kezdő dátuma', + 'table' => 'Táblázat', + 'delete_piggy_bank' => '":name" malacpersely törlése', + 'cannot_add_amount_piggy' => ':amount nem adható hozzá ehhez: ":name".', + 'cannot_remove_from_piggy' => ':amount nem távolítható el innen: ":name".', + 'deleted_piggy_bank' => '":name" malacpersely törölve', + 'added_amount_to_piggy' => ':amount hozzáadva ehhez: ":name"', + 'removed_amount_from_piggy' => ':amount eltávolítva innen: ":name"', + 'piggy_events' => 'Kapcsolódó Malacperselyek', + + // tags + 'delete_tag' => '":tag" címke törlése', + 'deleted_tag' => '":tag" címke törölve', + 'new_tag' => 'Új címke készítése', + 'edit_tag' => '":tag" címke szerkesztése', + 'updated_tag' => '":tag" címke frissítve', + 'created_tag' => '":tag" címke létrehozva!', + + 'transaction_journal_information' => 'Tranzakciós információk', + 'transaction_journal_meta' => 'Meta-információ', + 'transaction_journal_more' => 'További információ', + 'att_part_of_journal' => 'Eltárolva ":journal" alatt', + 'total_amount' => 'Teljes összeg', + 'number_of_decimals' => 'Tizedesjegyek száma', + + // administration + 'administration' => 'Adminisztráció', + 'user_administration' => 'Felhasználók adminisztrálása', + 'list_all_users' => 'Összes felhasználó', + 'all_users' => 'Összes felhasználó', + 'instance_configuration' => 'Beállítás', + 'firefly_instance_configuration' => 'A Firefly III beállítási lehetőségei', + 'setting_single_user_mode' => 'Egyfelhasználós mód', + 'setting_single_user_mode_explain' => 'By default, Firefly III only accepts one (1) registration: you. This is a security measure, preventing others from using your instance unless you allow them to. Future registrations are blocked. When you uncheck this box, others can use your instance as well, assuming they can reach it (when it is connected to the internet).', + 'store_configuration' => 'Beállítás tárolása', + 'single_user_administration' => ':email felhasználó adminisztrációja', + 'edit_user' => ':email felhasználó szerkesztése', + 'hidden_fields_preferences' => 'További tranzakciós lehetőségeket a beállításokban lehet engedélyezni.', + 'user_data_information' => 'Felhasználói adatok', + 'user_information' => 'Felhasználó információk', + 'total_size' => 'teljes méret', + 'budget_or_budgets' => 'költségkeretek', + 'budgets_with_limits' => 'költségkeretek beállított összeggel', + 'nr_of_rules_in_total_groups' => ':count_rules szabály :count_groups szabálycsoportban', + 'tag_or_tags' => 'címkék', + 'configuration_updated' => 'A konfiguráció frissítése megtörtént', + 'setting_is_demo_site' => 'Bemutató oldal', + 'setting_is_demo_site_explain' => 'Ha bejelölt, a telepítés bemutató oldalként fog viselkedni, aminek furcsa mellékhatásai lehetnek.', + 'block_code_bounced' => 'Visszapattant email üzenetek', + 'block_code_expired' => 'A Demo számla lejárt', + 'no_block_code' => 'Nincs oka a zárolásnak, vagy a felhasználó nincs zárolva', + 'block_code_email_changed' => 'A felhasználó még nem erősített meg az új email címet', + 'admin_update_email' => 'A profil oldallal ellentétben a felhasználó NEM kap értesítést az email címe megváltozásáról!', + 'update_user' => 'Felhasználó frissítése', + 'updated_user' => 'Felhasználói adatok módosítva.', + 'delete_user' => 'Felhasználó törlése ":email"', + 'user_deleted' => 'A felhasználó törölve lett', + 'send_test_email' => 'Teszt email küldése', + 'send_test_email_text' => 'To see if your installation is capable of sending email, please press this button. You will not see an error here (if any), the log files will reflect any errors. You can press this button as many times as you like. There is no spam control. The message will be sent to :email and should arrive shortly.', + 'send_message' => 'Üzenet küldése', + 'send_test_triggered' => 'Teszt elindítva. Ellenőrizd a bejövő üzeneteidet és a naplófájlokat.', + + 'split_transaction_title' => 'Felosztott tranzakció leírása', + 'split_title_help' => 'Felosztott tranzakció létrehozásakor meg kell adni egy globális leírást a tranzakció összes felosztása részére.', + 'transaction_information' => 'Tranzakció információ', + 'you_create_transfer' => 'Egy átvezetést hozol létre.', + 'you_create_withdrawal' => 'Egy költséget hozol létre.', + 'you_create_deposit' => 'Egy bevételt hozol létre.', + + + // links + 'journal_link_configuration' => 'Tranzakciós kapcsolatok beállítása', + 'create_new_link_type' => 'Új kapcsolattípus létrehozása', + 'store_new_link_type' => 'Új kapcsolattípus tárolása', + 'update_link_type' => 'Kapcsolattípus frissítése', + 'edit_link_type' => '":name" kapcsolattípus szerkesztése', + 'updated_link_type' => '":name" kapcsolattípus frissítve', + 'delete_link_type' => '":name" kapcsolattípus törlése', + 'deleted_link_type' => '":name" kapcsolattípus törölve', + 'stored_new_link_type' => '":name" új kapcsolattípus tárolása', + 'cannot_edit_link_type' => '":name" kapcsolattípus nem szerkeszthető', + 'link_type_help_name' => 'Pl. "Duplikátumok"', + 'link_type_help_inward' => 'Pl. "duplikátumok"', + 'link_type_help_outward' => 'Pl. "duplikálta"', + 'save_connections_by_moving' => 'Ezen tranzakciók közötti kapcsolat mentése másik kapcsolattípusba mozgatással:', + 'do_not_save_connection' => '(do not save connection)', + 'link_transaction' => 'Tranzakció kapcsolása', + 'link_to_other_transaction' => 'Tranzakció hozzákapcsolása egy másik tranzakcióhoz', + 'select_transaction_to_link' => 'Select a transaction to link this transaction to. The links are currently unused in Firefly III (apart from being shown), but I plan to change this in the future. Use the search box to select a transaction either by title or by ID. If you want to add custom link types, check out the administration section.', + 'this_transaction' => 'Ez a tranzakció', + 'transaction' => 'Tranzakció', + 'comments' => 'Megjegyzések', + 'link_notes' => 'Any notes you wish to store with the link.', + 'invalid_link_selection' => 'Ezeket a tranzakciókat nem lehet összekapcsolni', + 'selected_transaction' => 'Selected transaction', + 'journals_linked' => 'Tranzakciók összekapcsolva.', + 'journals_error_linked' => 'Ezek a tranzakciók már össze vannak kapcsolva.', + 'journals_link_to_self' => 'Tranzakciót nem lehet saját magához csatolni', + 'journal_links' => 'Tranzakció összekapcsolások', + 'this_withdrawal' => 'Ez a költség', + 'this_deposit' => 'Ez a bevétel', + 'this_transfer' => 'Ez az átvezetés', + 'overview_for_link' => '":name" kapcsolattípus áttekintése', + 'source_transaction' => 'Forrás tranzakció', + 'link_description' => 'Kapcsolat leírása', + 'destination_transaction' => 'Cél tranzakció', + 'delete_journal_link' => ':source és :destination közötti kapcsolat törlése', + 'deleted_link' => 'Törölt kapcsolat', + + // link translations: + 'Paid_name' => 'Fizetve', + 'Refund_name' => 'Visszatérítés', + 'Reimbursement_name' => 'Visszafizetés', + 'Related_name' => 'Kapcsolódó', + 'relates to_inward' => 'kapcsolódó', + 'is (partially) refunded by_inward' => '(részben) visszatérítette', + 'is (partially) paid for by_inward' => '(részben) kifizette', + 'is (partially) reimbursed by_inward' => '(részben) visszafizette', + 'inward_transaction' => 'Bejövő tranzakciók', + 'outward_transaction' => 'Kimenő tranzakciók', + 'relates to_outward' => 'kapcsolódik', + '(partially) refunds_outward' => '(részben) visszatérítve', + '(partially) pays for_outward' => '(partially) pays for', + '(partially) reimburses_outward' => '(részben) visszafizetve', + + // split a transaction: + 'splits' => 'Felosztások', + 'add_another_split' => 'Másik felosztás hozzáadása', + 'split-transactions' => 'Tranzakciók felosztása', + 'do_split' => 'Felosztás', + 'split_this_withdrawal' => 'Költség felosztása', + 'split_this_deposit' => 'Bevétel felosztása', + 'split_this_transfer' => 'Átvezetés felosztása', + 'cannot_edit_opening_balance' => 'Nem lehet szerkeszteni egy bankszámla nyitóegyenlegét.', + 'no_edit_multiple_left' => 'Nincs kiválasztva érvényes tranzakció a szerkesztéshez.', + 'breadcrumb_convert_group' => 'Convert transaction', + 'convert_invalid_source' => 'Source information is invalid for transaction #%d.', + 'convert_invalid_destination' => 'Destination information is invalid for transaction #%d.', + + // Import page (general strings only) + 'import_index_title' => 'Tranzakciók importálása a Firefly III-ba', + 'import_data' => 'Adatimport', + 'import_transactions' => 'Tranzakciók importálása', + + // sandstorm.io errors and messages: + 'sandstorm_not_available' => 'Ez a funkció nem elérhető ha a Firefly III Sandstorm.io környezetben van használva.', + + // empty lists? no objects? instructions: + 'no_accounts_title_asset' => 'Ideje létrehozni egy eszközszámlát!', + 'no_accounts_intro_asset' => 'Még nincsenek eszközszámlák. Az eszközszámlák a fő számlák: folyószámla, megtakarítási számlák, megosztott számlák vagy akár hitelkártya.', + 'no_accounts_imperative_asset' => 'A Firefly III használatának megkezdéséhez létre kell hozni legalább egy eszközszámlát. Csináljuk meg most:', + 'no_accounts_create_asset' => 'Egy eszközszámla létrehozása', + 'no_accounts_title_expense' => 'Ideje létrehozni egy költségszámlát!', + 'no_accounts_intro_expense' => 'Még nincs költségszámlád. A költségszámlák azok a helyek, ahol pénzköltés történik, például üzletek és szupermarketek.', + 'no_accounts_imperative_expense' => 'A költségszámlák automatikusan jönnek létre tranzakciók létrehozásakor, de kézzel is létre lehet hozni. Hozzunk létre egyet most:', + 'no_accounts_create_expense' => 'Költségszámla létrehozása', + 'no_accounts_title_revenue' => 'Ideje létrehozni egy jövedelemszámlát!', + 'no_accounts_intro_revenue' => 'Még nincsenek jövedelemszámlák. A jövedelemszámlák azok a helyek, ahonnan pénz jön, ilyen például a munkáltató.', + 'no_accounts_imperative_revenue' => 'A jövedelemszámlák automatikusan létrejönnek tranzakciók létrehozásakor, de ha szükséges, létre lehet hozni manuálisan is. Létrehozás most:', + 'no_accounts_create_revenue' => 'Új jövedelemszámla létrehozása', + 'no_accounts_title_liabilities' => 'Ideje létrehozni egy kötelezettséget!', + 'no_accounts_intro_liabilities' => 'Még nincsenek kötelezettségek. A kötelezettségek azok a számlák amik a (diák)hiteleket és egyéb adósságokat regisztrálják.', + 'no_accounts_imperative_liabilities' => 'Nem kötelező használni ezt a funkciót, de hasznos lehet ha szükséges követni ezeket a dolgokat.', + 'no_accounts_create_liabilities' => 'Új kötelezettség létrehozása', + 'no_budgets_title_default' => 'Ideje létrehozni egy költségkeretet', + 'no_budgets_intro_default' => 'Még nincsenek költségkeretek. A költségkeretek arra szolgálnak, hogy a költségeket logikai csoportokba szervezhessük, amelyekhez a költségek csökkentéséhez megadható egy átléphető felső határ.', + 'no_budgets_imperative_default' => 'A költségkeretek a pénzügyi menedzsment alapvető eszközei. Létrehozás most:', + 'no_budgets_create_default' => 'Költségkeret létrehozása', + 'no_categories_title_default' => 'Ideje létrehozni egy kategóriát!', + 'no_categories_intro_default' => 'Még nincsenek kategóriák. A kategóriákat a tranzakciók finomhangolására, kijelölt kategóriákkal történő megjelölésére használják.', + 'no_categories_imperative_default' => 'A kategóriák automatikusan létrejönnek tranzakciók létrehozásakor, de ha szükséges, létre lehet hozni manuálisan is. Létrehozás most:', + 'no_categories_create_default' => 'Kategória létrehozása', + 'no_tags_title_default' => 'Ideje létrehozni egy címkét!', + 'no_tags_intro_default' => 'Még nincsenek címkék. A címkéket a tranzakciók finomhangolására, speciális kulcsszavakkal történő megjelölésére használják.', + 'no_tags_imperative_default' => 'A címkék automatikusan létrejönnek tranzakciók létrehozásakor, de ha szükséges, létre lehet hozni manuálisan is. Létrehozás most:', + 'no_tags_create_default' => 'Címke létrehozása', + 'no_transactions_title_withdrawal' => 'Ideje létrehozni egy költséget!', + 'no_transactions_intro_withdrawal' => 'Még nincsenek költségek. A pénzügyek kezeléséhez létre kell hozni költségeket.', + 'no_transactions_imperative_withdrawal' => 'Pénzt költöttél? Akkor le kellene írni:', + 'no_transactions_create_withdrawal' => 'Költség létrehozása', + 'no_transactions_title_deposit' => 'Hozzunk létre bevételt!', + 'no_transactions_intro_deposit' => 'Még nincs rögzített bevétel. A pénzügyek kezeléséhez létre kell hozni bevételi bejegyzéseket.', + 'no_transactions_imperative_deposit' => 'Pénzhez jutottál? Akkor le kellene írni:', + 'no_transactions_create_deposit' => 'Új bevétel létrehozása', + 'no_transactions_title_transfers' => 'Ideje létrehozni egy átvezetést!', + 'no_transactions_intro_transfers' => 'Még nincsenek átvezetések. Eszközszámlák közötti pénzmozgás átvezetésként lesz rögzítve.', + 'no_transactions_imperative_transfers' => 'Pénzt költöttél? Akkor le kellene írni:', + 'no_transactions_create_transfers' => 'Átvezetés létrehozása', + 'no_piggies_title_default' => 'Ideje létrehozni egy malacperselyt!', + 'no_piggies_intro_default' => 'Még nincsenek malacperselyek. Létre lehet hozni malacperselyeket a megtakarítások felosztásához és a megtakarítások nyomon követéséhez.', + 'no_piggies_imperative_default' => 'Van valami, amire pénzt kellene megtakarítani? Hozz létre egy malacperselyt és kövesd nyomon:', + 'no_piggies_create_default' => 'Új malacpersely létrehozása', + 'no_bills_title_default' => 'Ideje létrehozni egy számlát!', + 'no_bills_intro_default' => 'Még nincsenek számlák. Létre lehet hozni számlákat az olyan rendszeres kiadások nyomon követéséhez mint például a bérleti vagy biztosítási díjak.', + 'no_bills_imperative_default' => 'Vannak rendszeres számlák? Létre lehet hozni egy számlát és nyomon követni a kifizetéseket:', + 'no_bills_create_default' => 'Számla létrehozása', + + // recurring transactions + 'recurrences' => 'Ismétlődő tranzakciók', + 'recurring_calendar_view' => 'Naptár', + 'no_recurring_title_default' => 'Hozzunk létre egy ismétlődő tranzakciót!', + 'no_recurring_intro_default' => 'Még nincsenek ismétlődő tranzakciók. Ezek használatával a Firefly III automatikusan létrehozza a tranzakciókat.', + 'no_recurring_imperative_default' => 'Ez egy elég fejlett funkció ami rendkívül hasznos tud lenni. Erősen ajánlott elolvasni a dokumentációt (?) ikon a jobb felső sarokban) a folytatás előtt.', + 'no_recurring_create_default' => 'Ismétlődő tranzakció létrehozása', + 'make_new_recurring' => 'Ismétlődő tranzakció létrehozása', + 'recurring_daily' => 'Mindennap', + 'recurring_weekly' => 'Minden héten ekkor: :weekday', + 'recurring_monthly' => 'Minden hónap ezen napján: :dayOfMonth', + 'recurring_ndom' => 'Minden hónapban ekkor: :dayOfMonth :weekday', + 'recurring_yearly' => 'Minden évben ekkor: :date', + 'overview_for_recurrence' => '":title" ismétlődő tranzakció áttekintése', + 'warning_duplicates_repetitions' => 'Ritka esetben a dátumok kétszer jelennek meg a listában. Ez akkor fordulhat elő amikor több ismétlődés ütközik egymással. A Firefly III minden esetben csak egy tranzakciót hoz létre naponta.', + 'created_transactions' => 'Kapcsolódó tranzakciók', + 'expected_withdrawals' => 'Várható költségek', + 'expected_deposits' => 'Várható bevételek', + 'expected_transfers' => 'Várható átvezetések', + 'created_withdrawals' => 'Létrehozott költségek', + 'created_deposits' => 'Létrehozott bevételek', + 'created_transfers' => 'Átvezetések létrehozva', + 'created_from_recurrence' => 'Létrehozva ":title" (#:id) ismétlődő tranzakcióból', + 'recurring_never_cron' => 'It seems the cron job that is necessary to support recurring transactions has never run. This is of course normal when you have just installed Firefly III, but this should be something to set up as soon as possible. Please check out the help-pages using the (?)-icon in the top right corner of the page.', + 'recurring_cron_long_ago' => 'It looks like it has been more than 36 hours since the cron job to support recurring transactions has fired for the last time. Are you sure it has been set up correctly? Please check out the help-pages using the (?)-icon in the top right corner of the page.', + + 'recurring_meta_field_tags' => 'Címkék', + 'recurring_meta_field_notes' => 'Megjegyzések', + 'recurring_meta_field_bill_id' => 'Számla', + 'recurring_meta_field_piggy_bank_id' => 'Malacpersely', + 'create_new_recurrence' => 'Új ismétlődő tranzakció létrehozása', + 'help_first_date' => 'Az első várható ismétlést jelöli. A jövőben kell lennie.', + 'help_first_date_no_past' => 'Az első várható ismétlést jelöli. A Firefly III nem fog tranzakciókat létrehozni a múltban.', + 'no_currency' => '(nincs pénznem)', + 'mandatory_for_recurring' => 'Kötelező ismétlődési információ', + 'mandatory_for_transaction' => 'Kötelező tranzakció információ', + 'optional_for_recurring' => 'Nem kötelező ismétlődési információ', + 'optional_for_transaction' => 'Nem kötelező tranzakció információ', + 'change_date_other_options' => 'További lehetőségek megtekintéséhez módosítani kell az "első dátum"-ot.', + 'mandatory_fields_for_tranaction' => 'Az itteni értékek a létrehozott tranzakciókba kerülnek', + 'click_for_calendar' => 'Ide kattintva egy naptár jelenik meg ami megmutatja, hogy a tranzakció mikor lesz megismételve.', + 'repeat_forever' => 'Folyamatos ismétlés', + 'repeat_until_date' => 'Ismétlés egy időpontig', + 'repeat_times' => 'Ismétlés többször', + 'recurring_skips_one' => 'Every other', + 'recurring_skips_more' => ':count előfordulás kihagyása', + 'store_new_recurrence' => 'Ismétlődő tranzakció letárolása', + 'stored_new_recurrence' => '":title" ismétlődő tranzakció sikeresen letárolva.', + 'edit_recurrence' => '":title" ismétlődő tranzakció szerkesztése', + 'recurring_repeats_until' => 'Ismétlés :date-ig', + 'recurring_repeats_forever' => 'Folyamatos ismétlés', + 'recurring_repeats_x_times' => 'Ismétlés ennyiszer: :count', + 'update_recurrence' => 'Ismétlődő tranzakció frissítése', + 'updated_recurrence' => '":title" ismétlődő tranzakció frissítve', + 'recurrence_is_inactive' => 'Ez az ismétlődő tranzakció nem aktív, nem fog új tranzakciókat létrehozni.', + 'delete_recurring' => '":title" ismétlődő tranzakció törlése', + 'new_recurring_transaction' => 'Új ismétlődő tranzakció', + 'help_weekend' => 'Mit tegyen a Firefly III ha az ismétlődő tranzakció szombatra vagy vasárnapra esik?', + 'do_nothing' => 'Hozza létre a tranzakciót', + 'skip_transaction' => 'Előfordulás kihagyása', + 'jump_to_friday' => 'A tranzakció inkább az előző pénteken jöjjön létre', + 'jump_to_monday' => 'A tranzakció inkább az következő hétfőn jöjjön létre', + 'will_jump_friday' => 'Pénteken lesz létrehozva hétvége helyett.', + 'will_jump_monday' => 'Hétfőn lesz létrehozva hétvége helyett.', + 'except_weekends' => 'Kivéve hétvégék', + 'recurrence_deleted' => '":title" ismétlődő tranzakció törölve', + + // new lines for summary controller. + 'box_balance_in_currency' => 'Egyenleg (:currency)', + 'box_spent_in_currency' => 'Költés (:currency)', + 'box_earned_in_currency' => 'Megkeresett (:currency)', + 'box_bill_paid_in_currency' => 'Fizetett számlák (:currency)', + 'box_bill_unpaid_in_currency' => 'Fizetetlen számlák (:currency)', + 'box_left_to_spend_in_currency' => 'Elkölthető (:currency)', + 'box_net_worth_in_currency' => 'Nettó érték (:currency)', + 'box_spend_per_day' => 'Naponta elkölthető: :amount', + +]; diff --git a/resources/lang/hu_HU/form.php b/resources/lang/hu_HU/form.php new file mode 100644 index 0000000000..e7594f01bc --- /dev/null +++ b/resources/lang/hu_HU/form.php @@ -0,0 +1,259 @@ +. + */ + +declare(strict_types=1); + +return [ + // new user: + 'bank_name' => 'Bank neve', + 'bank_balance' => 'Egyenleg', + 'savings_balance' => 'Megtakarítási egyenleg', + 'credit_card_limit' => 'Hitelkártya limit', + 'automatch' => 'Automatikus egyezés', + 'skip' => 'Kihagyás', + 'enabled' => 'Engedélyezett', + 'name' => 'Név', + 'active' => 'Aktív', + 'amount_min' => 'Minimális összeg', + 'amount_max' => 'Maximális összeg', + 'match' => 'Találatok', + 'strict' => 'Szigorú mód', + 'repeat_freq' => 'Ismétlődések', + 'journal_currency_id' => 'Pénznem', + 'currency_id' => 'Pénznem', + 'transaction_currency_id' => 'Pénznem', + 'external_ip' => 'A szervered külső IP címe', + 'attachments' => 'Mellékletek', + 'journal_amount' => 'Összeg', + 'journal_source_name' => 'Jövedelemszámla (forrás)', + 'keep_bill_id' => 'Számla', + 'journal_source_id' => 'Költségszámla (forrás)', + 'BIC' => 'BIC', + 'verify_password' => 'Jelszóbiztonság ellenőrzése', + 'source_account' => 'Forrás bankszámla', + 'destination_account' => 'Cél bankszámla', + 'journal_destination_id' => 'Költségszámla (cél)', + 'asset_destination_account' => 'Célszámla', + 'include_net_worth' => 'Befoglalva a nettó értékbe', + 'asset_source_account' => 'Forrás számla', + 'journal_description' => 'Leírás', + 'note' => 'Megjegyzések', + 'store_new_transaction' => 'Új tranzakció letárolása', + 'split_journal' => 'Tranzakció felosztása', + 'split_journal_explanation' => 'A tranzakció több részre osztása', + 'currency' => 'Pénznem', + 'account_id' => 'Eszközszámla', + 'budget_id' => 'Költségkeret', + 'opening_balance' => 'Nyitó egyenleg', + 'tagMode' => 'Címke mód', + 'tag_position' => 'Címke helye', + 'virtual_balance' => 'Virtuális egyenleg', + 'targetamount' => 'Cél összeg', + 'account_role' => 'Bankszámla szerepköre', + 'opening_balance_date' => 'Nyitó egyenleg dátuma', + 'cc_type' => 'Hitelkártya fizetési terv', + 'cc_monthly_payment_date' => 'Hitelkártya havi fizetés dátuma', + 'piggy_bank_id' => 'Malacpersely', + 'returnHere' => 'Visszatérés ide', + 'returnHereExplanation' => 'A tárolás után térjen vissza ide új létrehozásához.', + 'returnHereUpdateExplanation' => 'Frissítés után térjen vissza ide.', + 'description' => 'Leírás', + 'expense_account' => 'Költségszámla', + 'revenue_account' => 'Jövedelemszámla', + 'decimal_places' => 'Tizedes jel helye', + 'exchange_rate_instruction' => 'Külföldi pénznemek', + 'source_amount' => 'Összeg (forrás)', + 'destination_amount' => 'Összeg (cél)', + 'native_amount' => 'Natív összeg', + 'new_email_address' => 'Új email cím', + 'verification' => 'Ellenőrzés', + 'api_key' => 'API-kulcs', + 'remember_me' => 'Emlékezzen rám', + 'liability_type_id' => 'Kötelezettség típusa', + 'interest' => 'Kamat', + 'interest_period' => 'Kamatperiódus', + + 'source_account_asset' => 'Forrásszámla (eszközszámla)', + 'destination_account_expense' => 'Célszámla (költségszámla)', + 'destination_account_asset' => 'Célszámla (eszközszámla)', + 'source_account_revenue' => 'Forrásszámla (jövedelemszámla)', + 'type' => 'Típus', + 'convert_Withdrawal' => 'Költség konvertálása', + 'convert_Deposit' => 'Bevétel konvertálása', + 'convert_Transfer' => 'Átvezetés konvertálása', + + 'amount' => 'Összeg', + 'foreign_amount' => 'Foreign amount', + 'existing_attachments' => 'Meglévő mellékletek', + 'date' => 'Dátum', + 'interest_date' => 'Kamatfizetési időpont', + 'book_date' => 'Könyvelés dátuma', + 'process_date' => 'Feldolgozás dátuma', + 'category' => 'Kategória', + 'tags' => 'Címkék', + 'deletePermanently' => 'Végleges törlés', + 'cancel' => 'Mégse', + 'targetdate' => 'Céldátum', + 'startdate' => 'Kezdő dátum', + 'tag' => 'Címke', + 'under' => 'Alatt', + 'symbol' => 'Szimbólum', + 'code' => 'Kód', + 'iban' => 'IBAN', + 'account_number' => 'Számlaszám', + 'creditCardNumber' => 'Hitelkártyaszám', + 'has_headers' => 'Fejlécek', + 'date_format' => 'Dátumformátum', + 'specifix' => 'Bank- vagy fájlspecifikus javítások', + 'attachments[]' => 'Mellékletek', + 'store_new_withdrawal' => 'Új költség tárolása', + 'store_new_deposit' => 'Új bevétel eltárolása', + 'store_new_transfer' => 'Új átvezetés létrehozása', + 'add_new_withdrawal' => 'Új költség hozzáadása', + 'add_new_deposit' => 'Új bevétel hozzáadása', + 'add_new_transfer' => 'Egy új átvezetés létrehozása', + 'title' => 'Cím', + 'notes' => 'Megjegyzések', + 'filename' => 'Fájlnév', + 'mime' => 'MIME-típus', + 'size' => 'Méret', + 'trigger' => 'Eseményindító', + 'stop_processing' => 'Feldolgozás leállítása', + 'start_date' => 'Tartomány kezdete', + 'end_date' => 'Tartomány vége', + 'include_attachments' => 'Tartalmazza a feltöltött mellékleteket', + 'include_old_uploads' => 'Tartalmazza az importált adatot', + 'delete_account' => '":name" bankszámla törlése', + 'delete_bill' => '":name" számla törlése', + 'delete_budget' => '":name" költségkeret törlése', + 'delete_category' => '":name" kategória törlése', + 'delete_currency' => '":name" pénznem törlése', + 'delete_journal' => '":description" tranzakció törlése', + 'delete_attachment' => '":name" melléklet törlése', + 'delete_rule' => '":title" szabály törlése', + 'delete_rule_group' => '":title" szabálycsoport törlése', + 'delete_link_type' => '":name" kapcsolattípus törlése', + 'delete_user' => '":email" felhasználó törlése', + 'delete_recurring' => '":title" ismétlődő tranzakció törlése', + 'user_areYouSure' => '":email" felhasználó törlésével minden el fog veszni. Nincs visszavonás, helyreállítás vagy ezekhez hasonló. A saját felhasználó törlésével elveszik a hozzáférés a Firefly III ezen példányához.', + 'attachment_areYouSure' => '":name" melléklet biztosan törölhető?', + 'account_areYouSure' => '":name" bankszámla biztosan törölhető?', + 'bill_areYouSure' => '":name" számla biztosan törölhető?', + 'rule_areYouSure' => '":title" szabály biztosan törölhető?', + 'ruleGroup_areYouSure' => '":title" szabálycsoportot biztosan törölhető?', + 'budget_areYouSure' => '":name" költségkeretet biztosan törölhető?', + 'category_areYouSure' => '":name" kategória biztosan törölhető?', + 'recurring_areYouSure' => ':title ismétlődő tranzakció biztosan törölhető?', + 'currency_areYouSure' => '":name" pénznem biztosan törölhető?', + 'piggyBank_areYouSure' => '":name" malacpersely biztosan törölhető?', + 'journal_areYouSure' => '":description" tranzakció biztosan törölhető?', + 'mass_journal_are_you_sure' => 'Ezek a tranzakciók biztosan törölhetőek?', + 'tag_areYouSure' => '":tag" címke biztosan törölhető?', + 'journal_link_areYouSure' => ':source és :destination közötti kapcsolat biztosan törölhető?', + 'linkType_areYouSure' => '":name" (":inward" / ":outward") kapcsolattípus biztosan törölhető?', + 'permDeleteWarning' => 'A Firefly III-ból történő törlés végleges és nem vonható vissza.', + 'mass_make_selection' => 'A jelölőnégyzet eltávolításával megakadályozható az elemek törlése.', + 'delete_all_permanently' => 'Kijelöltek végleges törlése', + 'update_all_journals' => 'A tranzakciók frissítése', + 'also_delete_transactions' => 'A bankszámlához tartozó egyetlen tranzakció is törölve lesz. | Az ehhez a bankszámlához tartozó :count tranzakció is törölve lesz.', + 'also_delete_connections' => 'A csak ezzel a kapcsolattípussal rendelkező tranzakciók elveszítik az összerendelésüket. | Mind a :count tranzakció, amely ezzel a hivatkozástípussal kapcsolódik elveszíti az összrendelését.', + 'also_delete_rules' => 'A szabálycsoporthoz tartozó egyetlen szabály is törölve lesz. | Az ezen szabálycsoporthoz tartozó :count szabály is törölve lesz.', + 'also_delete_piggyBanks' => 'A bankszámlához tartozó egyetlen malacpersely is törölve lesz. | Az ehhez a bankszámlához tartozó :count malacpersely is törölve lesz.', + 'bill_keep_transactions' => 'A számlához tartozó egyetlen tranzakció nem lesz törölve.|Az ehhez a számlához tartozó :count tranzakció nem lesz törölve.', + 'budget_keep_transactions' => 'A költségkerethez tartozó egyetlen tranzakció nem lesz törölve.|Az ehhez a költségkerethez tartozó :count tranzakció nem lesz törölve.', + 'category_keep_transactions' => 'A kategóriához tartozó egyetlen tranzakció nem lesz törölve.|Az ehhez a kategóriához tartozó :count tranzakció nem lesz törölve.', + 'recurring_keep_transactions' => 'Az ismétlődő tranzakció által létrehozott egyetlen tranzakció nem lesz törölve.|Az ismétlődő tranzakció által létrehozott :count tranzakció nem lesz törölve.', + 'tag_keep_transactions' => 'A címkéhez tartozó egyetlen tranzakció nem lesz törölve.|Az ehhez a címkéhez tartozó :count tranzakció nem lesz törölve.', + 'check_for_updates' => 'Frissítések ellenőrzése', + + 'email' => 'Email cím', + 'password' => 'Jelszó', + 'password_confirmation' => 'Jelszó (ismét)', + 'blocked' => 'Letiltott?', + 'blocked_code' => 'Letiltás oka', + 'login_name' => 'Bejelentkezés', + + // import + 'apply_rules' => 'Szabályok alkalmazása', + 'artist' => 'Előadó', + 'album' => 'Album', + 'song' => 'Dal', + + + // admin + 'domain' => 'Tartomány', + 'single_user_mode' => 'Regisztráció tiltása', + 'is_demo_site' => 'Bemutatóoldal', + + // import + 'import_file' => 'Import fájl', + 'configuration_file' => 'Beállítás fájl', + 'import_file_type' => 'Import fájl típusa', + 'csv_comma' => 'Egy vessző (,)', + 'csv_semicolon' => 'Egy pontosvessző (;)', + 'csv_tab' => 'Egy fül (láthatatlan)', + 'csv_delimiter' => 'CSV mezőhatároló', + 'csv_import_account' => 'Alapértelmezés szerinti importálási bankszámla', + 'csv_config' => 'CSV importálás beállítása', + 'client_id' => 'Ügyfélazonosító', + 'service_secret' => 'Service secret', + 'app_secret' => 'App secret', + 'app_id' => 'Alkalmazás azonosító', + 'secret' => 'Titkos kód', + 'public_key' => 'Nyilvános kulcs', + 'country_code' => 'Országkód', + 'provider_code' => 'Bank vagy adatszolgáltató', + 'fints_url' => 'FinTS API URL', + 'fints_port' => 'Port', + 'fints_bank_code' => 'Bank kódja', + 'fints_username' => 'Felhasználónév', + 'fints_password' => 'PIN / Jelszó', + 'fints_account' => 'FinTS fiók', + 'local_account' => 'Firefly III fiók', + 'from_date' => 'Dátumtól', + 'to_date' => 'Dátumig', + + + 'due_date' => 'Lejárati időpont', + 'payment_date' => 'Fizetés dátuma', + 'invoice_date' => 'Számla dátuma', + 'internal_reference' => 'Belső hivatkozás', + 'inward' => 'Belső leírás', + 'outward' => 'Külső leírás', + 'rule_group_id' => 'Szabálycsoport', + 'transaction_description' => 'Tranzakció leírása', + 'first_date' => 'Első dátum', + 'transaction_type' => 'Tranzakció típusa', + 'repeat_until' => 'Ismétlés eddig:', + 'recurring_description' => 'Ismétlődő tranzakció leírása', + 'repetition_type' => 'Ismétlődés típusa', + 'foreign_currency_id' => 'Foreign currency', + 'repetition_end' => 'Ismétlés vége', + 'repetitions' => 'Ismétlések', + 'calendar' => 'Naptár', + 'weekend' => 'Hétvége', + 'client_secret' => 'Ügyfél titkos kódja', + + 'withdrawal_destination_id' => 'Destination account', + 'deposit_source_id' => 'Source account', + +]; diff --git a/resources/lang/hu_HU/import.php b/resources/lang/hu_HU/import.php new file mode 100644 index 0000000000..35cd855c01 --- /dev/null +++ b/resources/lang/hu_HU/import.php @@ -0,0 +1,316 @@ +. + */ + +declare(strict_types=1); + +return [ + // ALL breadcrumbs and subtitles: + 'index_breadcrumb' => 'Adatok importálása a Firefly III-ba', + 'prerequisites_breadcrumb_fake' => 'Előfeltételek az imitált import szolgáltató részére', + 'prerequisites_breadcrumb_spectre' => 'Spectre előfeltételei', + 'prerequisites_breadcrumb_bunq' => 'bunq előfeltételei', + 'prerequisites_breadcrumb_ynab' => 'YNAB bunq előfeltételei', + 'job_configuration_breadcrumb' => 'Konfiguráció ":key"', + 'job_status_breadcrumb' => 'Importálás állapota: ":key"', + 'disabled_for_demo_user' => 'nem érhető el bemutató módban', + + // index page: + 'general_index_intro' => 'Üdvözli a Firefly III importáló eljárása. A Firefly III-ba adatokat több módon is lehet importálni, melyek gombként jelennek meg.', + + // import provider strings (index): + 'button_fake' => 'Importálás imitálása', + 'button_file' => 'Fájl importálása', + 'button_bunq' => 'Import bunq-ból', + 'button_spectre' => 'Importálás Spectre használatával', + 'button_plaid' => 'Importálás Plaid használatával', + 'button_yodlee' => 'Importálás Yodlee használatával', + 'button_quovo' => 'Importálás Quovo használatával', + 'button_ynab' => 'Importálás You Need A Budget-ből', + 'button_fints' => 'Importálás FinTS használatával', + + + // prerequisites box (index) + 'need_prereq_title' => 'Importálás előfeltételei', + 'need_prereq_intro' => 'Néhány importálási mód felhasználói beavatkozást igényel a használata előtt. Például szükség lehet különleges API kulcsokra vagy titkos kódokra. Ezeket itt lehet beállítani. Az ikon jelzi, hogy teljesültek-e ezek az előfeltételek.', + 'do_prereq_fake' => 'Az imitálás szolgáltató előfeltételei', + 'do_prereq_file' => 'Fájl import előfeltételei', + 'do_prereq_bunq' => 'Importálás bunqból előfeltételei', + 'do_prereq_spectre' => 'A Spectre használatával történő importálás előfeltételei', + 'do_prereq_plaid' => 'A Plaid használatával történő importálás előfeltételei', + 'do_prereq_yodlee' => 'A Yodlee használatával történő importálás előfeltételei', + 'do_prereq_quovo' => 'A Quovo használatával történő importálás előfeltételei', + 'do_prereq_ynab' => 'Az YNAB használatával történő importálás előfeltételei', + + // prerequisites: + 'prereq_fake_title' => 'Importálás előfeltételei az imitált import szolgáltatótól', + 'prereq_fake_text' => 'Az imitált szolgáltatónak szüksége van egy hamis API kulcsra. Ennek 32 karakter hosszúnak kell lennie. Például lehet ez: 123456789012345678901234567890AA', + 'prereq_spectre_title' => 'A Spectre API használatával történő importálás előfeltételei', + 'prereq_spectre_text' => 'A Spectre API (v4) használatával történő importáláshoz két titkos értéket kell megadni a Firefly III-nak. Ezek a titkos kódok oldalon találhatóak.', + 'prereq_spectre_pub' => 'A Spectre API-nak a lenti nyilvános kulcsra is szüksége van. Enélkül nem fog felismerni téged. A nyilvános kulcsot a titkos kódok oldalon kell megadni.', + 'prereq_bunq_title' => 'Az bunq használatával történő importálás előfeltételei', + 'prereq_bunq_text' => 'A bunqból történő importáláshoz be kell szerezni egy API kulcsot. Ezt az alkalmazással lehet megtenni. Fontos tudni, hogy a bunq import funkció BETA állapotú. Csaka korlátozott API-val lett tesztelve.', + 'prereq_bunq_ip' => 'A bunqnak szüksége van a kívülről látható IP címre. A Firefly III megpróbálja kitölteni az ipify szolgáltatás használatával. Ellenőrizni kell, hogy jó-e az IP cím vagy különben az importálás nem fog sikerülni.', + 'prereq_ynab_title' => 'Az YNAB használatával történő importálás előfeltételei', + 'prereq_ynab_text' => 'Ahhoz, hogy a YNAB-ből tranzakciókat lehessen letölteni egy új alkalmazást kell létrehozni a Developer Settings Page oldalon, majd az ügyfél azonosítót és a titkos kódot fel kell venni ezen az oldalon.', + 'prereq_ynab_redirect' => 'A beállítás befejezéséhez meg kell adni a következő URL-t a Developer Settings Page oldalon a "Redirect URI(s)" alatt.', + 'callback_not_tls' => 'Firefly III has detected the following callback URI. It seems your server is not set up to accept TLS-connections (https). YNAB will not accept this URI. You may continue with the import (because Firefly III could be wrong) but please keep this in mind.', + // prerequisites success messages: + 'prerequisites_saved_for_fake' => 'Hamis API kulcs sikeres eltárolva!', + 'prerequisites_saved_for_spectre' => 'Alkalmazás azonosító és titkos kód eltárolva!', + 'prerequisites_saved_for_bunq' => 'API kulcs és IP eltárolva!', + 'prerequisites_saved_for_ynab' => 'YNAB kliens azonosító és titkos kód eltárolva!', + + // job configuration: + 'job_config_apply_rules_title' => 'Feladat beállítása - szabályok alkalmazása?', + 'job_config_apply_rules_text' => 'Ha már fut az imitált szolgáltató, a szabályok alkalmazhatóak lesznek a tranzakciókon. Ez megnöveli az importálás idejét.', + 'job_config_input' => 'A bemenet', + // job configuration for the fake provider: + 'job_config_fake_artist_title' => 'Album nevének megadása', + 'job_config_fake_artist_text' => 'Több import rutinnál el kell végezni néhány beállítást. Az imitált import szolgáltató használatakor néhány furcsa kérdésre kell válaszolni. Ebben az esetben a folytatáshoz ezt kell beírni: David Bowie.', + 'job_config_fake_song_title' => 'Dal nevének megadása', + 'job_config_fake_song_text' => 'A "Golden years" dallal lehet folytatni az imitált importot.', + 'job_config_fake_album_title' => 'Album nevének megadása', + 'job_config_fake_album_text' => 'Több import rutin számára a folyamat közben további adatokat kell megadni. Az imitált import szolgáltató használatakor néhány furcsa kérdésre kell válaszolni. Ebben az esetben a folytatáshoz ezt kell beírni: Station to station.', + // job configuration form the file provider + 'job_config_file_upload_title' => 'Importálás beállítása (1/4) - Saját fájl feltöltése', + 'job_config_file_upload_text' => 'Ez a rutin segítséget nyújt fájlok importálásához a bankból a Firefly III-ba. ', + 'job_config_file_upload_help' => 'Fájl kiválasztása. A fájlnak UTF-8 kódolásúnak kell lennie.', + 'job_config_file_upload_config_help' => 'Ha korábban már történt adatimportálás a Firefly III-ba, akkor rendelkezésre áll egy előre beállított értékeket tartalmazó beállítási fájl. Néhány bank esetében más felhasználók nyilvánossá tették a saját beállítási fájljukat', + 'job_config_file_upload_type_help' => 'A feltölteni kívánt fájl típusának kiválasztása', + 'job_config_file_upload_submit' => 'Fájlok feltöltése', + 'import_file_type_csv' => 'CSV (comma separated values - vesszővel elválasztott értékek)', + 'import_file_type_ofx' => 'OFX', + 'file_not_utf8' => 'A feltöltött fájl nem UTF-8 vagy ASCII kódolású. A Firefly III nem tudja kezelni az ilyen fájlokat. A Notepad++ vagy a Sublime segítségével lehet a fájlt UTF-8-ra átkódolni.', + 'job_config_uc_title' => 'Importálás beállítása (2/4) - Alapvető fájl beállítások', + 'job_config_uc_text' => 'A fájl megfelelő importálásához ellenőrizni kell a lenti beállításokat.', + 'job_config_uc_header_help' => 'Be kell jelölni, ha a CSV fájl első sora oszlopcímeket tartalmaz.', + 'job_config_uc_date_help' => 'Dátumformátum a fájlban. Az ezen az oldalon bemutatott formátumot kell követnie. Az alapértelmezett érték az ilyen dátumokat fogja feldolgozni: :dateExample.', + 'job_config_uc_delimiter_help' => 'Ki kell választani a bemeneti fájlban használt mezőelválasztót. Ha nem biztos, hogy melyik, akkor a vessző a legbiztonságosabb választás.', + 'job_config_uc_account_help' => 'Ha a fájl NEM tartalmaz információt az eszközszámlákról, akkor ebből a listából lehet kiválasztani, hogy a fájlban szereplő tranzakciók melyik bankszámlához tartoznak.', + 'job_config_uc_apply_rules_title' => 'Szabályok alkalmazása', + 'job_config_uc_apply_rules_text' => 'A szabályok alkalmazása az összes importált tranzakción. Ez jelentősen lelassítja az importálást.', + 'job_config_uc_specifics_title' => 'Bank specifikus beállítások', + 'job_config_uc_specifics_txt' => 'Néhány bank rosszul formázott fájlokat biztosít. A Firefly III automatikusan ki tudja ezeket javítani. Ha a te bankod ilyen fájlokat biztosít és nincs itt felsorolva, akkor a GitHubon lehet ezt bejelenteni.', + 'job_config_uc_submit' => 'Folytatás', + 'invalid_import_account' => 'Érvénytelen számla lett kiválasztva az importáláshoz.', + 'import_liability_select' => 'Kötelezettség', + // job configuration for Spectre: + 'job_config_spectre_login_title' => 'Válassz bejelentkezést', + 'job_config_spectre_login_text' => 'A Firefly III :count meglévő bejelentkezést találta a Spectre számlához. Melyik legyen az importhoz használva?', + 'spectre_login_status_active' => 'Aktív', + 'spectre_login_status_inactive' => 'Inaktív', + 'spectre_login_status_disabled' => 'Letiltott', + 'spectre_login_new_login' => 'Bejelentkezés másik bankkal vagy ezen bankok egyikével más hitelesítő adatok megadásával.', + 'job_config_spectre_accounts_title' => 'Az importáláshoz használt számlák kiválasztása', + 'job_config_spectre_accounts_text' => '":name" (:country) kiválasztva. Ettől a szolgáltatótól :count számla áll rendelkezésre. Ki kell választani azokat a Firefly III eszközszámlákat amikbe az ezekből a számlákból származó tranzakció tárolni kell. Fontos tudni, hogy az adatok importálásához a Firefly III számlának és ":name"-számlának ugyanazt a pénznemet kell használnia.', + 'spectre_do_not_import' => '(ne importálja)', + 'spectre_no_mapping' => 'Úgy tűnik az importáláshoz nincs számla kiválasztva.', + 'imported_from_account' => 'Innen importálva: ":account"', + 'spectre_account_with_number' => 'Bankszámla száma :number', + 'job_config_spectre_apply_rules' => 'Szabályok alkalmazása', + 'job_config_spectre_apply_rules_text' => 'Alapértelmezés szerint a szabályok alkalmazva lesznek az importálás alatt létrejövő tranzakciókon. Ha erre nincs szükség, ki kell venni a dobozból a jelölést.', + + // job configuration for bunq: + 'job_config_bunq_accounts_title' => 'bunq számlák', + 'job_config_bunq_accounts_text' => 'Ezek a bunq fiókhoz tartozó számlák. Ki kell választani, hogy melyik számlából történjen az importálás és hogy melyik számlába legyenek importálva a tranzakciók.', + 'bunq_no_mapping' => 'Úgy tűnik egy számla sincs kiválasztva.', + 'should_download_config' => 'Ehhez a feladathoz érdemes letölteni a beállítási fájlt. Ez könnyebbé teszi a későbbi importálásokat.', + 'share_config_file' => 'Ha nyilvános bankból importáltál adatokat kérlek oszd meg a beállítási fájlodat ami más felhasználók számára megkönnyíti az adataik importálását. A beállítási fájl megosztása nem fedi fel a pénzügyeid részleteit.', + 'job_config_bunq_apply_rules' => 'Szabályok alkalmazása', + 'job_config_bunq_apply_rules_text' => 'Alapértelmezés szerint a szabályok alkalmazva lesznek az importálás alatt létrejövő tranzakciókon. Ha erre nincs szükség, ki kell venni a dobozból a jelölést.', + 'bunq_savings_goal' => 'Megtakarítási cél :amount (:percentage%)', + 'bunq_account_status_CANCELLED' => 'Zárolt bunq fiók', + + 'ynab_account_closed' => 'Fiók lezárva!', + 'ynab_account_deleted' => 'Fiók törölve!', + 'ynab_account_type_savings' => 'megtakarítási számla', + 'ynab_account_type_checking' => 'fiók ellenőrzése', + 'ynab_account_type_cash' => 'készpénzszámla', + 'ynab_account_type_creditCard' => 'bankkártya', + 'ynab_account_type_lineOfCredit' => 'line of credit', + 'ynab_account_type_otherAsset' => 'egyéb eszközszámla', + 'ynab_account_type_otherLiability' => 'egyéb kötelezettségek', + 'ynab_account_type_payPal' => 'PayPal', + 'ynab_account_type_merchantAccount' => 'kereskedői számla', + 'ynab_account_type_investmentAccount' => 'befektetési számla', + 'ynab_account_type_mortgage' => 'jelzálog', + 'ynab_do_not_import' => '(ne importálja)', + 'job_config_ynab_apply_rules' => 'Szabályok alkalmazása', + 'job_config_ynab_apply_rules_text' => 'Alapértelmezés szerint a szabályok alkalmazva lesznek az importálás alatt létrejövő tranzakciókon. Ha erre nincs szükség, ki kell venni a dobozból a jelölést.', + + // job configuration for YNAB: + 'job_config_ynab_select_budgets' => 'Költségkeret kiválasztása', + 'job_config_ynab_select_budgets_text' => ':count költségkeret van tárolva a YNAB-ben. Ki kell választani, hogy a Firefly III melyikből importálja a tranzakciókat.', + 'job_config_ynab_no_budgets' => 'There are no budgets available to be imported from.', + 'ynab_no_mapping' => 'It seems you have not selected any accounts to import from.', + 'job_config_ynab_bad_currency' => 'Nem lehet importálni a következő költségkeretekből mert nincs olyan számla aminek a pénzneme egyezik ezekkel a költségkeretekkel.', + 'job_config_ynab_accounts_title' => 'Számlák kiválasztása', + 'job_config_ynab_accounts_text' => 'A következő számlák állnak rendelkezésre ebben a költségkeretben. Ki kell választani, hogy mely számlák legyenek importálva és, hogy a tranzakciók hol legyenek eltárolva.', + + + // keys from "extra" array: + 'spectre_extra_key_iban' => 'IBAN', + 'spectre_extra_key_swift' => 'SWIFT', + 'spectre_extra_key_status' => 'Állapot', + 'spectre_extra_key_card_type' => 'Kártyatípus', + 'spectre_extra_key_account_name' => 'Számla neve', + 'spectre_extra_key_client_name' => 'Ügyfél neve', + 'spectre_extra_key_account_number' => 'Számlaszám', + 'spectre_extra_key_blocked_amount' => 'Blokkolt mennyiség', + 'spectre_extra_key_available_amount' => 'Rendelkezésre álló mennyiség', + 'spectre_extra_key_credit_limit' => 'Hitelkeret', + 'spectre_extra_key_interest_rate' => 'Kamatláb', + 'spectre_extra_key_expiry_date' => 'Lejárati dátum', + 'spectre_extra_key_open_date' => 'Nyitás dátuma', + 'spectre_extra_key_current_time' => 'Aktuális idő', + 'spectre_extra_key_current_date' => 'Aktuális dátum', + 'spectre_extra_key_cards' => 'Kártyák', + 'spectre_extra_key_units' => 'Egységek', + 'spectre_extra_key_unit_price' => 'Egységár', + 'spectre_extra_key_transactions_count' => 'Tranzakciók száma', + + //job configuration for finTS + 'fints_connection_failed' => 'Hiba történt a bankhoz történő kapcsolódás során. Le kell ellenőrizni, hogy minden megadott adat megfelelő. Az eredeti hibaüzenet: :originalError', + + 'job_config_fints_url_help' => 'Pl. https://banking-dkb.s-fints-pt-dkb.de/fints30', + 'job_config_fints_username_help' => 'Több banknál ez a számlaszám.', + 'job_config_fints_port_help' => 'Az alapértelmezett port 443.', + 'job_config_fints_account_help' => 'Kérem válaszon egy bankszámlát ahonnan szeretne tranzakciót importálni.', + 'job_config_local_account_help' => 'Ki kell választani a fentebb megadott bankszámlának megfelelő Firefly III számlát.', + // specifics: + 'specific_ing_name' => 'ING NL', + 'specific_ing_descr' => 'Jobb leírások létrehozása az ING exportálásokban', + 'specific_sns_name' => 'SNS / Volksbank NL', + 'specific_sns_descr' => 'Megjegyzések eltávolítása az SNS / Volksbank fájlokból', + 'specific_abn_name' => 'ABN AMRO NL', + 'specific_abn_descr' => 'Javítja az ABN AMRO fájlok lehetséges problémáit', + 'specific_rabo_name' => 'Rabobank NL', + 'specific_rabo_descr' => 'Javítja a Rabobank fájlok lehetséges problémáit', + 'specific_pres_name' => 'President\'s Choice Financial CA', + 'specific_pres_descr' => 'Javítja a PC fájlok lehetséges problémáit', + 'specific_belfius_name' => 'Belfius BE', + 'specific_belfius_descr' => 'Javítja a Belfius fájlok lehetséges problémáit', + 'specific_ingbelgium_name' => 'ING BE', + 'specific_ingbelgium_descr' => 'Fixes potential problems with ING Belgium files', + // job configuration for file provider (stage: roles) + 'job_config_roles_title' => 'Importálási beállítás (3/4) - Egyes oszlopok szerepeinek meghatározása', + 'job_config_roles_text' => 'A CSV fájl minden oszlopa bizonyos adatot tartalmaz. Meg kell adni, hogy az importáló milyen adatokra számíthat. Az adat „hozzárendelése” azt jelenti, hogy az oszlopokban talált adatokat hozzá lehet kötni egy értékhez az adatbázisban. Egy gyakran hozzárendelt oszlop az az oszlop ami az ellenszámla IBAN kódját tartalmazza. Ez mér könnyen megfeleltethető lesz az adatbázisban már szereplő IBAN kódoknak.', + 'job_config_roles_submit' => 'Folytatás', + 'job_config_roles_column_name' => 'Oszlop neve', + 'job_config_roles_column_example' => 'Oszlop példaadat', + 'job_config_roles_column_role' => 'Oszlopadat jelentése', + 'job_config_roles_do_map_value' => 'Értékek hozzárendelése', + 'job_config_roles_no_example' => 'Nincs elérhető példaadat', + 'job_config_roles_fa_warning' => 'If you mark a column as containing an amount in a foreign currency, you must also set the column that contains which currency it is.', + 'job_config_roles_rwarning' => 'Legalább egy oszlopot összeg oszlopként kell megjelölni. Javasolt továbbá kiválasztani egy oszlopot a leírásnak, a dátumnak és az ellenszámlának.', + 'job_config_roles_colum_count' => 'Oszlop', + // job config for the file provider (stage: mapping): + 'job_config_map_title' => 'Importálás beállítása (4/4) - Adatok összekapcsolása a Firefly III adataival', + 'job_config_map_text' => 'A következő táblákban a bal oldali érték a feltöltött fájlban található információkat mutatja. A felhasználó feladata az érték összerendelése egy, az adatbázisban már szereplő értékkel ha lehetséges. A Firefly ragaszkodni fog ehhez az összerendeléshez. Ha nincs érték amihez rendelni lehet, vagy nem szükséges az összerendelés akkor nem kell kiválasztani semmit se.', + 'job_config_map_nothing' => 'Nincs olyan adat a fájlban amit meglévő értékhez lehet rendelni. Folytatás az „Importálás kezdése” gombbal.', + 'job_config_field_value' => 'Mező értéke', + 'job_config_field_mapped' => 'Hozzárendelve', + 'map_do_not_map' => '(nincs hozzárendelés)', + 'job_config_map_submit' => 'Importálás elindítása', + + + // import status page: + 'import_with_key' => 'Importálás \':key\' kulccsal', + 'status_wait_title' => 'Kis türelmet...', + 'status_wait_text' => 'Ez a doboz hamarosan eltűnik.', + 'status_running_title' => 'Az importálás fut', + 'status_job_running' => 'Kérem várjon, az importálás folyamatban van...', + 'status_job_storing' => 'Kérem várjon, adatok tárolása...', + 'status_job_rules' => 'Kérem várjon, szabályok futtatása...', + 'status_fatal_title' => 'Végzetes hiba', + 'status_fatal_text' => 'Az import közben hiba történt amit nem sikerült helyreállítani. Elnézést kérünk!', + 'status_fatal_more' => 'Ezt a (valószínűleg nagyon rejtélyes) hibaüzenetet a merevlemezen, vagy a Firefly III futtatásához használt Docker tárolóban található naplófájlok egészítik ki.', + 'status_finished_title' => 'Az importálás befejeződött', + 'status_finished_text' => 'Az importálás befejeződött.', + 'finished_with_errors' => 'Hibák történtek importálás közben. Alaposan át kell nézni őket.', + 'unknown_import_result' => 'Ismeretlen import eredmény', + 'result_no_transactions' => 'No transactions have been imported. Perhaps they were all duplicates is simply no transactions where present to be imported. Perhaps the log files can tell you what happened. If you import data regularly, this is normal.', + 'result_one_transaction' => 'Pontosan egy tranzakció lett importálva. A :tag címke alatt lett eltárolva ahol később ellenőrizhető.', + 'result_many_transactions' => 'A Firefly III :count tranzakciót importált. A :tag címke alatt lettek eltárolva ahol később ellenőrizhetőek.', + + + // general errors and warnings: + 'bad_job_status' => 'Ennek az oldalnak az eléréséhez az import művelet állapota nem lehet ":status".', + + // column roles for CSV import: + 'column__ignore' => '(oszlop figyelmen kívül hagyása)', + 'column_account-iban' => 'Eszközszámla (IBAN)', + 'column_account-id' => 'Eszközszámla azonosító (FF3-nak megfelelő)', + 'column_account-name' => 'Eszközszámla (név)', + 'column_account-bic' => 'Eszközszámla (BIC)', + 'column_amount' => 'Összeg', + 'column_amount_foreign' => 'Összeg (devizában)', + 'column_amount_debit' => 'Összeg (tartozás oszlop)', + 'column_amount_credit' => 'Összeg (hitel oszlop)', + 'column_amount_negated' => 'Összeg (negált oszlop)', + 'column_amount-comma-separated' => 'Összeg (vessző mint tizedes elválasztó)', + 'column_bill-id' => 'Számla azonosító (FF3-nak megfelelő)', + 'column_bill-name' => 'Számla neve', + 'column_budget-id' => 'Költségkeret azonosító (FF3-nak megfelelő)', + 'column_budget-name' => 'Költségkeret neve', + 'column_category-id' => 'Kategória azonosító (FF3-nak megfelelő)', + 'column_category-name' => 'Kategória neve', + 'column_currency-code' => 'Pénznem kód (ISO 4217)', + 'column_foreign-currency-code' => 'Deviza kód (ISO 4217)', + 'column_currency-id' => 'Pénznem azonosító (FF3-nak megfelelő)', + 'column_currency-name' => 'Pénznem neve (FF3-nak megfelelő)', + 'column_currency-symbol' => 'Pénznem szimbóluma (FF3-nak megfelelő)', + 'column_date-interest' => 'Kamatszámítás dátuma', + 'column_date-book' => 'Tranzakció könyvelési dátuma', + 'column_date-process' => 'Tranzakció feldolgozási dátuma', + 'column_date-transaction' => 'Dátum', + 'column_date-due' => 'Tranzakció esedékessége', + 'column_date-payment' => 'Tranzakció fizetési dátuma', + 'column_date-invoice' => 'Tranzakció számla dátuma', + 'column_description' => 'Leírás', + 'column_opposing-iban' => 'Ellenszámla (IBAN)', + 'column_opposing-bic' => 'Ellenszámla (BIC)', + 'column_opposing-id' => 'Ellenszámla azonosító (FF3-nak megfelelő)', + 'column_external-id' => 'Külső azonosító', + 'column_opposing-name' => 'Ellenszámla (név)', + 'column_rabo-debit-credit' => 'Rabobank specifikus tartozás/hitel indikátor', + 'column_ing-debit-credit' => 'ING specifikus tartozás/hitel indikátor', + 'column_generic-debit-credit' => 'Általános bank specifikus tartozás/hitel indikátor', + 'column_sepa_ct_id' => 'SEPA végpontok közti azonosító', + 'column_sepa_ct_op' => 'SEPA ellenszámla azonosító', + 'column_sepa_db' => 'SEPA megbízás azonosító', + 'column_sepa_cc' => 'SEPA Clearing Code', + 'column_sepa_ci' => 'SEPA hitelező azonosító', + 'column_sepa_ep' => 'SEPA External Purpose', + 'column_sepa_country' => 'SEPA országkód', + 'column_sepa_batch_id' => 'SEPA Batch ID', + 'column_tags-comma' => 'Címkék (vesszővel elválasztva)', + 'column_tags-space' => 'Címkék (szóközzel elválasztva)', + 'column_account-number' => 'Eszközszámla (számlaszám)', + 'column_opposing-number' => 'Ellenszámla (számlaszám)', + 'column_note' => 'Megjegyzések', + 'column_internal-reference' => 'Belső hivatkozás', + + // error message + 'duplicate_row' => 'Row #:row (":description") could not be imported. It already exists.', + +]; diff --git a/resources/lang/hu_HU/intro.php b/resources/lang/hu_HU/intro.php new file mode 100644 index 0000000000..3fd73f08df --- /dev/null +++ b/resources/lang/hu_HU/intro.php @@ -0,0 +1,160 @@ +. + */ + +declare(strict_types=1); + +return [ + // index + 'index_intro' => 'Ez a Firefly III kezdőoldala. Érdemes némi időt szánni ennek a bemutatónak a megtekintésére a Firefly III alapjainak megismeréséhez.', + 'index_accounts-chart' => 'Ezen a grafikon az eszközszámlák aktuális egyenlege látható. A grafikonon megjelenő bankszámlákat a beállításokban lehet kiválasztani.', + 'index_box_out_holder' => 'Ez a kis doboz és a mellette láthatóak gyors áttekintést nyújtanak a pénzügyi helyzetről.', + 'index_help' => 'Ezt a gombot megnyomva lehet segítséget kérni egy oldal vagy egy űrlap használatához.', + 'index_outro' => 'A Firefly III legtöbb oldala egy ilyen rövid bemutatóval kezdődik. Kérdés vagy észrevét esetén szívesen állok rendelkezésre. Kellemes használatot!', + 'index_sidebar-toggle' => 'Az ez alatt az ikon alatt megnyíló menü használható új tranzakciók, bankszámlák vagy egyéb dolgok létrehozásához.', + 'index_cash_account' => 'These are the accounts created so far. You can use the cash account to track cash expenses but it\'s not mandatory of course.', + + // transactions (withdrawal) + 'transactions_create_withdrawal_source' => 'Select your favorite asset account or liability from this dropdown.', + 'transactions_create_withdrawal_destination' => 'Select an expense account here. Leave it empty if you want to make a cash expense.', + 'transactions_create_withdrawal_foreign_currency' => 'Use this field to set a foreign currency and amount.', + 'transactions_create_withdrawal_more_meta' => 'Plenty of other meta data you set in these fields.', + 'transactions_create_withdrawal_split_add' => 'If you want to split a transaction, add more splits with this button', + + // transactions (deposit) + 'transactions_create_deposit_source' => 'Select or type the payee in this auto-completing dropdown/textbox. Leave it empty if you want to make a cash deposit.', + 'transactions_create_deposit_destination' => 'Select an asset or liability account here.', + 'transactions_create_deposit_foreign_currency' => 'Use this field to set a foreign currency and amount.', + 'transactions_create_deposit_more_meta' => 'Plenty of other meta data you set in these fields.', + 'transactions_create_deposit_split_add' => 'If you want to split a transaction, add more splits with this button', + + // transactions (transfer) + 'transactions_create_transfer_source' => 'Select the source asset account here.', + 'transactions_create_transfer_destination' => 'Select the destination asset account here.', + 'transactions_create_transfer_foreign_currency' => 'Use this field to set a foreign currency and amount.', + 'transactions_create_transfer_more_meta' => 'Plenty of other meta data you set in these fields.', + 'transactions_create_transfer_split_add' => 'If you want to split a transaction, add more splits with this button', + + // create account: + 'accounts_create_iban' => 'Érvényes IBAN hozzáadása a számlához. Ez a jövőben nagyon egyszerűvé teheti az adatok importálását.', + 'accounts_create_asset_opening_balance' => 'Az eszközszámlák rendelkezhetnek egy „nyitó egyenleggel” ami a számla történetének kezdetét jelzi a Firefly III-ban.', + 'accounts_create_asset_currency' => 'A Firefly III több pénznemet támogat. Az eszközszámláknak van egy fő pénzneme, amelyet itt kell beállítani.', + 'accounts_create_asset_virtual' => 'Időnként segíthet egy virtuális egyenleget adni a bankszámlához: ez egy további összeg ami az aktuális egyenleghez mindig hozzáadódik vagy kivonásra kerül.', + + // budgets index + 'budgets_index_intro' => 'A költségkeretek a pénzügyek kezelésére szolgálnak, és a Firefly III egyik alapvető funkcióját képezik.', + 'budgets_index_set_budget' => 'Ha a teljes költségkeret minden időszakra be van állítva, a Firefly III megmondhatja, ha az összes rendelkezésre álló pénz fel lett használva.', + 'budgets_index_see_expenses_bar' => 'A pénzköltés lassan fel fogja tölteni ezt a sávot.', + 'budgets_index_navigate_periods' => 'Az időszakokon átnavigálva könnyedén, még idő előtt be lehet állítani a költségkereteket.', + 'budgets_index_new_budget' => 'Új költségkeretet létrehozása.', + 'budgets_index_list_of_budgets' => 'Ezen a táblán lehet beállítani a költségkeretek összegeit és áttekinteni, hogy hogyan állnak.', + 'budgets_index_outro' => 'A költségkeretek használatáról további információk a jobb felső sarokban található súgó ikon alatt találhatóak.', + + // reports (index) + 'reports_index_intro' => 'Ezek a jelentések részletes betekintést biztosítanak a pénzügyekbe.', + 'reports_index_inputReportType' => 'Jelentéstípus kiválasztása. A súgóoldalakon megtalálható, hogy az egyes jelentések mit mutatnak meg.', + 'reports_index_inputAccountsSelect' => 'Szükség szerint lehet kizárni vagy hozzáadni eszközfiókokat.', + 'reports_index_inputDateRange' => 'Tetszőleges dátumtartományt lehet választani, egy naptól 10 évig.', + 'reports_index_extra-options-box' => 'A kiválasztott jelentéstől függően további szűrők és beállítások választhatóak. Ezek ebben a dobozban fognak megjelenni.', + + // reports (reports) + 'reports_report_default_intro' => 'Ez a jelentés egy gyors és átfogó képet ad a pénzügyekről. Ha bármi másnak szerepelni kéne rajta vedd fel velem a kapcsolatot!', + 'reports_report_audit_intro' => 'Ez a jelentés részletes betekintést nyújt az eszközszámlákba.', + 'reports_report_audit_optionsBox' => 'A jelölőnégyzetek használatával lehet megjeleníteni vagy elrejteni az egyes oszlopokat.', + + 'reports_report_category_intro' => 'Ez a jelentés egy vagy több kategóriában nyújt betekintést.', + 'reports_report_category_pieCharts' => 'Ezek a diagramok áttekintést nyújtanak a költségekről és a bevételekről, kategóriánként vagy bankszámlákként.', + 'reports_report_category_incomeAndExpensesChart' => 'Ez a diagram a kategóriákon belüli költségeket és jövedelmeket mutatja.', + + 'reports_report_tag_intro' => 'Ez a jelentés egy vagy több címkébe nyújt betekintést.', + 'reports_report_tag_pieCharts' => 'Ezek a diagramok áttekintést nyújtanak a költségekről és a bevételekről, címkékként, bankszámlákként, kategóriákként vagy költségkeretenként.', + 'reports_report_tag_incomeAndExpensesChart' => 'Ez a diagram a kiadásokat és a bevételeket mutatja címkék szerint.', + + 'reports_report_budget_intro' => 'Ez a jelentés betekintést nyújt egy vagy több költségkeretbe.', + 'reports_report_budget_pieCharts' => 'Ezek a diagramok betekintést nyújtanak a költségekbe költségkeretenként vagy számlánként.', + 'reports_report_budget_incomeAndExpensesChart' => 'Ez a táblázat a költségkeretenkénti költségeket mutatja.', + + // create transaction + 'transactions_create_switch_box' => 'Ezzel a gombbal lehet gyorsan átkapcsolni a menteni kívánt tranzakció típusát.', + 'transactions_create_ffInput_category' => 'Ebbe a mezőbe szabadon lehet gépelni. A korábban létrehozott kategóriák javaslatként fognak megjelenni.', + 'transactions_create_withdrawal_ffInput_budget' => 'A költségek költségkeret kapcsolásával jobb pénzügyi kontroll érhető el.', + 'transactions_create_withdrawal_currency_dropdown_amount' => 'Ezzel a legördülő listával lehet más pénznemet beállítani a költséghez.', + 'transactions_create_deposit_currency_dropdown_amount' => 'Ezzel a legördülő listával lehet más pénznemet beállítani a bevételhez.', + 'transactions_create_transfer_ffInput_piggy_bank_id' => 'Egy malacpersely kiválasztása és az átvezetés hozzákapcsolása megtakarításként.', + + // piggy banks index: + 'piggy-banks_index_saved' => 'Ez a mező azt mutatja, hogy menni a megtakarítás a malacperselyekben.', + 'piggy-banks_index_button' => 'A folyamatsáv mellett két gomb található (+ és -) melyekkel pénzt lehet a malacperselyekbe betenni vagy kivenni onnan.', + 'piggy-banks_index_accountStatus' => 'A legalább egy malacpersellyel rendelkező eszközszámlák állapota ebben a listában jelenik meg.', + + // create piggy + 'piggy-banks_create_name' => 'Mi a cél? Egy új kanapé, egy kamera, pénz a vészhelyzetekre?', + 'piggy-banks_create_date' => 'A malacperselyhez meg kell adni egy céldátumot vagy egy határidőt.', + + // show piggy + 'piggy-banks_show_piggyChart' => 'Ez a diagram a malacpersely történetét mutatja meg.', + 'piggy-banks_show_piggyDetails' => 'Néhány részlet a malacperselyről', + 'piggy-banks_show_piggyEvents' => 'Itt minden hozzáadás vagy kivétel is fel lesz sorolva.', + + // bill index + 'bills_index_rules' => 'Itt lehet látni, hogy mely szabályok lesznek ellenőrizve ezen a számlán', + 'bills_index_paid_in_period' => 'Ez a mező jelzi, hogy a számla mikor volt utoljára befizetve.', + 'bills_index_expected_in_period' => 'A mező azt mutatja meg a számláknál, hogy a következő számla várhatóan mikor esedékes.', + + // show bill + 'bills_show_billInfo' => 'Ez a táblázat néhány általános információt tartalmaz erről a számláról.', + 'bills_show_billButtons' => 'Ezzel a gombbal lehet újraolvasni a tranzakciókat, hogy egyeztetve legyenek a számlával.', + 'bills_show_billChart' => 'Ez a diagram a számlához kapcsolódó tranzakciókat mutatja.', + + // create bill + 'bills_create_intro' => 'A számlákkal lehet nyomon követni az egyes időszakokban fizetendő pénzösszegeket. Ezek lehetnek bérleti, biztosítási vagy jelzálog díjak.', + 'bills_create_name' => 'Érdemes leíró nevet használni, mint például a „Bérleti díj” vagy "Egészségbiztosítás".', + //'bills_create_match' => 'To match transactions, use terms from those transactions or the expense account involved. All words must match.', + 'bills_create_amount_min_holder' => 'Ki kell választani egy minimális és egy maximális összeget ehhez a számlához.', + 'bills_create_repeat_freq_holder' => 'A legtöbb számla havonta ismétlődik, de más rendszeresség is beállítható.', + 'bills_create_skip_holder' => 'Ha egy számla 2 hetente ismétlődik, akkor a „kihagyás” mezőt „1”-re kell állítani, hogy minden második hetet kihagyja.', + + // rules index + 'rules_index_intro' => 'A Firefly III lehetővé teszi szabályok kezelését melyek automágikusan alkalmazva lesznek az összes létrehozott vagy szerkesztett tranzakcióra.', + 'rules_index_new_rule_group' => 'A szabályokat csoportokba lehet rendezni a könnyebb kezelhetőség érdekében.', + 'rules_index_new_rule' => 'Bármennyi szabályt létre lehet hozni.', + 'rules_index_prio_buttons' => 'Bármely módon rendezni lehet őket.', + 'rules_index_test_buttons' => 'A szabályokat lehet tesztelni vagy alkalmazni a már meglévő tranzakciókra.', + 'rules_index_rule-triggers' => 'A szabályokhoz „eseményindítók” és „műveletek” tartozhatnak, melyeket húzással lehet sorba rendezni.', + 'rules_index_outro' => 'A súgóoldalak a jobb felső sarokban a (?) ikon alatt találhatóak!', + + // create rule: + 'rules_create_mandatory' => 'Egy informatív címet kell választani és beállítani, hogy a szabálynak mikor kell lefutnia.', + 'rules_create_ruletriggerholder' => 'Bármennyi eseményindító megadható, de meg kell jegyezni, hogy MINDEN eseményindítónak egyeznie kell a műveletek végrehajtása előtt.', + 'rules_create_test_rule_triggers' => 'Ezzel a gombbal meg lehet nézni, hogy mely tranzakciók felelnek meg a szabálynak.', + 'rules_create_actions' => 'Bármennyi műveletet be lehet állítani.', + + // preferences + 'preferences_index_tabs' => 'A fülek mögött több beállítása lehetőség áll rendelkezésre.', + + // currencies + 'currencies_index_intro' => 'A Firefly III több pénznemet támogat, melyeket ezen az oldalon lehet módosítani.', + 'currencies_index_default' => 'A Firefly III-nak csak egy alapértelmezett pénzneme van.', + 'currencies_index_buttons' => 'Ezzel a gombbal lehet módosítani az alapértelmezés szerinti pénznemet vagy más pénznemeket engedélyezni.', + + // create currency + 'currencies_create_code' => 'Ennek a kódnak ISO kompatibilisnek kell lennie (új pénznemnél érdemes a Google-t használni).', +]; diff --git a/resources/lang/hu_HU/list.php b/resources/lang/hu_HU/list.php new file mode 100644 index 0000000000..b7be22027c --- /dev/null +++ b/resources/lang/hu_HU/list.php @@ -0,0 +1,136 @@ +. + */ + +declare(strict_types=1); + +return [ + 'buttons' => 'Gombok', + 'icon' => 'Ikon', + 'id' => 'Azonosító', + 'create_date' => 'Létrehozva:', + 'update_date' => 'Frissítve:', + 'updated_at' => 'Frissítve:', + 'balance_before' => 'Egyenleg előtte', + 'balance_after' => 'Egyenleg utána', + 'name' => 'Név', + 'role' => 'Szerepkör', + 'currentBalance' => 'Aktuális egyenleg', + 'linked_to_rules' => 'Vonatkozó szabályok', + 'active' => 'Aktív?', + 'transaction_type' => 'Type', + 'lastActivity' => 'Utolsó aktivitás', + 'balanceDiff' => 'Egyenleg különbség', + 'matchesOn' => 'Találatok', + 'account_type' => 'Bankszámla típusa', + 'created_at' => 'Létrehozva', + 'account' => 'Bankszámla', + 'matchingAmount' => 'Összeg', + 'split_number' => 'Felosztás #', + 'destination' => 'Cél', + 'source' => 'Forrás', + 'next_expected_match' => 'Következő várható egyezés', + 'automatch' => 'Automatikus egyezés?', + 'repeat_freq' => 'Ismétlődések', + 'description' => 'Leírás', + 'amount' => 'Összeg', + 'internal_reference' => 'Belső hivatkozás', + 'date' => 'Dátum', + 'interest_date' => 'Kamatfizetési időpont', + 'book_date' => 'Könyvelés dátuma', + 'process_date' => 'Feldolgozás dátuma', + 'due_date' => 'Lejárati időpont', + 'payment_date' => 'Fizetés dátuma', + 'invoice_date' => 'Számla dátuma', + 'interal_reference' => 'Belső hivatkozás', + 'notes' => 'Megjegyzések', + 'from' => 'Honnan', + 'piggy_bank' => 'Malacpersely', + 'to' => 'Hova', + 'budget' => 'Költségkeret', + 'category' => 'Kategória', + 'bill' => 'Számla', + 'withdrawal' => 'Költség', + 'deposit' => 'Bevétel', + 'transfer' => 'Átvezetés', + 'type' => 'Típus', + 'completed' => 'Teljesített', + 'iban' => 'IBAN', + 'paid_current_period' => 'Fizetve ebben az időszakban', + 'email' => 'Email', + 'registered_at' => 'Regisztrálva:', + 'is_blocked' => 'Letiltott', + 'is_admin' => 'Adminisztrátor', + 'has_two_factor' => '2 Lépcsős azonosítással rendelkezik', + 'blocked_code' => 'Letiltás kódja', + 'source_account' => 'Forrás bankszámla', + 'destination_account' => 'Cél bankszámla', + 'accounts_count' => 'Bankszámlák száma', + 'journals_count' => 'Tranzakciók száma', + 'attachments_count' => 'Mellékletek száma', + 'bills_count' => 'Számlák száma', + 'categories_count' => 'Kategóriák száma', + 'import_jobs_count' => 'Import folyamatok száma', + 'budget_count' => 'Költségkeretek száma', + 'rule_and_groups_count' => 'Szabályok és szabálycsoportok száma', + 'tags_count' => 'Címkék száma', + 'tags' => 'Címkék', + 'inward' => 'Belső leírás', + 'outward' => 'Külső leírás', + 'number_of_transactions' => 'Tranzakciók száma', + 'total_amount' => 'Teljes összeg', + 'sum' => 'Összesen', + 'sum_excluding_transfers' => 'Összeg (az átvezetések kivételével)', + 'sum_withdrawals' => 'Költségek összege', + 'sum_deposits' => 'Bevételek összege', + 'sum_transfers' => 'Átvezetések összege', + 'reconcile' => 'Egyeztetés', + 'account_on_spectre' => 'Fiók (Spectre)', + 'account_on_ynab' => 'Számla (YNAB)', + 'do_import' => 'Importálás ebből a fiókból:', + 'sepa_ct_id' => 'SEPA End to End Identifier', + 'sepa_ct_op' => 'SEPA ellenszámla azonosító', + 'sepa_db' => 'SEPA megbízás azonosító', + 'sepa_country' => 'SEPA ország', + 'sepa_cc' => 'SEPA Clearing Code', + 'sepa_ep' => 'SEPA External Purpose', + 'sepa_ci' => 'SEPA hitelező azonosító', + 'sepa_batch_id' => 'SEPA Batch ID', + 'external_id' => 'Külső azonosító', + 'account_at_bunq' => 'Account with bunq', + 'file_name' => 'Fájlnév', + 'file_size' => 'Fájl méret', + 'file_type' => 'Fájltípus', + 'attached_to' => 'Csatolva', + 'file_exists' => 'Létező fájl', + 'spectre_bank' => 'Bank', + 'spectre_last_use' => 'Legutóbbi bejelentkezés', + 'spectre_status' => 'Állapot', + 'bunq_payment_id' => 'bunq fizetési azonosító', + 'repetitions' => 'Ismétlődések', + 'title' => 'Cím', + 'transaction_s' => 'Tranzakciók', + 'field' => 'Mező', + 'value' => 'Érték', + 'interest' => 'Kamat', + 'interest_period' => 'kamatperiódus', + 'liability_type' => 'A kötelezettség típusa', +]; diff --git a/resources/lang/hu_HU/pagination.php b/resources/lang/hu_HU/pagination.php new file mode 100644 index 0000000000..ebe0086512 --- /dev/null +++ b/resources/lang/hu_HU/pagination.php @@ -0,0 +1,28 @@ +. + */ + +declare(strict_types=1); + +return [ + 'previous' => '« Előző', + 'next' => 'Következő »', +]; diff --git a/resources/lang/hu_HU/passwords.php b/resources/lang/hu_HU/passwords.php new file mode 100644 index 0000000000..5927eaf387 --- /dev/null +++ b/resources/lang/hu_HU/passwords.php @@ -0,0 +1,32 @@ +. + */ + +declare(strict_types=1); + +return [ + 'password' => 'A jelszónak legalább hat karakterből kell állnia, és meg kell egyeznie a megerősítéssel.', + 'user' => 'Nem található felhasználó ezzel az email címmel.', + 'token' => 'Ez a jelszó visszaállítási vezérjel érvénytelen.', + 'sent' => 'Emailben elküldtük a jelszó visszaállításához szükséges hivatkozást!', + 'reset' => 'A jelszó visszaállításra került!', + 'blocked' => 'Szép próbálkozás.', +]; diff --git a/resources/lang/hu_HU/validation.php b/resources/lang/hu_HU/validation.php new file mode 100644 index 0000000000..7cf257a14c --- /dev/null +++ b/resources/lang/hu_HU/validation.php @@ -0,0 +1,195 @@ +. + */ + +declare(strict_types=1); + +return [ + 'iban' => 'Ez nem egy érvényes IBAN.', + 'zero_or_more' => 'Az érték nem lehet negatív.', + 'date_or_time' => 'Az értéknek érvényes dátum vagy időformátumúnak kell lennie (ISO 8601).', + 'source_equals_destination' => 'A forrásszámla egyenlő a célszámlával.', + 'unique_account_number_for_user' => 'Úgy néz ki, hogy ez a számlaszám már használatban van.', + 'unique_iban_for_user' => 'Úgy néz ki, hogy ez a számlaszám már használatban van.', + 'deleted_user' => 'Biztonsági megkötések miatt ezzel az email címmel nem lehet regisztrálni.', + 'rule_trigger_value' => 'Ez az érték érvénytelen a kiválasztott eseményindítóhoz.', + 'rule_action_value' => 'Ez az érték érvénytelen a kiválasztott művelethez.', + 'file_already_attached' => 'A feltöltött ":name" fájl már csatolva van ehhez az objektumhoz.', + 'file_attached' => '":name" fájl sikeresen feltöltve.', + 'must_exist' => 'Az ID mező :attribute nem létezik az adatbázisban.', + 'all_accounts_equal' => 'Ebben a mezőben minden számlának meg kell egyeznie.', + 'group_title_mandatory' => 'A csoportcím kötelező ha egynél több tranzakció van.', + 'transaction_types_equal' => 'Minden felosztásnak ugyanolyan típusúnak kell lennie.', + 'invalid_transaction_type' => 'Érvénytelen tranzakciótípus.', + 'invalid_selection' => 'Érvénytelen kiválasztás.', + 'belongs_user' => 'Az érték nem érvényes erre a mezőre.', + 'at_least_one_transaction' => 'Legalább egy tranzakció szükséges.', + 'at_least_one_repetition' => 'Legalább egy ismétlés szükséges.', + 'require_repeat_until' => 'Legalább egy ismétlésszám vagy egy végdátum (repeat_until) kötelező. Csak az egyik.', + 'require_currency_info' => 'Ennek a mezőnek a tartalma érvénytelen pénznem információ nélkül.', + 'require_currency_amount' => 'The content of this field is invalid without foreign amount information.', + 'equal_description' => 'A tranzakció leírása nem egyezhet meg a globális leírással.', + 'file_invalid_mime' => '":name" fájl ":mime" típusú ami nem lehet új feltöltés.', + 'file_too_large' => '":name" fájl túl nagy.', + 'belongs_to_user' => ':attribute értéke ismeretlen.', + 'accepted' => ':attribute attribútumot el kell fogadni.', + 'bic' => 'Ez nem egy érvényes BIC.', + 'at_least_one_trigger' => 'A szabályban legalább egy eseményindítónak lennie kell.', + 'at_least_one_action' => 'A szabályban legalább egy műveletnek lennie kell.', + 'base64' => 'Ez nem érvényes base64 kódolású adat.', + 'model_id_invalid' => 'A megadott azonosító érvénytelennek tűnik ehhez a modellhez.', + 'more' => ':attribute nagyobb kell legyen nullánál.', + 'less' => ':attribute kisebbnek kell lennie 10,000,000-nél', + 'active_url' => ':attribute nem egy érvényes URL.', + 'after' => ':attribute egy :date utáni dátum kell legyen.', + 'alpha' => ':attribute csak betűket tartalmazhat.', + 'alpha_dash' => ':attribute csak számokat, betűket és kötőjeleket tartalmazhat.', + 'alpha_num' => ':attribute csak betűket és számokat tartalmazhat.', + 'array' => ':attribute egy tömb kell legyen.', + 'unique_for_user' => ':attribute attribútumhoz már van bejegyzés.', + 'before' => ':attribute csak :date előtti dátum lehet.', + 'unique_object_for_user' => 'A név már használatban van.', + 'unique_account_for_user' => 'Ez a fióknév már használatban van.', + 'between.numeric' => ':attribute :min és :max között kell legyen.', + 'between.file' => ':attribute :min és :max kilobájt között kell legyen.', + 'between.string' => ':attribute :min és :max karakter között kell legyen.', + 'between.array' => ':attribute :min és :max elem között kell legyen.', + 'boolean' => ':attribute mező csak igaz vagy hamis lehet.', + 'confirmed' => 'A :attribute ellenörzés nem egyezik.', + 'date' => ':attribute nem egy érvényes dátum.', + 'date_format' => ':attribute nem egyezik :format formátummal.', + 'different' => ':attribute és :other különböző kell legyen.', + 'digits' => ':attribute :digits számjegy kell legyen.', + 'digits_between' => ':attribute :min és :max számjegy között kell legyen.', + 'email' => ':attribute érvényes email cím kell legyen.', + 'filled' => ':attribute mező kötelező.', + 'exists' => 'A kiválasztott :attribute étvénytelen.', + 'image' => ':attribute kép kell legyen.', + 'in' => 'A kiválasztott :attribute étvénytelen.', + 'integer' => ':attribute csak egész szám lehet.', + 'ip' => ':attribute érvényes IP cím kell legyen.', + 'json' => ':attribute érvényes JSON karakterlánc kell legyen.', + 'max.numeric' => ':attribute nem lehet nagyobb, mint :max.', + 'max.file' => ':attribute nem lehet nagyobb, mint :max kilobájt.', + 'max.string' => ':attribute nem lehet nagyobb, mint :max karakter.', + 'max.array' => ':attribute nem lehet több, mint :max elem.', + 'mimes' => 'A :attribute ilyen fájl típusnak kell lennie: :values.', + 'min.numeric' => 'A :attribute legalább :min kell lenni.', + 'lte.numeric' => ':attribute attribútumnak :value értéknél kevesebbnek vagy vele egyenlőnek kell lennie.', + 'min.file' => ':attribute legalább :min kilobájt kell legyen.', + 'min.string' => ':attribute legalább :min karakter kell legyen.', + 'min.array' => ':attribute legalább :min elem kell legyen.', + 'not_in' => 'A kiválasztott :attribute étvénytelen.', + 'numeric' => ':attribute szám kell legyen.', + 'numeric_native' => 'A natív értéknek számnak kell lennie.', + 'numeric_destination' => 'A cél mennyiségnek számnak kell lennie.', + 'numeric_source' => 'A forrás mennyiségnek számnak kell lennie.', + 'regex' => ':attribute attribútum formátuma érvénytelen.', + 'required' => ':attribute mező kötelező.', + 'required_if' => ':attribute mező kötelező, ha :other :value.', + 'required_unless' => ':attribute mező kötelező, kivéve ha :other szerepel itt: :values.', + 'required_with' => ':attribute attribútum mező kötelező ha jelen van :values.', + 'required_with_all' => ':attribute attribútum mező kötelező ha jelen van :values.', + 'required_without' => ':attribute mező kötelező, ha :values nincs jelen.', + 'required_without_all' => ':attribute mező kötelező, ha :values közül egy sincs jelen.', + 'same' => ':attribute és :other meg kell egyezzenek.', + 'size.numeric' => ':attribute attribútumnak :size méretűnek kell lennie.', + 'amount_min_over_max' => 'A minimum mennyiség nem lehet nagyobb mint a maximális mennyiség.', + 'size.file' => ':attribute :size kilobájt kell legyen.', + 'size.string' => ':attribute :size karakter kell legyen.', + 'size.array' => ':attribute :size elemet kell, hogy tartalmazzon.', + 'unique' => ':attribute már foglalt.', + 'string' => ':attribute egy karakterlánc kell legyen.', + 'url' => ':attribute attribútum formátuma érvénytelen.', + 'timezone' => ':attribute érvényes zóna kell legyen.', + '2fa_code' => ':attribute mező érvénytelen.', + 'dimensions' => ':attribute attribútum képfelbontása érvénytelen.', + 'distinct' => ':attribute mezőben duplikált érték van.', + 'file' => ':attribute egy fájl kell legyen.', + 'in_array' => ':attribute nem létezik itt: :other.', + 'present' => ':attribute mezőnek jelen kell lennie.', + 'amount_zero' => 'A teljes mennyiség nem lehet nulla.', + 'unique_piggy_bank_for_user' => 'A malacpersely nevének egyedinek kell lennie.', + 'secure_password' => 'Ez nem biztonságos jelszó. Kérlek próbáld meg újra. További információért lásd: https://bit.ly/FF3-password-security', + 'valid_recurrence_rep_type' => 'Érvénytelen ismétléstípus az ismétlődő tranzakciókhoz.', + 'valid_recurrence_rep_moment' => 'Érvénytelen ismétlési időpont ehhez az ismétléstípushoz.', + 'invalid_account_info' => 'Érvénytelen számlainformáció.', + 'attributes' => [ + 'email' => 'email cím', + 'description' => 'leírás', + 'amount' => 'összeg', + 'name' => 'név', + 'piggy_bank_id' => 'malacpersely azonosító', + 'targetamount' => 'cél összeg', + 'opening_balance_date' => 'nyitó egyenleg dátuma', + 'opening_balance' => 'nyitó egyenleg', + 'match' => 'egyezés', + 'amount_min' => 'minimális összeg', + 'amount_max' => 'maximális összeg', + 'title' => 'cím', + 'tag' => 'címke', + 'transaction_description' => 'tranzakció leírása', + 'rule-action-value.1' => 'szabály művelet érték #1', + 'rule-action-value.2' => 'szabály művelet érték #2', + 'rule-action-value.3' => 'szabály művelet érték #3', + 'rule-action-value.4' => 'szabály művelet érték #4', + 'rule-action-value.5' => 'szabály művelet érték #5', + 'rule-action.1' => 'szabály művelet #1', + 'rule-action.2' => 'szabály művelet #2', + 'rule-action.3' => 'szabály művelet #3', + 'rule-action.4' => 'szabály művelet #4', + 'rule-action.5' => 'szabály művelet #5', + 'rule-trigger-value.1' => 'szabály eseményindító érték #1', + 'rule-trigger-value.2' => 'szabály eseményindító érték #2', + 'rule-trigger-value.3' => 'szabály eseményindító érték #3', + 'rule-trigger-value.4' => 'szabály eseményindító érték #4', + 'rule-trigger-value.5' => 'szabály eseményindító érték #5', + 'rule-trigger.1' => 'szabály eseményindító #1', + 'rule-trigger.2' => 'szabály eseményindító #2', + 'rule-trigger.3' => 'szabály eseményindító #3', + 'rule-trigger.4' => 'szabály eseményindító #4', + 'rule-trigger.5' => 'szabály eseményindító #5', + ], + + // validation of accounts: + 'withdrawal_source_need_data' => 'Egy érvényes forrásszámla azonosító és/vagy egy érvényes forrásszámla név kell a folytatáshoz.', + 'withdrawal_source_bad_data' => 'Nem található érvényes forrásszámla ":id" azonosító vagy ":name" név keresésekor.', + 'withdrawal_dest_need_data' => 'Egy érvényes célszámla azonosító és/vagy egy érvényes célszámla név kell a folytatáshoz.', + 'withdrawal_dest_bad_data' => 'Nem található érvényes célszámla ":id" azonosító vagy ":name" név keresésekor.', + + 'deposit_source_need_data' => 'Egy érvényes forrásszámla azonosító és/vagy egy érvényes forrásszámla név kell a folytatáshoz.', + 'deposit_source_bad_data' => 'Nem található érvényes forrásszámla ":id" azonosító vagy ":name" név keresésekor.', + 'deposit_dest_need_data' => 'Egy érvényes célszámla azonosító és/vagy egy érvényes célszámla név kell a folytatáshoz.', + 'deposit_dest_bad_data' => 'Nem található érvényes célszámla ":id" azonosító vagy ":name" név keresésekor.', + + 'transfer_source_need_data' => 'Egy érvényes forrásszámla azonosító és/vagy egy érvényes forrásszámla név kell a folytatáshoz.', + 'transfer_source_bad_data' => 'Nem található érvényes forrásszámla ":id" azonosító vagy ":name" név keresésekor.', + 'transfer_dest_need_data' => 'Egy érvényes célszámla azonosító és/vagy egy érvényes célszámla név kell a folytatáshoz.', + 'transfer_dest_bad_data' => 'Nem található érvényes célszámla ":id" azonosító vagy ":name" név keresésekor.', + 'need_id_in_edit' => 'Minden felosztásnak rendelkeznie kell "transaction_journal_id"-val (lehet érvényes érték vagy 0).', + + 'ob_source_need_data' => 'Egy érvényes forrásszámla azonosító és/vagy egy érvényes forrásszámla név kell a folytatáshoz.', + 'ob_dest_need_data' => 'Egy érvényes célszámla azonosító és/vagy egy érvényes célszámla név kell a folytatáshoz.', + 'ob_dest_bad_data' => 'Nem található érvényes célszámla ":id" azonosító vagy ":name" név keresésekor.', + + 'generic_invalid_source' => 'You can\'t use this account as the source account.', + 'generic_invalid_destination' => 'You can\'t use this account as the destination account.', +]; diff --git a/resources/lang/id_ID/breadcrumbs.php b/resources/lang/id_ID/breadcrumbs.php index a6e166fd57..1b65028076 100644 --- a/resources/lang/id_ID/breadcrumbs.php +++ b/resources/lang/id_ID/breadcrumbs.php @@ -23,35 +23,37 @@ declare(strict_types=1); return [ - 'home' => 'Beranda', - 'edit_currency' => 'Edit mata uang ":name"', - 'delete_currency' => 'Hapus mata uang ":name"', - 'newPiggyBank' => 'Buat celengan baru', - 'edit_piggyBank' => 'Edit celengan ":name"', - 'preferences' => 'Preferensi', - 'profile' => 'Profil', - 'changePassword' => 'Ubah kata sandi Anda', - 'change_email' => 'Ubah alamat email anda', - 'bills' => 'Tagihan', - 'newBill' => 'Tagihan baru', - 'edit_bill' => 'Edit tagihan ":name"', - 'delete_bill' => 'Hapus tagihan ":name"', - 'reports' => 'Laporan', - 'search_result' => 'Hasil pencarian untuk ":query"', - 'withdrawal_list' => 'Pengeluaran', - 'deposit_list' => 'Pendapatan, pemasukan, dan deposit', - 'transfer_list' => 'Transfer', - 'transfers_list' => 'Transfer', - 'reconciliation_list' => 'Rekonsiliasi', - 'create_withdrawal' => 'Buat penarikan baru', - 'create_deposit' => 'Buat deposit baru', - 'create_transfer' => 'Buat transfer baru', - 'edit_journal' => 'Edit transaksi ":description"', - 'edit_reconciliation' => 'Edit ":description"', - 'delete_journal' => 'Hapus transaksi ":description"', - 'tags' => 'Label', - 'createTag' => 'Buat label baru', - 'edit_tag' => 'Edit label ":tag"', - 'delete_tag' => 'Hapus label ":tag"', - 'delete_journal_link' => 'Hapus tautan antar transaksi', + 'home' => 'Beranda', + 'edit_currency' => 'Edit mata uang ":name"', + 'delete_currency' => 'Hapus mata uang ":name"', + 'newPiggyBank' => 'Buat celengan baru', + 'edit_piggyBank' => 'Edit celengan ":name"', + 'preferences' => 'Preferensi', + 'profile' => 'Profil', + 'changePassword' => 'Ubah kata sandi Anda', + 'change_email' => 'Ubah alamat email anda', + 'bills' => 'Tagihan', + 'newBill' => 'Tagihan baru', + 'edit_bill' => 'Edit tagihan ":name"', + 'delete_bill' => 'Hapus tagihan ":name"', + 'reports' => 'Laporan', + 'search_result' => 'Hasil pencarian untuk ":query"', + 'withdrawal_list' => 'Pengeluaran', + 'Withdrawal_list' => 'Expenses', + 'deposit_list' => 'Pendapatan, pemasukan, dan deposit', + 'transfer_list' => 'Transfer', + 'transfers_list' => 'Transfer', + 'reconciliation_list' => 'Rekonsiliasi', + 'create_withdrawal' => 'Buat penarikan baru', + 'create_deposit' => 'Buat deposit baru', + 'create_transfer' => 'Buat transfer baru', + 'create_new_transaction' => 'Create a new transaction', + 'edit_journal' => 'Edit transaksi ":description"', + 'edit_reconciliation' => 'Edit ":description"', + 'delete_journal' => 'Hapus transaksi ":description"', + 'tags' => 'Label', + 'createTag' => 'Buat label baru', + 'edit_tag' => 'Edit label ":tag"', + 'delete_tag' => 'Hapus label ":tag"', + 'delete_journal_link' => 'Hapus tautan antar transaksi', ]; diff --git a/resources/lang/id_ID/firefly.php b/resources/lang/id_ID/firefly.php index d5c4f4c03c..0deed9309d 100644 --- a/resources/lang/id_ID/firefly.php +++ b/resources/lang/id_ID/firefly.php @@ -33,7 +33,7 @@ return [ 'last_seven_days' => '7 hari terakhir', 'last_thirty_days' => '30 hari terakhir', 'welcomeBack' => 'Apa yang sedang diputar?', - 'welcome_back' => 'What\'s playing?', + 'welcome_back' => 'What\'s playing?', 'everything' => 'Segala sesuatu', 'today' => 'hari ini', 'customRange' => 'Rentang khusus', @@ -54,6 +54,7 @@ return [ 'create_new_stuff' => 'Buat barang baru', 'new_withdrawal' => 'Penarikan baru', 'create_new_transaction' => 'Create new transaction', + 'new_transaction' => 'New transaction', 'go_to_asset_accounts' => 'View your asset accounts', 'go_to_budgets' => 'Go to your budgets', 'go_to_categories' => 'Go to your categories', @@ -82,28 +83,32 @@ return [ 'help_for_this_page' => 'Help for this page', 'no_help_could_be_found' => 'No help text could be found.', 'no_help_title' => 'Apologies, an error occurred.', - 'two_factor_welcome' => 'Halo, :user!', - 'two_factor_enter_code' => 'Untuk melanjutkan, masukkan kode otentikasi dua faktor Anda. Aplikasi Anda bisa menghasilkannya untuk Anda.', - 'two_factor_code_here' => 'Masukkan kode di sini', - 'two_factor_title' => 'Dua faktor otentikasi', - 'authenticate' => 'Otentikasi', - 'two_factor_forgot_title' => 'Kehilangan dua faktor otentikasi', - 'two_factor_forgot' => 'Saya lupa dua faktor saya.', - 'two_factor_lost_header' => 'Kehilangan autentikasi dua faktor Anda?', - 'two_factor_lost_intro' => 'Sayangnya, ini bukan sesuatu yang bisa Anda atur ulang dari antarmuka web. Anda punya dua pilihan.', - 'two_factor_lost_fix_self' => 'Jika Anda menjalankan contoh Firefly III Anda sendiri, periksa log di storage / logs untuk mendapatkan petunjuk.', - 'two_factor_lost_fix_owner' => 'Jika tidak, kirimkan email ke pemilik situs, :site_owner dan mintalah mereka untuk menyetel ulang autentikasi dua faktor Anda.', - 'warning_much_data' => ':days hari data mungkin perlu beberapa saat untuk memuat.', - 'registered' => 'Anda telah berhasil mendaftar!', - 'Default asset account' => 'Akun aset standar', - 'no_budget_pointer' => 'You seem to have no budgets yet. You should create some on the budgets-page. Budgets can help you keep track of expenses.', - 'Savings account' => 'Rekening tabungan', - 'Credit card' => 'Kartu kredit', - 'source_accounts' => 'Akun sumber', - 'destination_accounts' => 'Akun tujuan', - 'user_id_is' => 'Id pengguna Anda adalah :user', - 'field_supports_markdown' => 'Bidang ini mendukung Markdown.', - 'need_more_help' => 'Jika Anda memerlukan bantuan lebih banyak menggunakan Firefly III, silakan opsi tiket di Github.', + 'two_factor_welcome' => 'Hello!', + 'two_factor_enter_code' => 'Untuk melanjutkan, masukkan kode otentikasi dua faktor Anda. Aplikasi Anda bisa menghasilkannya untuk Anda.', + 'two_factor_code_here' => 'Masukkan kode di sini', + 'two_factor_title' => 'Dua faktor otentikasi', + 'authenticate' => 'Otentikasi', + 'two_factor_forgot_title' => 'Kehilangan dua faktor otentikasi', + 'two_factor_forgot' => 'Saya lupa dua faktor saya.', + 'two_factor_lost_header' => 'Kehilangan autentikasi dua faktor Anda?', + 'two_factor_lost_intro' => 'If you lost your backup codes as well, you have bad luck. This is not something you can fix from the web interface. You have two choices.', + 'two_factor_lost_fix_self' => 'If you run your own instance of Firefly III, check the logs in storage/logs for instructions, or run docker logs <container_id> to see the instructions (refresh this page).', + 'two_factor_lost_fix_owner' => 'Jika tidak, kirimkan email ke pemilik situs, :site_owner dan mintalah mereka untuk menyetel ulang autentikasi dua faktor Anda.', + 'mfa_backup_code' => 'You have used a backup code to login to Firefly III. It can\'t be used again, so cross it from your list.', + 'pref_two_factor_new_backup_codes' => 'Get new backup codes', + 'pref_two_factor_backup_code_count' => 'You have :count valid backup code(s).', + '2fa_i_have_them' => 'I stored them!', + 'warning_much_data' => ':days hari data mungkin perlu beberapa saat untuk memuat.', + 'registered' => 'Anda telah berhasil mendaftar!', + 'Default asset account' => 'Akun aset standar', + 'no_budget_pointer' => 'You seem to have no budgets yet. You should create some on the budgets-page. Budgets can help you keep track of expenses.', + 'Savings account' => 'Rekening tabungan', + 'Credit card' => 'Kartu kredit', + 'source_accounts' => 'Akun sumber', + 'destination_accounts' => 'Akun tujuan', + 'user_id_is' => 'Id pengguna Anda adalah :user', + 'field_supports_markdown' => 'Bidang ini mendukung Markdown.', + 'need_more_help' => 'Jika Anda memerlukan bantuan lebih banyak menggunakan Firefly III, silakan opsi tiket di Github.', 'reenable_intro_text' => 'Anda juga dapat mengaktifkan panduan pendahuluan.', 'intro_boxes_after_refresh' => 'Kotak pengantar akan muncul kembali saat Anda menyegarkan halaman.', 'show_all_no_filter' => 'Tampilkan semua transaksi tanpa mengelompokkan mereka menurut tanggal.', @@ -220,21 +225,23 @@ return [ 'search_query' => 'Pertanyaan', 'search_found_transactions' => 'Firefly III found :count transaction(s) in :time seconds.', 'search_for_query' => 'Firefly III is searching for transactions with all of these words in them: :query', - 'search_modifier_amount_is' => 'Amount is exactly :value', - 'search_modifier_amount' => 'Amount is exactly :value', - 'search_modifier_amount_max' => 'Amount is at most :value', - 'search_modifier_amount_min' => 'Amount is at least :value', - 'search_modifier_amount_less' => 'Amount is less than :value', - 'search_modifier_amount_more' => 'Amount is more than :value', - 'search_modifier_source' => 'Source account is :value', - 'search_modifier_destination' => 'Destination account is :value', - 'search_modifier_category' => 'Category is :value', - 'search_modifier_budget' => 'Budget is :value', - 'search_modifier_bill' => 'Bill is :value', - 'search_modifier_type' => 'Transaction type is :value', - 'search_modifier_date' => 'Transaction date is :value', - 'search_modifier_date_before' => 'Transaction date is before :value', - 'search_modifier_date_after' => 'Transaction date is after :value', + 'search_modifier_amount_is' => 'Amount is exactly :value', + 'search_modifier_amount' => 'Amount is exactly :value', + 'search_modifier_amount_max' => 'Amount is at most :value', + 'search_modifier_amount_min' => 'Amount is at least :value', + 'search_modifier_amount_less' => 'Amount is less than :value', + 'search_modifier_amount_more' => 'Amount is more than :value', + 'search_modifier_source' => 'Source account is :value', + 'search_modifier_from' => 'Source account is :value', + 'search_modifier_destination' => 'Destination account is :value', + 'search_modifier_to' => 'Destination account is :value', + 'search_modifier_category' => 'Category is :value', + 'search_modifier_budget' => 'Budget is :value', + 'search_modifier_bill' => 'Bill is :value', + 'search_modifier_type' => 'Transaction type is :value', + 'search_modifier_date' => 'Transaction date is :value', + 'search_modifier_date_before' => 'Transaction date is before :value', + 'search_modifier_date_after' => 'Transaction date is after :value', 'search_modifier_on' => 'Transaction date is :value', 'search_modifier_before' => 'Transaction date is before :value', 'search_modifier_after' => 'Transaction date is after :value', @@ -257,33 +264,6 @@ return [ 'half-year' => 'setiap setengah tahun', 'yearly' => 'tahunan', - // export data: - 'import_and_export' => 'Impor dan ekspor', - 'export_data' => 'Data ekspor', - 'export_and_backup_data' => 'Export data', - 'export_data_intro' => 'Use the exported data to move to a new financial application. Please note that these files are not meant as a backup. They do not contain enough meta-data to fully restore a new Firefly III installation. If you want to make a backup of your data, please backup the database directly.', - 'export_format' => 'Format ekspor', - 'export_format_csv' => 'Nilai yang dipisahkan koma (file CSV)', - 'export_format_mt940' => 'Format MT940 yang kompatibel', - 'include_old_uploads_help' => 'Firefly III tidak membuang file CSV asli yang telah Anda impor sebelumnya. Anda bisa memasukkannya ke dalam ekspor Anda.', - 'do_export' => 'Ekspor', - 'export_status_never_started' => 'Ekspor belum dimulai', - 'export_status_make_exporter' => 'Membuat barang eksportir...', - 'export_status_collecting_journals' => 'Mengumpulkan transaksi Anda...', - 'export_status_collected_journals' => 'Mengumpulkan transaksi Anda!', - 'export_status_converting_to_export_format' => 'Mengubah transaksi Anda...', - 'export_status_converted_to_export_format' => 'Mengubah transaksi anda!', - 'export_status_creating_journal_file' => 'Membuat file ekspor...', - 'export_status_created_journal_file' => 'Membuat file ekspor!', - 'export_status_collecting_attachments' => 'Mengumpulkan semua lampiran Anda...', - 'export_status_collected_attachments' => 'Mengumpulkan semua lampiran Anda!', - 'export_status_collecting_old_uploads' => 'Mengumpulkan semua unggahan sebelumnya...', - 'export_status_collected_old_uploads' => 'Mengumpulkan semua upload sebelumnya!', - 'export_status_creating_zip_file' => 'Membuat file zip...', - 'export_status_created_zip_file' => 'Membuat file zip!', - 'export_status_finished' => 'Ekspor telah berhasil selesai! Yay!', - 'export_data_please_wait' => 'Mohon tunggu...', - // rules 'rules' => 'Aturan', 'rule_name' => 'Nama aturan', @@ -502,30 +482,33 @@ return [ 'pref_custom_fiscal_year_help' => 'Di negara-negara yang menggunakan tahun keuangan selain 1 Januari sampai 31 Desember, Anda dapat mengaktifkannya dan menentukan hari-hari awal / akhir dari tahun fiskal', 'pref_fiscal_year_start_label' => 'Tahun anggaran mulai tanggal', 'pref_two_factor_auth' => 'Verifikasi 2 langkah', - 'pref_two_factor_auth_help' => 'Bila Anda mengaktifkan verifikasi 2 langkah (juga dikenal sebagai autentikasi dua faktor), Anda menambahkan lapisan keamanan ekstra ke akun Anda. Anda masuk dengan sesuatu yang Anda tahu (kata sandi Anda) dan sesuatu yang Anda miliki (kode verifikasi). Kode verifikasi dihasilkan oleh aplikasi di ponsel Anda, seperti Authy atau Google Authenticator.', - 'pref_enable_two_factor_auth' => 'Aktifkan verifikasi 2 langkah', - 'pref_two_factor_auth_disabled' => 'Kode verifikasi 2 langkah dihapus dan dinonaktifkan', - 'pref_two_factor_auth_remove_it' => 'Jangan lupa menghapus akun dari aplikasi autentikasi Anda!', - 'pref_two_factor_auth_code' => 'Kode verifikasi', - 'pref_two_factor_auth_code_help' => 'Scan the QR code with an application on your phone such as Authy or Google Authenticator and enter the generated code.', - 'pref_two_factor_auth_reset_code' => 'Setel ulang kode verifikasi', - 'pref_two_factor_auth_disable_2fa' => 'Disable 2FA', - '2fa_use_secret_instead' => 'If you cannot scan the QR code, feel free to use the secret instead: :secret.', - 'pref_save_settings' => 'Simpan Pengaturan', - 'saved_preferences' => 'Preferensi disimpan!', - 'preferences_general' => 'Umum', - 'preferences_frontpage' => 'Tampilan depan', - 'preferences_security' => 'Keamanan', - 'preferences_layout' => 'Tata ruang', - 'pref_home_show_deposits' => 'Tampilkan deposito pada layar awal', - 'pref_home_show_deposits_info' => 'Layar awal sudah menunjukkan rekening pengeluaran Anda. Harus itu juga menunjukkan akun pendapatan Anda?', - 'pref_home_do_show_deposits' => 'Ya, menunjukkan kepada mereka', - 'successful_count' => ':count menghitung sukses', - 'list_page_size_title' => 'Ukuran halaman', - 'list_page_size_help' => 'Setiap daftar hal-hal (rekening, transaksi, dll) menunjukkan paling ini banyak per halaman.', - 'list_page_size_label' => 'Ukuran halaman', - 'between_dates' => '(:start dan :end)', - 'pref_optional_fields_transaction' => 'Bidang opsional untuk transaksi', + 'pref_two_factor_auth_help' => 'Bila Anda mengaktifkan verifikasi 2 langkah (juga dikenal sebagai autentikasi dua faktor), Anda menambahkan lapisan keamanan ekstra ke akun Anda. Anda masuk dengan sesuatu yang Anda tahu (kata sandi Anda) dan sesuatu yang Anda miliki (kode verifikasi). Kode verifikasi dihasilkan oleh aplikasi di ponsel Anda, seperti Authy atau Google Authenticator.', + 'pref_enable_two_factor_auth' => 'Aktifkan verifikasi 2 langkah', + 'pref_two_factor_auth_disabled' => 'Kode verifikasi 2 langkah dihapus dan dinonaktifkan', + 'pref_two_factor_auth_remove_it' => 'Jangan lupa menghapus akun dari aplikasi autentikasi Anda!', + 'pref_two_factor_auth_code' => 'Kode verifikasi', + 'pref_two_factor_auth_code_help' => 'Scan the QR code with an application on your phone such as Authy or Google Authenticator and enter the generated code.', + 'pref_two_factor_auth_reset_code' => 'Setel ulang kode verifikasi', + 'pref_two_factor_auth_disable_2fa' => 'Disable 2FA', + '2fa_use_secret_instead' => 'If you cannot scan the QR code, feel free to use the secret instead: :secret.', + '2fa_backup_codes' => 'Store these backup codes for access in case you lose your device.', + '2fa_already_enabled' => '2-step verification is already enabled.', + 'wrong_mfa_code' => 'This MFA code is not valid.', + 'pref_save_settings' => 'Simpan Pengaturan', + 'saved_preferences' => 'Preferensi disimpan!', + 'preferences_general' => 'Umum', + 'preferences_frontpage' => 'Tampilan depan', + 'preferences_security' => 'Keamanan', + 'preferences_layout' => 'Tata ruang', + 'pref_home_show_deposits' => 'Tampilkan deposito pada layar awal', + 'pref_home_show_deposits_info' => 'Layar awal sudah menunjukkan rekening pengeluaran Anda. Harus itu juga menunjukkan akun pendapatan Anda?', + 'pref_home_do_show_deposits' => 'Ya, menunjukkan kepada mereka', + 'successful_count' => ':count menghitung sukses', + 'list_page_size_title' => 'Ukuran halaman', + 'list_page_size_help' => 'Setiap daftar hal-hal (rekening, transaksi, dll) menunjukkan paling ini banyak per halaman.', + 'list_page_size_label' => 'Ukuran halaman', + 'between_dates' => '(:start dan :end)', + 'pref_optional_fields_transaction' => 'Bidang opsional untuk transaksi', 'pref_optional_fields_transaction_help' => 'Secara default tidak semua bidang diaktifkan saat membuat transaksi baru (karena kekacauan). Di bawah, Anda dapat mengaktifkan bidang ini jika Anda berpikir mereka bisa berguna bagi Anda. Tentu saja, setiap bidang yang dinonaktifkan, tapi sudah diisi, akan terlihat terlepas dari pengaturan.', 'optional_tj_date_fields' => 'Bidang tanggal', 'optional_tj_business_fields' => 'Bidang usaha', @@ -579,24 +562,25 @@ return [ 'login_with_new_email' => 'Anda sekarang bisa masuk dengan alamat email baru Anda.', 'login_with_old_email' => 'Anda sekarang dapat login dengan alamat email lama Anda lagi.', 'login_provider_local_only' => 'This action is not available when authenticating through ":login_provider".', - 'delete_local_info_only' => 'Because you authenticate through ":login_provider", this will only delete local Firefly III information.', + 'delete_local_info_only' => 'Because you authenticate through ":login_provider", this will only delete local Firefly III information.', // attachments - 'nr_of_attachments' => 'Satu lampiran |:count lampiran', - 'attachments' => 'Lampiran', - 'edit_attachment' => 'Edit lampiran ":name"', - 'update_attachment' => 'Perbarui lampiran', - 'delete_attachment' => 'Hapus lampiran ":name"', - 'attachment_deleted' => 'Lampiran yang dihapus ":name"', - 'attachment_updated' => 'Lampiran yang diperbarui ":name"', - 'upload_max_file_size' => 'Ukuran file maksimum: :size', - 'list_all_attachments' => 'List of all attachments', + 'nr_of_attachments' => 'Satu lampiran |:count lampiran', + 'attachments' => 'Lampiran', + 'edit_attachment' => 'Edit lampiran ":name"', + 'update_attachment' => 'Perbarui lampiran', + 'delete_attachment' => 'Hapus lampiran ":name"', + 'attachment_deleted' => 'Lampiran yang dihapus ":name"', + 'liabilities_deleted' => 'Deleted liability ":name"', + 'attachment_updated' => 'Lampiran yang diperbarui ":name"', + 'upload_max_file_size' => 'Ukuran file maksimum: :size', + 'list_all_attachments' => 'List of all attachments', // transaction index - 'title_expenses' => 'Beban', - 'title_withdrawal' => 'Beban', - 'title_revenue' => 'Pendapatan / penghasilan', - 'title_deposit' => 'Pendapatan / penghasilan', + 'title_expenses' => 'Beban', + 'title_withdrawal' => 'Beban', + 'title_revenue' => 'Pendapatan / penghasilan', + 'title_deposit' => 'Pendapatan / penghasilan', 'title_transfer' => 'Transfer', 'title_transfers' => 'Transfer', @@ -634,6 +618,9 @@ return [ 'converted_to_Transfer' => 'Transaksi telah dikonversi menjadi transfer', 'invalid_convert_selection' => 'Akun yang telah Anda pilih sudah digunakan dalam transaksi ini atau tidak ada.', 'source_or_dest_invalid' => 'Cannot find the correct transaction details. Conversion is not possible.', + 'convert_to_withdrawal' => 'Convert to a withdrawal', + 'convert_to_deposit' => 'Convert to a deposit', + 'convert_to_transfer' => 'Convert to a transfer', // create new stuff: 'create_new_withdrawal' => 'Buat penarikan baru', @@ -695,7 +682,9 @@ return [ 'available_amount_indication' => 'Gunakan jumlah ini untuk mendapatkan indikasi berapa total anggaran Anda.', 'suggested' => 'Disarankan', 'average_between' => 'Rata-rata antara :start dan :end', - 'over_budget_warn' => ' Normally you budget about :amount per day. This is :over_amount per day.', + 'over_budget_warn' => ' Usually you budget about :amount per day. This time it\'s :over_amount per day. Are you sure?', + 'transferred_in' => 'Transferred (in)', + 'transferred_away' => 'Transferred (away)', // bills: 'match_between_amounts' => 'Bill matches transactions between :low and :high.', @@ -779,6 +768,7 @@ return [ 'reconcile_options' => 'Pilihan rekonsiliasi', 'reconcile_range' => 'Jangkauan rekonsiliasi', 'start_reconcile' => 'Mulai rekonsiliasi', + 'cash_account_type' => 'Cash', 'cash' => 'tunai', 'account_type' => 'Jenis akun', 'save_transactions_by_moving' => 'Menyimpan transaksi tersebut(s) dengan memindahkan mereka ke akun lain:', @@ -803,7 +793,9 @@ return [ 'reconcile_go_back' => 'Anda selalu dapat mengedit atau menghapus koreksi kemudian.', 'must_be_asset_account' => 'Anda hanya bisa mendamaikan akun aset', 'reconciliation_stored' => 'Rekonsiliasi disimpan', - 'reconcilliation_transaction_title' => 'Rekonsiliasi (:from ke :to)', + 'reconciliation_error' => 'Due to an error the transactions were marked as reconciled but the correction has not been stored: :error.', + 'reconciliation_transaction_title' => 'Reconciliation (:from to :to)', + 'sum_of_reconciliation' => 'Sum of reconciliation', 'reconcile_this_account' => 'Rekonsiliasi akun ini', 'confirm_reconciliation' => 'Konfirmasikan rekonsiliasi', 'submitted_start_balance' => 'Saldo awal yang dikirim', @@ -815,7 +807,7 @@ return [ 'interest_calc_daily' => 'Per day', 'interest_calc_monthly' => 'Per month', 'interest_calc_yearly' => 'Per year', - 'initial_balance_account' => 'Initial balance account of :name', + 'initial_balance_account' => 'Initial balance account of :account', // categories: 'new_category' => 'Kategori baru', @@ -847,6 +839,8 @@ return [ 'deleted_deposit' => 'Berhasil menghapus deposit ":description"', 'deleted_transfer' => 'Berhasil menghapus transfer ":description"', 'stored_journal' => 'Berhasil membuat transaksi baru ":description"', + 'stored_journal_no_descr' => 'Successfully created your new transaction', + 'updated_journal_no_descr' => 'Successfully updated your transaction', 'select_transactions' => 'Pilih transaksi', 'rule_group_select_transactions' => 'Terapkan ":title" untuk transaksi', 'rule_select_transactions' => 'Terapkan ":title" untuk transaksi', @@ -855,26 +849,33 @@ return [ 'mass_delete_journals' => 'Hapus sejumlah transaksi', 'mass_edit_journals' => 'Edit sejumlah transaksi', 'mass_bulk_journals' => 'Bulk edit a number of transactions', - 'mass_bulk_journals_explain' => 'If you do not want to change your transactions one-by-one using the mass-edit function, you can update them in one go. Simply select the preferred category, tag(s) or budget in the fields below, and all the transactions in the table will be updated.', + 'mass_bulk_journals_explain' => 'This form allows you to change properties of the transactions listed below in one sweeping update. All the transactions in the table will be updated when you change the parameters you see here.', + 'part_of_split' => 'This transaction is part of a split transaction. If you have not selected all the splits, you may end up with changing only half the transaction.', 'bulk_set_new_values' => 'Use the inputs below to set new values. If you leave them empty, they will be made empty for all. Also, note that only withdrawals will be given a budget.', 'no_bulk_category' => 'Don\'t update category', 'no_bulk_budget' => 'Don\'t update budget', - 'no_bulk_tags' => 'Don\'t update tag(s)', - 'bulk_edit' => 'Bulk edit', - 'cannot_edit_other_fields' => 'Anda tidak bisa menyunting bidang lain dari yang lain di sini, karena tidak ada ruang untuk ditunjukkan kepada mereka. Ikuti tautan dan edit dengan satu per satu, jika Anda perlu mengedit bidang ini.', - 'no_budget' => '(no budget)', - 'no_budget_squared' => '(tidak ada anggaran)', - 'perm-delete-many' => 'Menghapus banyak barang dalam satu go bisa sangat mengganggu. Harap berhati-hati.', - 'mass_deleted_transactions_success' => 'Dihapus:amount transaksi.', - 'mass_edited_transactions_success' => 'Diperbarui:amount transaksi', - 'opt_group_' => '(no account type)', + 'no_bulk_tags' => 'Don\'t update tag(s)', + 'mass_edit' => 'Edit selected individually', + 'bulk_edit' => 'Edit selected in bulk', + 'mass_delete' => 'Delete selected', + 'cannot_edit_other_fields' => 'Anda tidak bisa menyunting bidang lain dari yang lain di sini, karena tidak ada ruang untuk ditunjukkan kepada mereka. Ikuti tautan dan edit dengan satu per satu, jika Anda perlu mengedit bidang ini.', + 'cannot_change_amount_reconciled' => 'You can\'t change the amount of reconciled transactions.', + 'no_budget' => '(no budget)', + 'no_budget_squared' => '(tidak ada anggaran)', + 'perm-delete-many' => 'Deleting many items in one go can be very disruptive. Please be cautious. You can delete part of a split transaction from this page, so take care.', + 'mass_deleted_transactions_success' => 'Dihapus:amount transaksi.', + 'mass_edited_transactions_success' => 'Diperbarui:amount transaksi', + 'opt_group_' => '(no account type)', 'opt_group_no_account_type' => '(tidak ada jenis akun)', 'opt_group_defaultAsset' => 'Akun aset standar', 'opt_group_savingAsset' => 'Menyimpan akun', 'opt_group_sharedAsset' => 'Akun aset bersama', 'opt_group_ccAsset' => 'Kartu kredit', 'opt_group_cashWalletAsset' => 'Cash wallets', + 'opt_group_expense_account' => 'Expense accounts', + 'opt_group_revenue_account' => 'Revenue accounts', 'opt_group_l_Loan' => 'Liability: Loan', + 'opt_group_cash_account' => 'Cash account', 'opt_group_l_Debt' => 'Liability: Debt', 'opt_group_l_Mortgage' => 'Liability: Mortgage', 'opt_group_l_Credit card' => 'Liability: Credit card', @@ -973,7 +974,7 @@ return [ 'errors' => 'Kesalahan', 'debt_start_date' => 'Start date of debt', 'debt_start_amount' => 'Start amount of debt', - 'debt_start_amount_help' => 'Please enter the original amount of this liability as a positive number. You may also enter the current amount. Make sure to edit the date below to match.', + 'debt_start_amount_help' => 'If you owe an amount its best to enter a negative amount, because it influences your net worth. If you\'re owed an amount the same applies. Check out the help pages for more information.', 'store_new_liabilities_account' => 'Store new liability', 'edit_liabilities_account' => 'Edit liability ":name"', @@ -1145,6 +1146,7 @@ return [ 'deleted_piggy_bank' => 'Dihapus celengan ":name"', 'added_amount_to_piggy' => 'Ditambahkan:amount ke ":name"', 'removed_amount_from_piggy' => 'Dihapus:amount dari ":name"', + 'piggy_events' => 'Related piggy banks', // tags 'delete_tag' => 'Hapus tag " :tag"', @@ -1154,47 +1156,57 @@ return [ 'updated_tag' => 'Diperbarui tag " :tag"', 'created_tag' => 'Tag " :tag" telah dibuat!', - 'transaction_journal_information' => 'Informasi transaksi', - 'transaction_journal_meta' => 'Informasi meta', - 'total_amount' => 'Jumlah total', - 'number_of_decimals' => 'Jumlah desimal', + 'transaction_journal_information' => 'Informasi transaksi', + 'transaction_journal_meta' => 'Informasi meta', + 'transaction_journal_more' => 'More information', + 'att_part_of_journal' => 'Stored under ":journal"', + 'total_amount' => 'Jumlah total', + 'number_of_decimals' => 'Jumlah desimal', // administration - 'administration' => 'Administrasi', - 'user_administration' => 'Administrasi pengguna', - 'list_all_users' => 'Semua pengguna', - 'all_users' => 'Semua pengguna', - 'instance_configuration' => 'Konfigurasi', - 'firefly_instance_configuration' => 'Pilihan konfigurasi untuk Firefly III', - 'setting_single_user_mode' => 'Mode pengguna tunggal', - 'setting_single_user_mode_explain' => 'By default, Firefly III only accepts one (1) registration: you. This is a security measure, preventing others from using your instance unless you allow them to. Future registrations are blocked. When you uncheck this box, others can use your instance as well, assuming they can reach it (when it is connected to the internet).', - 'store_configuration' => 'Konfigurasi toko', - 'single_user_administration' => 'Administrasi pengguna untuk :email', - 'edit_user' => 'Edit pengguna :email', - 'hidden_fields_preferences' => 'Tidak semua bidang terlihat sekarang. Anda harus mengaktifkannya di setelan Anda.', - 'user_data_information' => 'Data pengguna', - 'user_information' => 'Informasi pengguna', - 'total_size' => 'ukuran total', - 'budget_or_budgets' => 'anggaran', - 'budgets_with_limits' => 'anggaran dengan jumlah yang dikonfigurasi', - 'nr_of_rules_in_total_groups' => ':count_rules aturan di:count_groups rule group (s)', - 'tag_or_tags' => 'tag (s)', - 'configuration_updated' => 'Konfigurasi telah diperbarui', - 'setting_is_demo_site' => 'Situs demo', - 'setting_is_demo_site_explain' => 'Jika Anda mencentang kotak ini, instalasi ini akan berperilaku seolah-olah itu adalah situs demo, yang dapat memiliki efek samping yang aneh.', - 'block_code_bounced' => 'Pesan email terpental', - 'block_code_expired' => 'Akun demo kadaluarsa', - 'no_block_code' => 'Tidak ada alasan untuk memblokir atau pengguna tidak diblokir', - 'block_code_email_changed' => 'Pengguna belum mengkonfirmasi alamat email baru', - 'admin_update_email' => 'Bertentangan dengan halaman profil, pengguna TIDAK akan diberitahu alamat email mereka telah berubah!', - 'update_user' => 'Perbarui pengguna', - 'updated_user' => 'Data pengguna telah diubah.', - 'delete_user' => 'Hapus pengguna :email', - 'user_deleted' => 'Pengguna telah dihapus', - 'send_test_email' => 'Kirim pesan email percobaan', - 'send_test_email_text' => 'Untuk melihat apakah pemasangan Anda mampu mengirim email, tekan tombol ini. Anda tidak akan melihat kesalahan di sini (jika ada), file log akan mencerminkan kesalahan. Anda bisa menekan tombol ini sebanyak yang Anda mau. Tidak ada kontrol spam. Pesan akan dikirim ke :email dan akan segera tiba.', - 'send_message' => 'Mengirim pesan', - 'send_test_triggered' => 'Uji dipicu. Periksa kotak masuk dan file log Anda.', + 'administration' => 'Administrasi', + 'user_administration' => 'Administrasi pengguna', + 'list_all_users' => 'Semua pengguna', + 'all_users' => 'Semua pengguna', + 'instance_configuration' => 'Konfigurasi', + 'firefly_instance_configuration' => 'Pilihan konfigurasi untuk Firefly III', + 'setting_single_user_mode' => 'Mode pengguna tunggal', + 'setting_single_user_mode_explain' => 'By default, Firefly III only accepts one (1) registration: you. This is a security measure, preventing others from using your instance unless you allow them to. Future registrations are blocked. When you uncheck this box, others can use your instance as well, assuming they can reach it (when it is connected to the internet).', + 'store_configuration' => 'Konfigurasi toko', + 'single_user_administration' => 'Administrasi pengguna untuk :email', + 'edit_user' => 'Edit pengguna :email', + 'hidden_fields_preferences' => 'You can enable more transaction options in your settings.', + 'user_data_information' => 'Data pengguna', + 'user_information' => 'Informasi pengguna', + 'total_size' => 'ukuran total', + 'budget_or_budgets' => 'anggaran', + 'budgets_with_limits' => 'anggaran dengan jumlah yang dikonfigurasi', + 'nr_of_rules_in_total_groups' => ':count_rules aturan di:count_groups rule group (s)', + 'tag_or_tags' => 'tag (s)', + 'configuration_updated' => 'Konfigurasi telah diperbarui', + 'setting_is_demo_site' => 'Situs demo', + 'setting_is_demo_site_explain' => 'Jika Anda mencentang kotak ini, instalasi ini akan berperilaku seolah-olah itu adalah situs demo, yang dapat memiliki efek samping yang aneh.', + 'block_code_bounced' => 'Pesan email terpental', + 'block_code_expired' => 'Akun demo kadaluarsa', + 'no_block_code' => 'Tidak ada alasan untuk memblokir atau pengguna tidak diblokir', + 'block_code_email_changed' => 'Pengguna belum mengkonfirmasi alamat email baru', + 'admin_update_email' => 'Bertentangan dengan halaman profil, pengguna TIDAK akan diberitahu alamat email mereka telah berubah!', + 'update_user' => 'Perbarui pengguna', + 'updated_user' => 'Data pengguna telah diubah.', + 'delete_user' => 'Hapus pengguna :email', + 'user_deleted' => 'Pengguna telah dihapus', + 'send_test_email' => 'Kirim pesan email percobaan', + 'send_test_email_text' => 'Untuk melihat apakah pemasangan Anda mampu mengirim email, tekan tombol ini. Anda tidak akan melihat kesalahan di sini (jika ada), file log akan mencerminkan kesalahan. Anda bisa menekan tombol ini sebanyak yang Anda mau. Tidak ada kontrol spam. Pesan akan dikirim ke :email dan akan segera tiba.', + 'send_message' => 'Mengirim pesan', + 'send_test_triggered' => 'Uji dipicu. Periksa kotak masuk dan file log Anda.', + + 'split_transaction_title' => 'Description of the split transaction', + 'split_title_help' => 'If you create a split transaction, there must be a global description for all splits of the transaction.', + 'transaction_information' => 'Transaction information', + 'you_create_transfer' => 'You\'re creating a transfer.', + 'you_create_withdrawal' => 'You\'re creating a withdrawal.', + 'you_create_deposit' => 'You\'re creating a deposit.', + // links 'journal_link_configuration' => 'Konfigurasi link transaksi', @@ -1214,12 +1226,13 @@ return [ 'do_not_save_connection' => '(jangan simpan koneksi)', 'link_transaction' => 'Transaksi link', 'link_to_other_transaction' => 'Tautkan transaksi ini ke transaksi lain', - 'select_transaction_to_link' => 'Pilih transaksi untuk menautkan transaksi ini', + 'select_transaction_to_link' => 'Select a transaction to link this transaction to. The links are currently unused in Firefly III (apart from being shown), but I plan to change this in the future. Use the search box to select a transaction either by title or by ID. If you want to add custom link types, check out the administration section.', 'this_transaction' => 'Transaksi ini', 'transaction' => 'Transaksi', 'comments' => 'Komentar', - 'to_link_not_found' => 'Jika transaksi yang ingin Anda taut tidak terdaftar, cukup masukkan ID-nya.', + 'link_notes' => 'Any notes you wish to store with the link.', 'invalid_link_selection' => 'Tidak dapat menautkan transaksi ini', + 'selected_transaction' => 'Selected transaction', 'journals_linked' => 'Transaksi terkait.', 'journals_error_linked' => 'Transaksi ini sudah tertaut.', 'journals_link_to_self' => 'You cannot link a transaction to itself', @@ -1258,12 +1271,11 @@ return [ 'split_this_withdrawal' => 'Pisahkan penarikan ini', 'split_this_deposit' => 'Pisahkan deposit ini', 'split_this_transfer' => 'Pisahkan transfer ini', - 'cannot_edit_multiple_source' => 'Anda tidak dapat mengedit transaksi splitted #:id dengan deskripsi ":description" karena berisi beberapa akun sumber.', - 'cannot_edit_multiple_dest' => 'Anda tidak dapat mengedit transaksi splitted #:id dengan deskripsi ":description" karena berisi beberapa akun tujuan.', - 'cannot_edit_reconciled' => 'Anda tidak dapat mengedit transaksi #:id dengan deskripsi ":description" karena telah ditandai sebagai didamaikan.', 'cannot_edit_opening_balance' => 'Anda tidak dapat mengedit saldo awal akun.', 'no_edit_multiple_left' => 'Anda tidak memilih transaksi yang sah untuk diedit.', - 'cannot_convert_split_journal' => 'Tidak dapat mengonversi transaksi split', + 'breadcrumb_convert_group' => 'Convert transaction', + 'convert_invalid_source' => 'Source information is invalid for transaction #%d.', + 'convert_invalid_destination' => 'Destination information is invalid for transaction #%d.', // Import page (general strings only) 'import_index_title' => 'Import transactions into Firefly III', @@ -1389,4 +1401,15 @@ return [ 'will_jump_monday' => 'Will be created on Monday instead of the weekends.', 'except_weekends' => 'Except weekends', 'recurrence_deleted' => 'Recurring transaction ":title" deleted', + + // new lines for summary controller. + 'box_balance_in_currency' => 'Balance (:currency)', + 'box_spent_in_currency' => 'Spent (:currency)', + 'box_earned_in_currency' => 'Earned (:currency)', + 'box_bill_paid_in_currency' => 'Bills paid (:currency)', + 'box_bill_unpaid_in_currency' => 'Bills unpaid (:currency)', + 'box_left_to_spend_in_currency' => 'Left to spend (:currency)', + 'box_net_worth_in_currency' => 'Net worth (:currency)', + 'box_spend_per_day' => 'Left to spend per day: :amount', + ]; diff --git a/resources/lang/id_ID/form.php b/resources/lang/id_ID/form.php index f2ecd16cb6..b9abb5bb3c 100644 --- a/resources/lang/id_ID/form.php +++ b/resources/lang/id_ID/form.php @@ -57,20 +57,21 @@ return [ 'asset_source_account' => 'Source account', 'journal_description' => 'Deskripsi', 'note' => 'Catatan', + 'store_new_transaction' => 'Store new transaction', 'split_journal' => 'Pisahkan transaksi ini', 'split_journal_explanation' => 'Split transaksi ini di banyak bagian', 'currency' => 'Mata uang', 'account_id' => 'Akun aset', 'budget_id' => 'Anggaran', - 'openingBalance' => 'Saldo awal', + 'opening_balance' => 'Opening balance', 'tagMode' => 'Mode Tag', 'tag_position' => 'Lokasi tag', - 'virtualBalance' => 'Saldo virtual', + 'virtual_balance' => 'Virtual balance', 'targetamount' => 'Jumlah target', - 'accountRole' => 'Peran akun', - 'openingBalanceDate' => 'Membuka tanggal saldo', - 'ccType' => 'Rencana pembayaran kartu kredit', - 'ccMonthlyPaymentDate' => 'Credit card monthly payment date', + 'account_role' => 'Account role', + 'opening_balance_date' => 'Opening balance date', + 'cc_type' => 'Credit card payment plan', + 'cc_monthly_payment_date' => 'Credit card monthly payment date', 'piggy_bank_id' => 'Celengan', 'returnHere' => 'Kembali ke sini', 'returnHereExplanation' => 'Setelah menyimpan, kembali ke sini untuk membuat yang lain.', @@ -118,7 +119,7 @@ return [ 'symbol' => 'Simbol', 'code' => 'Kode', 'iban' => 'IBAN', - 'accountNumber' => 'Nomor akun', + 'account_number' => 'Account number', 'creditCardNumber' => 'Nomor kartu kredit', 'has_headers' => 'Judul', 'date_format' => 'Format tanggal', @@ -139,12 +140,8 @@ return [ 'stop_processing' => 'Berhenti memproses', 'start_date' => 'Mulai dari jangkauan', 'end_date' => 'Akhir rentang', - 'export_start_range' => 'Mulai dari rentang ekspor', - 'export_end_range' => 'Akhir rentang ekspor', - 'export_format' => 'Format file', 'include_attachments' => 'Sertakan lampiran yang diunggah', 'include_old_uploads' => 'Sertakan data yang diimpor', - 'accounts' => 'Mengekspor transaksi dari akun ini', 'delete_account' => 'Delete account ":name"', 'delete_bill' => 'Hapus tagihan ":name"', 'delete_budget' => 'Hapus anggaran ":name"', @@ -256,4 +253,7 @@ return [ 'weekend' => 'Weekend', 'client_secret' => 'Client secret', + 'withdrawal_destination_id' => 'Destination account', + 'deposit_source_id' => 'Source account', + ]; diff --git a/resources/lang/id_ID/import.php b/resources/lang/id_ID/import.php index a03a310683..5bb250f079 100644 --- a/resources/lang/id_ID/import.php +++ b/resources/lang/id_ID/import.php @@ -207,6 +207,10 @@ return [ 'specific_rabo_descr' => 'Fixes potential problems with Rabobank files', 'specific_pres_name' => 'President\'s Choice Financial CA', 'specific_pres_descr' => 'Fixes potential problems with PC files', + 'specific_belfius_name' => 'Belfius BE', + 'specific_belfius_descr' => 'Fixes potential problems with Belfius files', + 'specific_ingbelgium_name' => 'ING BE', + 'specific_ingbelgium_descr' => 'Fixes potential problems with ING Belgium files', // job configuration for file provider (stage: roles) 'job_config_roles_title' => 'Import setup (3/4) - Define each column\'s role', 'job_config_roles_text' => 'Each column in your CSV file contains certain data. Please indicate what kind of data the importer should expect. The option to "map" data means that you will link each entry found in the column to a value in your database. An often mapped column is the column that contains the IBAN of the opposing account. That can be easily matched to IBAN\'s present in your database already.', @@ -291,14 +295,14 @@ return [ 'column_rabo-debit-credit' => 'Indikator debit / kredit khusus Rabobank', 'column_ing-debit-credit' => 'Indikator debit / kredit ING yang spesifik', 'column_generic-debit-credit' => 'Generic bank debit/credit indicator', - 'column_sepa-ct-id' => 'SEPA end-to-end Identifier', - 'column_sepa-ct-op' => 'SEPA Opposing Account Identifier', - 'column_sepa-db' => 'SEPA Mandate Identifier', - 'column_sepa-cc' => 'SEPA Clearing Code', - 'column_sepa-ci' => 'SEPA Creditor Identifier', - 'column_sepa-ep' => 'SEPA External Purpose', - 'column_sepa-country' => 'SEPA Country Code', - 'column_sepa-batch-id' => 'SEPA Batch ID', + 'column_sepa_ct_id' => 'SEPA end-to-end Identifier', + 'column_sepa_ct_op' => 'SEPA Opposing Account Identifier', + 'column_sepa_db' => 'SEPA Mandate Identifier', + 'column_sepa_cc' => 'SEPA Clearing Code', + 'column_sepa_ci' => 'SEPA Creditor Identifier', + 'column_sepa_ep' => 'SEPA External Purpose', + 'column_sepa_country' => 'SEPA Country Code', + 'column_sepa_batch_id' => 'SEPA Batch ID', 'column_tags-comma' => 'Tag (dipisahkan koma)', 'column_tags-space' => 'Tag (spasi terpisah)', 'column_account-number' => 'Akun aset (nomor rekening)', @@ -306,4 +310,7 @@ return [ 'column_note' => 'Catatan (s)', 'column_internal-reference' => 'Internal reference', + // error message + 'duplicate_row' => 'Row #:row (":description") could not be imported. It already exists.', + ]; diff --git a/resources/lang/id_ID/intro.php b/resources/lang/id_ID/intro.php index 4a5298ad16..b81f530b08 100644 --- a/resources/lang/id_ID/intro.php +++ b/resources/lang/id_ID/intro.php @@ -24,39 +24,61 @@ declare(strict_types=1); return [ // index - 'index_intro' => 'Selamat datang di halaman indeks Firefly III. Mohon luangkan waktu untuk menelusuri pengantar ini melihat bagaimana Firefly III bekerja.', - 'index_accounts-chart' => 'Bagan ini menunjukkan saldo akun aset anda saat ini, Anda dapat memilih akun yang terlihat di sini dalam preferensi anda.', - 'index_box_out_holder' => 'Kotak kecil dan kotak di samping kotak ini akan memberi anda gambaran singkat tentang situasi keuangan anda.', - 'index_help' => 'Jika anda memerlukan bantuan dengan halaman atau formulir, tekan tombol ini.', - 'index_outro' => 'Sebagian besar halaman Firefly III akan dimulai dengan petunjuk kecil seperti ini. Silahkan hubungi saya bila ada pertanyaan atau komentar. Selamat mencoba!', - 'index_sidebar-toggle' => 'Untuk membuat transakisi baru, akun atau hal lainnya, gunakan menu di bawah gambar ini.', + 'index_intro' => 'Selamat datang di halaman indeks Firefly III. Mohon luangkan waktu untuk menelusuri pengantar ini melihat bagaimana Firefly III bekerja.', + 'index_accounts-chart' => 'Bagan ini menunjukkan saldo akun aset anda saat ini, Anda dapat memilih akun yang terlihat di sini dalam preferensi anda.', + 'index_box_out_holder' => 'Kotak kecil dan kotak di samping kotak ini akan memberi anda gambaran singkat tentang situasi keuangan anda.', + 'index_help' => 'Jika anda memerlukan bantuan dengan halaman atau formulir, tekan tombol ini.', + 'index_outro' => 'Sebagian besar halaman Firefly III akan dimulai dengan petunjuk kecil seperti ini. Silahkan hubungi saya bila ada pertanyaan atau komentar. Selamat mencoba!', + 'index_sidebar-toggle' => 'Untuk membuat transakisi baru, akun atau hal lainnya, gunakan menu di bawah gambar ini.', + 'index_cash_account' => 'These are the accounts created so far. You can use the cash account to track cash expenses but it\'s not mandatory of course.', + + // transactions (withdrawal) + 'transactions_create_withdrawal_source' => 'Select your favorite asset account or liability from this dropdown.', + 'transactions_create_withdrawal_destination' => 'Select an expense account here. Leave it empty if you want to make a cash expense.', + 'transactions_create_withdrawal_foreign_currency' => 'Use this field to set a foreign currency and amount.', + 'transactions_create_withdrawal_more_meta' => 'Plenty of other meta data you set in these fields.', + 'transactions_create_withdrawal_split_add' => 'If you want to split a transaction, add more splits with this button', + + // transactions (deposit) + 'transactions_create_deposit_source' => 'Select or type the payee in this auto-completing dropdown/textbox. Leave it empty if you want to make a cash deposit.', + 'transactions_create_deposit_destination' => 'Select an asset or liability account here.', + 'transactions_create_deposit_foreign_currency' => 'Use this field to set a foreign currency and amount.', + 'transactions_create_deposit_more_meta' => 'Plenty of other meta data you set in these fields.', + 'transactions_create_deposit_split_add' => 'If you want to split a transaction, add more splits with this button', + + // transactions (transfer) + 'transactions_create_transfer_source' => 'Select the source asset account here.', + 'transactions_create_transfer_destination' => 'Select the destination asset account here.', + 'transactions_create_transfer_foreign_currency' => 'Use this field to set a foreign currency and amount.', + 'transactions_create_transfer_more_meta' => 'Plenty of other meta data you set in these fields.', + 'transactions_create_transfer_split_add' => 'If you want to split a transaction, add more splits with this button', // create account: - 'accounts_create_iban' => 'Berikan akun anda IBAN yang benar. Hal ini bisa membuat data impor sangat mudah di masa yang akan datang.', - 'accounts_create_asset_opening_balance' => 'Rekening aset boleh memiliki "saldo awal", yang menandakan dimulainya riwayat rekening ini di Firefly III.', - 'accounts_create_asset_currency' => 'Firelfly III mendukung berbagai mata uang. Akun aset memiliki satu mata uang utama, yang yang harus anda tetapkan.', - 'accounts_create_asset_virtual' => 'Kadang-kadang itu dapat membantu memberi akun anda saldo virtual: jumlah tambahan yang selalu ditambahkan atau dihapus dari saldo sebenarnya.', + 'accounts_create_iban' => 'Berikan akun anda IBAN yang benar. Hal ini bisa membuat data impor sangat mudah di masa yang akan datang.', + 'accounts_create_asset_opening_balance' => 'Rekening aset boleh memiliki "saldo awal", yang menandakan dimulainya riwayat rekening ini di Firefly III.', + 'accounts_create_asset_currency' => 'Firelfly III mendukung berbagai mata uang. Akun aset memiliki satu mata uang utama, yang yang harus anda tetapkan.', + 'accounts_create_asset_virtual' => 'Kadang-kadang itu dapat membantu memberi akun anda saldo virtual: jumlah tambahan yang selalu ditambahkan atau dihapus dari saldo sebenarnya.', // budgets index - 'budgets_index_intro' => 'Anggaran yang digunakan untuk mengelola keuangan anda dan membentuk salah satu fungsi inti dari Firefly III.', - 'budgets_index_set_budget' => 'Tetapkan total anggaran Anda untuk setiap periode sehingga Firefly III dapat menginformasikan apakah semua uang Anda sudah dianggarkan atau belum.', - 'budgets_index_see_expenses_bar' => 'Menghabiskan uang secara perlahan dengan mengisi di bar ini.', - 'budgets_index_navigate_periods' => 'Menavigasi melalui periode dengan mudah menetapkan anggaraan sebelumnya.', - 'budgets_index_new_budget' => 'Buat anggaran baru sesuai keinginan anda.', - 'budgets_index_list_of_budgets' => 'Gunakan tabel ini untuk menetapkan jumlah setiap anggaran daan melihat bagaimana keadaan anda.', - 'budgets_index_outro' => 'Untuk mempelajari lebih lanjut tentang anggaran, periksa ikon bantuan di pojok kanan atas.', + 'budgets_index_intro' => 'Anggaran yang digunakan untuk mengelola keuangan anda dan membentuk salah satu fungsi inti dari Firefly III.', + 'budgets_index_set_budget' => 'Tetapkan total anggaran Anda untuk setiap periode sehingga Firefly III dapat menginformasikan apakah semua uang Anda sudah dianggarkan atau belum.', + 'budgets_index_see_expenses_bar' => 'Menghabiskan uang secara perlahan dengan mengisi di bar ini.', + 'budgets_index_navigate_periods' => 'Menavigasi melalui periode dengan mudah menetapkan anggaraan sebelumnya.', + 'budgets_index_new_budget' => 'Buat anggaran baru sesuai keinginan anda.', + 'budgets_index_list_of_budgets' => 'Gunakan tabel ini untuk menetapkan jumlah setiap anggaran daan melihat bagaimana keadaan anda.', + 'budgets_index_outro' => 'Untuk mempelajari lebih lanjut tentang anggaran, periksa ikon bantuan di pojok kanan atas.', // reports (index) - 'reports_index_intro' => 'Gunakan laporan ini untuk mendapatkan wawasan terperinci dalam keuangan anda.', - 'reports_index_inputReportType' => 'Pilih sebuah jenis laporan. Periksa bantuan halaman untuk melihat apa yang ditunjukkan pada laporan anda.', - 'reports_index_inputAccountsSelect' => 'Anda dapat mengecualikan atau menyertakan akun aset sesuai keinginan anda.', - 'reports_index_inputDateRange' => 'Rentang tanggal yang dipilih sepenuhnya terserah anda: dari satu hari sampai 10 tahun.', - 'reports_index_extra-options-box' => 'Bergantung pada laporan yang anda pilih, anda dapat memilih filter dan opsi tambahan di sini. Lihat kotak ini saat anda mengubah jenis laporan.', + 'reports_index_intro' => 'Gunakan laporan ini untuk mendapatkan wawasan terperinci dalam keuangan anda.', + 'reports_index_inputReportType' => 'Pilih sebuah jenis laporan. Periksa bantuan halaman untuk melihat apa yang ditunjukkan pada laporan anda.', + 'reports_index_inputAccountsSelect' => 'Anda dapat mengecualikan atau menyertakan akun aset sesuai keinginan anda.', + 'reports_index_inputDateRange' => 'Rentang tanggal yang dipilih sepenuhnya terserah anda: dari satu hari sampai 10 tahun.', + 'reports_index_extra-options-box' => 'Bergantung pada laporan yang anda pilih, anda dapat memilih filter dan opsi tambahan di sini. Lihat kotak ini saat anda mengubah jenis laporan.', // reports (reports) - 'reports_report_default_intro' => 'Laporan ini akan memberi gambaran singkat tentang keuanggan anda secara cepat dan menyeluruh. Jika anda ingin melihat yang lain, jangan ragu untuk menghubungi saya!', - 'reports_report_audit_intro' => 'Laporan ini memberikan anda pengetahuan rnci dalam akun aset anda.', - 'reports_report_audit_optionsBox' => 'Gunakan kotak centang ini untuk menampilkan atau menyembunyikan kolom yang anda suka.', + 'reports_report_default_intro' => 'Laporan ini akan memberi gambaran singkat tentang keuanggan anda secara cepat dan menyeluruh. Jika anda ingin melihat yang lain, jangan ragu untuk menghubungi saya!', + 'reports_report_audit_intro' => 'Laporan ini memberikan anda pengetahuan rnci dalam akun aset anda.', + 'reports_report_audit_optionsBox' => 'Gunakan kotak centang ini untuk menampilkan atau menyembunyikan kolom yang anda suka.', 'reports_report_category_intro' => 'Laporan ini akan memberi anda pengetahuan pada satu atau beberapa kategori.', 'reports_report_category_pieCharts' => 'Bagan ini memberi anda pengetahuan tentang biaya dan pendapatan per kategori atau per akun.', diff --git a/resources/lang/id_ID/list.php b/resources/lang/id_ID/list.php index 8015bf90ba..7005c6d772 100644 --- a/resources/lang/id_ID/list.php +++ b/resources/lang/id_ID/list.php @@ -23,71 +23,71 @@ declare(strict_types=1); return [ - 'buttons' => 'Tombol', - 'icon' => 'Ikon', - 'id' => 'ID', - 'create_date' => 'Dibuat pada', - 'update_date' => 'Diperbarui pada', - 'updated_at' => 'Diperbarui pada', - 'balance_before' => 'Saldo Sebelumnya', - 'balance_after' => 'Saldo setelahnya', - 'name' => 'Nama', - 'role' => 'Peran', - 'currentBalance' => 'Saldo saat ini', - 'linked_to_rules' => 'Aturan yang relevan', - 'active' => 'Aktif?', - 'lastActivity' => 'Aktifitas terakhir', - 'balanceDiff' => 'Perbedaan saldo', - 'matchesOn' => 'Cocok di', - 'account_type' => 'Jenis akun', - 'created_at' => 'Dibuat di', - 'account' => 'Akun', - 'matchingAmount' => 'Jumlah', - 'split_number' => 'Split #', - 'destination' => 'Tujuan', - 'source' => 'Sumber', - 'next_expected_match' => 'Transaksi yang diharapkan berikutnya', - 'automatch' => 'Pencocokan otomatis?', - 'repeat_freq' => 'Berulang', - 'description' => 'Deskripsi', - 'amount' => 'Jumlah', - 'internal_reference' => 'Referensi Internal', - 'date' => 'Tanggal', - 'interest_date' => 'Tanggal Bunga', - 'book_date' => 'Tanggal Buku', - 'process_date' => 'Tanggal pemrosesan', - 'due_date' => 'Tenggat waktu', - 'payment_date' => 'Tanggal pembayaran', - 'invoice_date' => 'Tanggal Faktur', - 'interal_reference' => 'Referensi Internal', - 'notes' => 'Catatan', - 'from' => 'Dari', - 'piggy_bank' => 'Celengan', - 'to' => 'Untuk', - 'budget' => 'Anggaran', - 'category' => 'Kategori', - 'bill' => 'Tagihan', - 'withdrawal' => 'Penarikan', - 'deposit' => 'Simpanan', - 'transfer' => 'Transfer', - 'type' => 'Jenis', - 'completed' => 'Lengkap', - 'iban' => 'IBAN', - 'paid_current_period' => 'Membayar periode ini', - 'email' => 'Email', - 'registered_at' => 'Terdaftar di', - 'is_blocked' => 'Diblokir', - 'is_admin' => 'Apakah admin', - 'has_two_factor' => 'Memiliki 2FA', - 'blocked_code' => 'Kode blok', - 'source_account' => 'Akun sumber', + 'buttons' => 'Tombol', + 'icon' => 'Ikon', + 'id' => 'ID', + 'create_date' => 'Dibuat pada', + 'update_date' => 'Diperbarui pada', + 'updated_at' => 'Diperbarui pada', + 'balance_before' => 'Saldo Sebelumnya', + 'balance_after' => 'Saldo setelahnya', + 'name' => 'Nama', + 'role' => 'Peran', + 'currentBalance' => 'Saldo saat ini', + 'linked_to_rules' => 'Aturan yang relevan', + 'active' => 'Aktif?', + 'transaction_type' => 'Type', + 'lastActivity' => 'Aktifitas terakhir', + 'balanceDiff' => 'Perbedaan saldo', + 'matchesOn' => 'Cocok di', + 'account_type' => 'Jenis akun', + 'created_at' => 'Dibuat di', + 'account' => 'Akun', + 'matchingAmount' => 'Jumlah', + 'split_number' => 'Split #', + 'destination' => 'Tujuan', + 'source' => 'Sumber', + 'next_expected_match' => 'Transaksi yang diharapkan berikutnya', + 'automatch' => 'Pencocokan otomatis?', + 'repeat_freq' => 'Berulang', + 'description' => 'Deskripsi', + 'amount' => 'Jumlah', + 'internal_reference' => 'Referensi Internal', + 'date' => 'Tanggal', + 'interest_date' => 'Tanggal Bunga', + 'book_date' => 'Tanggal Buku', + 'process_date' => 'Tanggal pemrosesan', + 'due_date' => 'Tenggat waktu', + 'payment_date' => 'Tanggal pembayaran', + 'invoice_date' => 'Tanggal Faktur', + 'interal_reference' => 'Referensi Internal', + 'notes' => 'Catatan', + 'from' => 'Dari', + 'piggy_bank' => 'Celengan', + 'to' => 'Untuk', + 'budget' => 'Anggaran', + 'category' => 'Kategori', + 'bill' => 'Tagihan', + 'withdrawal' => 'Penarikan', + 'deposit' => 'Simpanan', + 'transfer' => 'Transfer', + 'type' => 'Jenis', + 'completed' => 'Lengkap', + 'iban' => 'IBAN', + 'paid_current_period' => 'Membayar periode ini', + 'email' => 'Email', + 'registered_at' => 'Terdaftar di', + 'is_blocked' => 'Diblokir', + 'is_admin' => 'Apakah admin', + 'has_two_factor' => 'Memiliki 2FA', + 'blocked_code' => 'Kode blok', + 'source_account' => 'Akun sumber', 'destination_account' => 'Akun tujuan', 'accounts_count' => 'Jumlah rekening', 'journals_count' => 'Jumlah transaksi', 'attachments_count' => 'Jumlah lampiran', 'bills_count' => 'Jumlah tagihan', 'categories_count' => 'Jumlah kategori', - 'export_jobs_count' => 'Jumlah pekerjaan ekspor', 'import_jobs_count' => 'Jumlah pekerjaan impor', 'budget_count' => 'Jumlah anggaran', 'rule_and_groups_count' => 'Jumlah aturan dan kelompok aturan', @@ -106,14 +106,14 @@ return [ 'account_on_spectre' => 'Rekening (Spectre)', 'account_on_ynab' => 'Rekening (YNAB)', 'do_import' => 'Ambil dari rekening ini', - 'sepa-ct-id' => 'SEPA End to End Identifier', - 'sepa-ct-op' => 'SEPA Opposing Account Identifier', - 'sepa-db' => 'SEPA Mandate Identifier', - 'sepa-country' => 'Negara SEPA', - 'sepa-cc' => 'SEPA Clearing Code', - 'sepa-ep' => 'SEPA External Purpose', - 'sepa-ci' => 'SEPA Creditor Identifier', - 'sepa-batch-id' => 'SEPA Batch ID', + 'sepa_ct_id' => 'SEPA End to End Identifier', + 'sepa_ct_op' => 'SEPA Opposing Account Identifier', + 'sepa_db' => 'SEPA Mandate Identifier', + 'sepa_country' => 'SEPA Country', + 'sepa_cc' => 'SEPA Clearing Code', + 'sepa_ep' => 'SEPA External Purpose', + 'sepa_ci' => 'SEPA Creditor Identifier', + 'sepa_batch_id' => 'SEPA Batch ID', 'external_id' => 'ID Eksternal', 'account_at_bunq' => 'Rekening dengan bunq', 'file_name' => 'Nama file', diff --git a/resources/lang/id_ID/validation.php b/resources/lang/id_ID/validation.php index 3335e37474..3cd54a47f0 100644 --- a/resources/lang/id_ID/validation.php +++ b/resources/lang/id_ID/validation.php @@ -33,15 +33,19 @@ return [ 'rule_trigger_value' => 'Nilai ini tidak validi untuk trigger yang dipilih.', 'rule_action_value' => 'Nilai ini tidak valid untuk tindakan yang dipilih.', 'file_already_attached' => 'Upload file ";name" sudah terpasang pada objek ini.', - 'file_attached' => 'File yang diupload dengan sukses ":name.', + 'file_attached' => 'Successfully uploaded file ":name".', 'must_exist' => 'ID di bidang :attribute tidak ada di database.', 'all_accounts_equal' => 'Semua akun di bidang ini harus sama.', + 'group_title_mandatory' => 'A group title is mandatory when there is more than one transaction.', + 'transaction_types_equal' => 'All splits must be of the same type.', + 'invalid_transaction_type' => 'Invalid transaction type.', 'invalid_selection' => 'Pilihan Anda tidak valid.', 'belongs_user' => 'Nilai ini tidak sah untuk bidang ini.', 'at_least_one_transaction' => 'Minimal harus ada satu transaksi.', 'at_least_one_repetition' => 'Minimal harus ada satu pengulangan.', 'require_repeat_until' => 'Dibutuhkan hanya sebuah angka pengulangan, atau tanggal akhir (repeat_until). Bukan keduanya.', 'require_currency_info' => 'Isi dalam bidang ini tidak valid jika tidak disertai informasi mata uang.', + 'require_currency_amount' => 'The content of this field is invalid without foreign amount information.', 'equal_description' => 'Deskripsi transaksi harus berbeda dari deskripsi umum.', 'file_invalid_mime' => 'File ":name" adalah tipe ":mime" yang tidak diterima sebagai upload baru.', 'file_too_large' => 'File "; name" terlalu besar.', @@ -135,8 +139,8 @@ return [ 'name' => 'nama', 'piggy_bank_id' => 'celengan ID', 'targetamount' => 'target dana', - 'openingBalanceDate' => 'tanggal saldo awal', - 'openingBalance' => 'saldo awal', + 'opening_balance_date' => 'opening balance date', + 'opening_balance' => 'opening balance', 'match' => 'cocok', 'amount_min' => 'jumlah minimal', 'amount_max' => 'jumlah maksimal', @@ -164,4 +168,28 @@ return [ 'rule-trigger.4' => 'pemicu aturan #4', 'rule-trigger.5' => 'pemicu aturan #5', ], + + // validation of accounts: + 'withdrawal_source_need_data' => 'Need to get a valid source account ID and/or valid source account name to continue.', + 'withdrawal_source_bad_data' => 'Could not find a valid source account when searching for ID ":id" or name ":name".', + 'withdrawal_dest_need_data' => 'Need to get a valid destination account ID and/or valid destination account name to continue.', + 'withdrawal_dest_bad_data' => 'Could not find a valid destination account when searching for ID ":id" or name ":name".', + + 'deposit_source_need_data' => 'Need to get a valid source account ID and/or valid source account name to continue.', + 'deposit_source_bad_data' => 'Could not find a valid source account when searching for ID ":id" or name ":name".', + 'deposit_dest_need_data' => 'Need to get a valid destination account ID and/or valid destination account name to continue.', + 'deposit_dest_bad_data' => 'Could not find a valid destination account when searching for ID ":id" or name ":name".', + + 'transfer_source_need_data' => 'Need to get a valid source account ID and/or valid source account name to continue.', + 'transfer_source_bad_data' => 'Could not find a valid source account when searching for ID ":id" or name ":name".', + 'transfer_dest_need_data' => 'Need to get a valid destination account ID and/or valid destination account name to continue.', + 'transfer_dest_bad_data' => 'Could not find a valid destination account when searching for ID ":id" or name ":name".', + 'need_id_in_edit' => 'Each split must have transaction_journal_id (either valid ID or 0).', + + 'ob_source_need_data' => 'Need to get a valid source account ID and/or valid source account name to continue.', + 'ob_dest_need_data' => 'Need to get a valid destination account ID and/or valid destination account name to continue.', + 'ob_dest_bad_data' => 'Could not find a valid destination account when searching for ID ":id" or name ":name".', + + 'generic_invalid_source' => 'You can\'t use this account as the source account.', + 'generic_invalid_destination' => 'You can\'t use this account as the destination account.', ]; diff --git a/resources/lang/it_IT/breadcrumbs.php b/resources/lang/it_IT/breadcrumbs.php index 33192a2311..18a47302eb 100644 --- a/resources/lang/it_IT/breadcrumbs.php +++ b/resources/lang/it_IT/breadcrumbs.php @@ -23,35 +23,37 @@ declare(strict_types=1); return [ - 'home' => 'Pagina principale', - 'edit_currency' => 'Modifica valuta ":name"', - 'delete_currency' => 'Elimina valuta ":name"', - 'newPiggyBank' => 'Crea un nuovo salvadanaio', - 'edit_piggyBank' => 'Modifica salvadanaio ":name"', - 'preferences' => 'Preferenze', - 'profile' => 'Profilo', - 'changePassword' => 'Cambia la tua password', - 'change_email' => 'Cambia il tuo indirizzo email', - 'bills' => 'Bollette', - 'newBill' => 'Nuova bolletta', - 'edit_bill' => 'Modifica bolletta ":name"', - 'delete_bill' => 'Elimina bolletta ":name"', - 'reports' => 'Resoconti', - 'search_result' => 'Risultati di ricerca per ":query"', - 'withdrawal_list' => 'Spese, uscite e prelievi', - 'deposit_list' => 'Redditi, entrate e depositi', - 'transfer_list' => 'Trasferimenti', - 'transfers_list' => 'Trasferimenti', - 'reconciliation_list' => 'Riconciliazioni', - 'create_withdrawal' => 'Crea nuova uscita', - 'create_deposit' => 'Crea nuova entrata', - 'create_transfer' => 'Crea nuovo trasferimento', - 'edit_journal' => 'Modifica transazione ":description"', - 'edit_reconciliation' => 'Modifica ":description"', - 'delete_journal' => 'Elimina transazione ":description"', - 'tags' => 'Etichette', - 'createTag' => 'Crea nuova etichetta', - 'edit_tag' => 'Modifica etichetta ":tag"', - 'delete_tag' => 'Elimina etichetta ":tag"', - 'delete_journal_link' => 'Elimina il collegamento tra le transazioni', + 'home' => 'Pagina principale', + 'edit_currency' => 'Modifica valuta ":name"', + 'delete_currency' => 'Elimina valuta ":name"', + 'newPiggyBank' => 'Crea un nuovo salvadanaio', + 'edit_piggyBank' => 'Modifica salvadanaio ":name"', + 'preferences' => 'Preferenze', + 'profile' => 'Profilo', + 'changePassword' => 'Cambia la tua password', + 'change_email' => 'Cambia il tuo indirizzo email', + 'bills' => 'Bollette', + 'newBill' => 'Nuova bolletta', + 'edit_bill' => 'Modifica bolletta ":name"', + 'delete_bill' => 'Elimina bolletta ":name"', + 'reports' => 'Resoconti', + 'search_result' => 'Risultati di ricerca per ":query"', + 'withdrawal_list' => 'Spese, uscite e prelievi', + 'Withdrawal_list' => 'Uscite', + 'deposit_list' => 'Redditi, entrate e depositi', + 'transfer_list' => 'Trasferimenti', + 'transfers_list' => 'Trasferimenti', + 'reconciliation_list' => 'Riconciliazioni', + 'create_withdrawal' => 'Crea nuova uscita', + 'create_deposit' => 'Crea nuova entrata', + 'create_transfer' => 'Crea nuovo trasferimento', + 'create_new_transaction' => 'Crea una nuova transazione', + 'edit_journal' => 'Modifica transazione ":description"', + 'edit_reconciliation' => 'Modifica ":description"', + 'delete_journal' => 'Elimina transazione ":description"', + 'tags' => 'Etichette', + 'createTag' => 'Crea nuova etichetta', + 'edit_tag' => 'Modifica etichetta ":tag"', + 'delete_tag' => 'Elimina etichetta ":tag"', + 'delete_journal_link' => 'Elimina il collegamento tra le transazioni', ]; diff --git a/resources/lang/it_IT/firefly.php b/resources/lang/it_IT/firefly.php index ccffdcff32..3b07d6b7b6 100644 --- a/resources/lang/it_IT/firefly.php +++ b/resources/lang/it_IT/firefly.php @@ -33,7 +33,7 @@ return [ 'last_seven_days' => 'Ultimi sette giorni', 'last_thirty_days' => 'Ultimi trenta giorni', 'welcomeBack' => 'La tua situazione finanziaria', - 'welcome_back' => 'La tua situazione finanziaria', + 'welcome_back' => 'La tua situazione finanziaria', 'everything' => 'Tutto', 'today' => 'oggi', 'customRange' => 'Intervallo personalizzato', @@ -54,6 +54,7 @@ return [ 'create_new_stuff' => 'Crea nuove cose', 'new_withdrawal' => 'Nuova uscita', 'create_new_transaction' => 'Crea nuova transazione', + 'new_transaction' => 'Nuova transazione', 'go_to_asset_accounts' => 'Visualizza i tuoi conti attività', 'go_to_budgets' => 'Vai ai tuoi budget', 'go_to_categories' => 'Vai alle tue categorie', @@ -75,35 +76,39 @@ return [ 'flash_info' => 'Messaggio', 'flash_warning' => 'Avviso!', 'flash_error' => 'Errore!', - 'flash_info_multiple' => 'C\'è un messaggio | Ci sono :count messages', + 'flash_info_multiple' => 'C\'è un messaggio | Ci sono :count messaggi', 'flash_error_multiple' => 'C\'è un errore | Ci sono :count errors', 'net_worth' => 'Patrimonio', 'route_has_no_help' => 'Non c\'è aiuto per questa rotta.', 'help_for_this_page' => 'Aiuto per questa pagina', 'no_help_could_be_found' => 'Non è stato trovato alcun testo di aiuto.', 'no_help_title' => 'Ci scusiamo, si è verificato un errore.', - 'two_factor_welcome' => 'Ciao, :user!', - 'two_factor_enter_code' => 'Per continuare inserisci il tuo codice di autenticazione a due fattori. La tua applicazione può generarlo per te.', - 'two_factor_code_here' => 'Inserisci qui il codice', - 'two_factor_title' => 'Autenticazione a due fattori', - 'authenticate' => 'Autenticati', - 'two_factor_forgot_title' => 'Autenticazione a due fattori persa', - 'two_factor_forgot' => 'Ho dimenticato la mia chiave a due fattori.', - 'two_factor_lost_header' => 'Hai perso l\'autenticazione a due fattori?', - 'two_factor_lost_intro' => 'Sfortunatamente, questo non è qualcosa che puoi resettare dall\'interfaccia web. Hai due scelte.', - 'two_factor_lost_fix_self' => 'Se si esegue la propria istanza di Firefly III, controllare i log in storage/logs per istruzioni.', - 'two_factor_lost_fix_owner' => 'In caso contrario, invia un mail al proprietario del sito, :site_owner, e chiedi loro di resettare l\'autenticazione a due fattori.', - 'warning_much_data' => ':days di caricamento dei dati potrebbero richiedere un pò di tempo.', - 'registered' => 'Ti sei registrato con successo!', - 'Default asset account' => 'Conto attività predefinito', - 'no_budget_pointer' => 'Sembra che tu non abbia ancora dei budget. Dovresti crearne alcuni nella pagina dei budget. I budget possono aiutarti a tenere traccia delle spese.', - 'Savings account' => 'Conti risparmio', - 'Credit card' => 'Carta di credito', - 'source_accounts' => 'Conti origine', - 'destination_accounts' => 'Conti destinazione', - 'user_id_is' => 'Il tuo ID utente è :user', - 'field_supports_markdown' => 'Questo campo supporta Markdown.', - 'need_more_help' => 'Se hai bisogno di ulteriore aiuto con Firefly III, ti preghiamo di aprire un ticket su Github.', + 'two_factor_welcome' => 'Ciao!', + 'two_factor_enter_code' => 'Per continuare inserisci il tuo codice di autenticazione a due fattori. La tua applicazione può generarlo per te.', + 'two_factor_code_here' => 'Inserisci qui il codice', + 'two_factor_title' => 'Autenticazione a due fattori', + 'authenticate' => 'Autenticati', + 'two_factor_forgot_title' => 'Autenticazione a due fattori persa', + 'two_factor_forgot' => 'Ho dimenticato la mia chiave a due fattori.', + 'two_factor_lost_header' => 'Hai perso l\'autenticazione a due fattori?', + 'two_factor_lost_intro' => 'If you lost your backup codes as well, you have bad luck. This is not something you can fix from the web interface. You have two choices.', + 'two_factor_lost_fix_self' => 'If you run your own instance of Firefly III, check the logs in storage/logs for instructions, or run docker logs <container_id> to see the instructions (refresh this page).', + 'two_factor_lost_fix_owner' => 'In caso contrario, invia un mail al proprietario del sito, :site_owner, e chiedi loro di resettare l\'autenticazione a due fattori.', + 'mfa_backup_code' => 'You have used a backup code to login to Firefly III. It can\'t be used again, so cross it from your list.', + 'pref_two_factor_new_backup_codes' => 'Get new backup codes', + 'pref_two_factor_backup_code_count' => 'You have :count valid backup code(s).', + '2fa_i_have_them' => 'I stored them!', + 'warning_much_data' => ':days di caricamento dei dati potrebbero richiedere un pò di tempo.', + 'registered' => 'Ti sei registrato con successo!', + 'Default asset account' => 'Conto attività predefinito', + 'no_budget_pointer' => 'Sembra che tu non abbia ancora dei budget. Dovresti crearne alcuni nella pagina dei budget. I budget possono aiutarti a tenere traccia delle spese.', + 'Savings account' => 'Conti risparmio', + 'Credit card' => 'Carta di credito', + 'source_accounts' => 'Conti origine', + 'destination_accounts' => 'Conti destinazione', + 'user_id_is' => 'Il tuo ID utente è :user', + 'field_supports_markdown' => 'Questo campo supporta Markdown.', + 'need_more_help' => 'Se hai bisogno di ulteriore aiuto con Firefly III, ti preghiamo di aprire un ticket su Github.', 'reenable_intro_text' => 'Puoi anche riattivare la guida introduttiva.', 'intro_boxes_after_refresh' => 'Le caselle di introduzione riappariranno quando si aggiorna la pagina.', 'show_all_no_filter' => 'Mostra tutte le transazioni senza raggrupparle per data.', @@ -220,21 +225,23 @@ return [ 'search_query' => 'Domanda', 'search_found_transactions' => 'Firefly III ha trovato :count transazioni in :time secondi.', 'search_for_query' => 'Firefly III sta cercando le transazioni contenenti tutte queste parole: :query', - 'search_modifier_amount_is' => 'L\'importo è esattamente :value', - 'search_modifier_amount' => 'L\'importo è esattamente :value', - 'search_modifier_amount_max' => 'L\'importo è al massimo :value', - 'search_modifier_amount_min' => 'L\'importo è almeno :value', - 'search_modifier_amount_less' => 'L\'importo è inferiore a :value', - 'search_modifier_amount_more' => 'L\'importo è superiore a :value', - 'search_modifier_source' => 'Il conto di origine è :value', - 'search_modifier_destination' => 'Il conto di destinazione è :value', - 'search_modifier_category' => 'La categoria è :value', - 'search_modifier_budget' => 'Il budget è :value', - 'search_modifier_bill' => 'La bolletta è :value', - 'search_modifier_type' => 'Il tipo di transazione è :value', - 'search_modifier_date' => 'La data della transazione è :value', - 'search_modifier_date_before' => 'La data della transazione è antecedente a :value', - 'search_modifier_date_after' => 'La data della transazione è successiva a :value', + 'search_modifier_amount_is' => 'L\'importo è esattamente :value', + 'search_modifier_amount' => 'L\'importo è esattamente :value', + 'search_modifier_amount_max' => 'L\'importo è al massimo :value', + 'search_modifier_amount_min' => 'L\'importo è almeno :value', + 'search_modifier_amount_less' => 'L\'importo è inferiore a :value', + 'search_modifier_amount_more' => 'L\'importo è superiore a :value', + 'search_modifier_source' => 'Il conto di origine è :value', + 'search_modifier_from' => 'Source account is :value', + 'search_modifier_destination' => 'Il conto di destinazione è :value', + 'search_modifier_to' => 'Destination account is :value', + 'search_modifier_category' => 'La categoria è :value', + 'search_modifier_budget' => 'Il budget è :value', + 'search_modifier_bill' => 'La bolletta è :value', + 'search_modifier_type' => 'Il tipo di transazione è :value', + 'search_modifier_date' => 'La data della transazione è :value', + 'search_modifier_date_before' => 'La data della transazione è antecedente a :value', + 'search_modifier_date_after' => 'La data della transazione è successiva a :value', 'search_modifier_on' => 'La data della transazione è :value', 'search_modifier_before' => 'La data della transazione è antecedente a :value', 'search_modifier_after' => 'La data della transazione è successiva a :value', @@ -257,33 +264,6 @@ return [ 'half-year' => 'ogni sei mesi', 'yearly' => 'annuale', - // export data: - 'import_and_export' => 'Importa e esporta', - 'export_data' => 'Esporta dati', - 'export_and_backup_data' => 'Esporta dati', - 'export_data_intro' => 'Utilizza i dati esportati per passare a una nuova applicazione finanziaria. Si noti che questi file non sono intesi come backup. Non contengono abbastanza metadati per ripristinare completamente una nuova installazione di Firefly III. Se desideri eseguire un backup dei dati, esegui direttamente il backup del database.', - 'export_format' => 'Formato esportazione', - 'export_format_csv' => 'Valori separati da virgola (file CSV)', - 'export_format_mt940' => 'Formato compatibile MT940', - 'include_old_uploads_help' => 'Firefly III non getta via i file CSV originali importati in passato. Puoi includerli nell\'esportazione.', - 'do_export' => 'Esporta', - 'export_status_never_started' => 'L\'esportazione non è ancora iniziata', - 'export_status_make_exporter' => 'Creazione esportatore...', - 'export_status_collecting_journals' => 'Raccolta delle tue transazioni...', - 'export_status_collected_journals' => 'Raccolto le tue transazioni!', - 'export_status_converting_to_export_format' => 'Conversione delle tue transazioni...', - 'export_status_converted_to_export_format' => 'Convertite le vostre transazioni!', - 'export_status_creating_journal_file' => 'Creazione del file di esportazione...', - 'export_status_created_journal_file' => 'File di esportazione creato!', - 'export_status_collecting_attachments' => 'Raccolta di tutti i tuoi allegati...', - 'export_status_collected_attachments' => 'Raccolti tutti i tuoi allegati!', - 'export_status_collecting_old_uploads' => 'Raccolta di tutti i tuoi caricamenti precedenti...', - 'export_status_collected_old_uploads' => 'Raccolti tutti i tuoi caricamenti precedenti!', - 'export_status_creating_zip_file' => 'Creazione di un file zip...', - 'export_status_created_zip_file' => 'File zip creato!', - 'export_status_finished' => 'L\'esportazione è terminata con successo! Evviva!', - 'export_data_please_wait' => 'Attendere prego...', - // rules 'rules' => 'Regole', 'rule_name' => 'Nome regola', @@ -376,7 +356,7 @@ return [ 'rule_trigger_amount_exactly_choice' => 'L\'importo è...', 'rule_trigger_amount_exactly' => 'L\'importo è :trigger_value', 'rule_trigger_amount_more_choice' => 'L\'importo è più di...', - 'rule_trigger_amount_more' => 'L\'importo è più di :trigger_value', + 'rule_trigger_amount_more' => 'L\'importo è superiore a :trigger_value', 'rule_trigger_description_starts_choice' => 'La descrizione inizia con...', 'rule_trigger_description_starts' => 'La descrizione inizia con ":trigger_value"', 'rule_trigger_description_ends_choice' => 'La descrizione termina con...', @@ -387,7 +367,7 @@ return [ 'rule_trigger_description_is' => 'La descrizione è ":trigger_value"', 'rule_trigger_budget_is_choice' => 'Il budget è...', 'rule_trigger_budget_is' => 'Il budget è ":trigger_value"', - 'rule_trigger_tag_is_choice' => '(A) tag è..', + 'rule_trigger_tag_is_choice' => 'Un tag è...', 'rule_trigger_tag_is' => 'Una etichetta è ":trigger_value"', 'rule_trigger_currency_is_choice' => 'La valuta della transazione è...', 'rule_trigger_currency_is' => 'La valuta della transazione è ":trigger_value"', @@ -502,30 +482,33 @@ return [ 'pref_custom_fiscal_year_help' => 'Nei paesi che utilizzano un anno finanziario diverso rispetto al dal 1 gennaio al 31 dicembre, è possibile attivarlo e specificare i giorni di inizio / fine dell\'anno fiscale', 'pref_fiscal_year_start_label' => 'Data di inizio anno fiscale', 'pref_two_factor_auth' => 'Verifica in due passaggi', - 'pref_two_factor_auth_help' => 'Quando abiliti la verifica a due passaggi (nota anche come autenticazione a due fattori), aggiungi un ulteriore livello di sicurezza al tuo account. Accedi con qualcosa che conosci (la tua password) e qualcosa che hai (un codice di verifica). I codici di verifica sono generati da un\'applicazione sul telefono, come Authy o Google Authenticator.', - 'pref_enable_two_factor_auth' => 'Abilita la verifica in due passaggi', - 'pref_two_factor_auth_disabled' => 'Codice di verifica in due passaggi rimosso e disabilitato', - 'pref_two_factor_auth_remove_it' => 'Non dimenticare di rimuovere l\'account dalla tua app di autenticazione!', - 'pref_two_factor_auth_code' => 'Verificare il codice', - 'pref_two_factor_auth_code_help' => 'Esegui la scansione del codice QR con un\'applicazione sul tuo telefono come Authy o Google Authenticator e inserisci il codice generato.', - 'pref_two_factor_auth_reset_code' => 'Reimposta il codice di verifica', - 'pref_two_factor_auth_disable_2fa' => 'Disattiva 2FA', - '2fa_use_secret_instead' => 'Se non puoi scansionare il codice QR puoi utilizzare il segreto: :secret.', - 'pref_save_settings' => 'Salva le impostazioni', - 'saved_preferences' => 'Preferenze salvate!', - 'preferences_general' => 'Generale', - 'preferences_frontpage' => 'Pagina principale', - 'preferences_security' => 'Sicurezza', - 'preferences_layout' => 'Impaginazione', - 'pref_home_show_deposits' => 'Mostra i depositi nella pagina principale', - 'pref_home_show_deposits_info' => 'La pagina iniziale mostra già i tuoi conti spese. Vuoi che mostri anche i tuoi conti entrate?', - 'pref_home_do_show_deposits' => 'Sì, mostrali', - 'successful_count' => 'di cui :count con successo', - 'list_page_size_title' => 'Dimensioni pagina', - 'list_page_size_help' => 'Ogni elenco (di conti, di transazioni, ecc) mostra al massimo questo numero di elementi per pagina.', - 'list_page_size_label' => 'Dimensioni pagina', - 'between_dates' => '(:start e :end)', - 'pref_optional_fields_transaction' => 'Campi opzionali per le transazioni', + 'pref_two_factor_auth_help' => 'Quando abiliti la verifica a due passaggi (nota anche come autenticazione a due fattori), aggiungi un ulteriore livello di sicurezza al tuo account. Accedi con qualcosa che conosci (la tua password) e qualcosa che hai (un codice di verifica). I codici di verifica sono generati da un\'applicazione sul telefono, come Authy o Google Authenticator.', + 'pref_enable_two_factor_auth' => 'Abilita la verifica in due passaggi', + 'pref_two_factor_auth_disabled' => 'Codice di verifica in due passaggi rimosso e disabilitato', + 'pref_two_factor_auth_remove_it' => 'Non dimenticare di rimuovere l\'account dalla tua app di autenticazione!', + 'pref_two_factor_auth_code' => 'Verificare il codice', + 'pref_two_factor_auth_code_help' => 'Esegui la scansione del codice QR con un\'applicazione sul tuo telefono come Authy o Google Authenticator e inserisci il codice generato.', + 'pref_two_factor_auth_reset_code' => 'Reimposta il codice di verifica', + 'pref_two_factor_auth_disable_2fa' => 'Disattiva 2FA', + '2fa_use_secret_instead' => 'If you cannot scan the QR code, feel free to use the secret instead: :secret.', + '2fa_backup_codes' => 'Store these backup codes for access in case you lose your device.', + '2fa_already_enabled' => '2-step verification is already enabled.', + 'wrong_mfa_code' => 'This MFA code is not valid.', + 'pref_save_settings' => 'Salva le impostazioni', + 'saved_preferences' => 'Preferenze salvate!', + 'preferences_general' => 'Generale', + 'preferences_frontpage' => 'Pagina principale', + 'preferences_security' => 'Sicurezza', + 'preferences_layout' => 'Impaginazione', + 'pref_home_show_deposits' => 'Mostra i depositi nella pagina principale', + 'pref_home_show_deposits_info' => 'La pagina iniziale mostra già i tuoi conti spese. Vuoi che mostri anche i tuoi conti entrate?', + 'pref_home_do_show_deposits' => 'Sì, mostrali', + 'successful_count' => 'di cui :count con successo', + 'list_page_size_title' => 'Dimensioni pagina', + 'list_page_size_help' => 'Ogni elenco (di conti, di transazioni, ecc) mostra al massimo questo numero di elementi per pagina.', + 'list_page_size_label' => 'Dimensioni pagina', + 'between_dates' => '(:start e :end)', + 'pref_optional_fields_transaction' => 'Campi opzionali per le transazioni', 'pref_optional_fields_transaction_help' => 'Per impostazione predefinita, non tutti i campi sono abilitati quando si crea una nuova transazione (a causa della confusione). Di seguito, puoi abilitare questi campi se ritieni che possano essere utili per te. Ovviamente, qualsiasi campo che è disabilitato, ma già compilato, sarà visibile indipendentemente dall\'impostazione.', 'optional_tj_date_fields' => 'Campi data', 'optional_tj_business_fields' => 'Campi aziendali', @@ -579,24 +562,25 @@ return [ 'login_with_new_email' => 'Ora puoi accedere con il tuo nuovo indirizzo email.', 'login_with_old_email' => 'Ora puoi accedere nuovamente con il tuo vecchio indirizzo email.', 'login_provider_local_only' => 'Questa azione non è disponibile quando ci si è autenticati con ":login_provider".', - 'delete_local_info_only' => 'Poiché ti sei autenticato tramite ":login_provider", questo eliminerà solamente le informazioni locali di Firefly III.', + 'delete_local_info_only' => 'Poiché ti sei autenticato tramite ":login_provider", questo eliminerà solamente le informazioni locali di Firefly III.', // attachments - 'nr_of_attachments' => 'Un allegato|:count allegati', - 'attachments' => 'Allegati', - 'edit_attachment' => 'Modifica allegato ":name"', - 'update_attachment' => 'Aggiorna allegati', - 'delete_attachment' => 'Elimina allegato ":name"', - 'attachment_deleted' => 'Allegato eliminato ":name"', - 'attachment_updated' => 'Allegato ":name" aggiornato', - 'upload_max_file_size' => 'Dimensione massima del file: :size', - 'list_all_attachments' => 'Lista di tutti gli allegati', + 'nr_of_attachments' => 'Un allegato|:count allegati', + 'attachments' => 'Allegati', + 'edit_attachment' => 'Modifica allegato ":name"', + 'update_attachment' => 'Aggiorna allegati', + 'delete_attachment' => 'Elimina allegato ":name"', + 'attachment_deleted' => 'Allegato eliminato ":name"', + 'liabilities_deleted' => 'Deleted liability ":name"', + 'attachment_updated' => 'Allegato ":name" aggiornato', + 'upload_max_file_size' => 'Dimensione massima del file: :size', + 'list_all_attachments' => 'Lista di tutti gli allegati', // transaction index - 'title_expenses' => 'Spese', - 'title_withdrawal' => 'Spese', - 'title_revenue' => 'Entrate', - 'title_deposit' => 'Redditi / entrate', + 'title_expenses' => 'Spese', + 'title_withdrawal' => 'Spese', + 'title_revenue' => 'Entrate', + 'title_deposit' => 'Redditi / entrate', 'title_transfer' => 'Trasferimenti', 'title_transfers' => 'Trasferimenti', @@ -634,6 +618,9 @@ return [ 'converted_to_Transfer' => 'La transazione è stata convertita in un trasferimento', 'invalid_convert_selection' => 'Il conto che hai selezionato è già utilizzato in questa transazione o non esiste.', 'source_or_dest_invalid' => 'Impossibile trovare i dettagli corretti della transazione. Non è possibile effettuare la conversione.', + 'convert_to_withdrawal' => 'Converti in prelievo', + 'convert_to_deposit' => 'Converti in entrata', + 'convert_to_transfer' => 'Converti in trasferimento', // create new stuff: 'create_new_withdrawal' => 'Crea una nuova uscita', @@ -695,7 +682,9 @@ return [ 'available_amount_indication' => 'Utilizza questi importi per ottenere un\'indicazione di quale potrebbe essere il tuo budget totale.', 'suggested' => 'Consigliato', 'average_between' => 'Media tra :start e :end', - 'over_budget_warn' => 'Normalmente il tuo budget giornaliero è :amount. Questo è :over_amount al giorno.', + 'over_budget_warn' => ' Di solito metti a budget circa :amount al giorno. Questa volta è :over_amount al giorno. Sei sicuro?', + 'transferred_in' => 'Trasferito (ingresso)', + 'transferred_away' => 'Trasferito (uscita)', // bills: 'match_between_amounts' => 'La bolletta abbina le transazioni tra :low e :high.', @@ -779,6 +768,7 @@ return [ 'reconcile_options' => 'Opzioni di riconciliazione', 'reconcile_range' => 'Intervallo di riconciliazione', 'start_reconcile' => 'Inizia la riconciliazione', + 'cash_account_type' => 'Cash', 'cash' => 'contanti', 'account_type' => 'Tipo conto', 'save_transactions_by_moving' => 'Salva queste transazioni spostandole in un altro conto:', @@ -803,7 +793,9 @@ return [ 'reconcile_go_back' => 'Puoi sempre modificare o eliminare una correzione in un secondo momento.', 'must_be_asset_account' => 'È possibile riconciliare solo i conti attività', 'reconciliation_stored' => 'Riconciliazione memorizzata', - 'reconcilliation_transaction_title' => 'Riconciliazione (:from a :to)', + 'reconciliation_error' => 'A causa di un errore le transazioni sono state contrassegnate come riconciliate ma la correzione non è stata memorizzata: :error.', + 'reconciliation_transaction_title' => 'Riconciliazione (:from - :to)', + 'sum_of_reconciliation' => 'Somma riconciliazione', 'reconcile_this_account' => 'Riconcilia questo conto', 'confirm_reconciliation' => 'Conferma riconciliazione', 'submitted_start_balance' => 'Saldo iniziale inserito', @@ -815,7 +807,7 @@ return [ 'interest_calc_daily' => 'Al giorno', 'interest_calc_monthly' => 'Al mese', 'interest_calc_yearly' => 'All\'anno', - 'initial_balance_account' => 'Saldo iniziale del conto :name', + 'initial_balance_account' => 'Saldo iniziale del conto :account', // categories: 'new_category' => 'Nuova categoria', @@ -847,6 +839,8 @@ return [ 'deleted_deposit' => 'Entrata ":description" eliminata correttamente', 'deleted_transfer' => 'Trasferimento ":description" eliminato correttamente', 'stored_journal' => 'Nuova transazione ":description" creata correttamente', + 'stored_journal_no_descr' => 'Hai creato con successo la nuova transazione', + 'updated_journal_no_descr' => 'Transazione aggiornata con successo', 'select_transactions' => 'Seleziona transazioni', 'rule_group_select_transactions' => 'Applica ":title" a transazioni', 'rule_select_transactions' => 'Applica ":title" a transazioni', @@ -855,26 +849,33 @@ return [ 'mass_delete_journals' => 'Elimina un certo numero di transazioni', 'mass_edit_journals' => 'Modifica un certo numero di transazioni', 'mass_bulk_journals' => 'Modifica in blocco un certo numero di transazioni', - 'mass_bulk_journals_explain' => 'Se non si desidera modificare le transazioni una alla volta utilizzando la funzione di modifica in blocco, è possibile aggiornarle in una volta sola. Basta selezionare le categorie, le etichette o i budget preferiti nei campi sottostanti e tutte le transazioni nella tabella verranno aggiornate.', + 'mass_bulk_journals_explain' => 'This form allows you to change properties of the transactions listed below in one sweeping update. All the transactions in the table will be updated when you change the parameters you see here.', + 'part_of_split' => 'This transaction is part of a split transaction. If you have not selected all the splits, you may end up with changing only half the transaction.', 'bulk_set_new_values' => 'Usa gli inserimenti qui sotto per impostare nuovi valori. Se li lasci vuoti, saranno resi vuoti per tutti. Inoltre, si noti che solo i prelievi avranno un budget.', 'no_bulk_category' => 'Non aggiornare la categoria', 'no_bulk_budget' => 'Non aggiornare il budget', - 'no_bulk_tags' => 'Non aggiornare le etichette', - 'bulk_edit' => 'Modifica in blocco', - 'cannot_edit_other_fields' => 'Non puoi modificare in blocco altri campi oltre a quelli presenti perché non c\'è spazio per mostrarli. Segui il link e modificali uno per uno se è necessario modificare questi campi.', - 'no_budget' => '(nessun budget)', - 'no_budget_squared' => '(nessun budget)', - 'perm-delete-many' => 'Eliminare molti oggetti in una volta sola può essere molto pericoloso. Per favore sii cauto.', - 'mass_deleted_transactions_success' => ':amount transazioni eliminate .', - 'mass_edited_transactions_success' => ':amount transazioni aggiornate', - 'opt_group_' => '(nessun tipo di conto)', + 'no_bulk_tags' => 'Non aggiornare le etichette', + 'mass_edit' => 'Edit selected individually', + 'bulk_edit' => 'Edit selected in bulk', + 'mass_delete' => 'Delete selected', + 'cannot_edit_other_fields' => 'Non puoi modificare in blocco altri campi oltre a quelli presenti perché non c\'è spazio per mostrarli. Segui il link e modificali uno per uno se è necessario modificare questi campi.', + 'cannot_change_amount_reconciled' => 'You can\'t change the amount of reconciled transactions.', + 'no_budget' => '(nessun budget)', + 'no_budget_squared' => '(nessun budget)', + 'perm-delete-many' => 'Deleting many items in one go can be very disruptive. Please be cautious. You can delete part of a split transaction from this page, so take care.', + 'mass_deleted_transactions_success' => ':amount transazioni eliminate .', + 'mass_edited_transactions_success' => ':amount transazioni aggiornate', + 'opt_group_' => '(nessun tipo di conto)', 'opt_group_no_account_type' => '(nessun tipo di conto)', 'opt_group_defaultAsset' => 'Conto attività predefinito', 'opt_group_savingAsset' => 'Conti risparmio', 'opt_group_sharedAsset' => 'Conti attività condivisi', 'opt_group_ccAsset' => 'Carte di credito', 'opt_group_cashWalletAsset' => 'Portafogli', + 'opt_group_expense_account' => 'Expense accounts', + 'opt_group_revenue_account' => 'Revenue accounts', 'opt_group_l_Loan' => 'Passività: Prestito', + 'opt_group_cash_account' => 'Cash account', 'opt_group_l_Debt' => 'Passività: Debito', 'opt_group_l_Mortgage' => 'Passività: Mutuo', 'opt_group_l_Credit card' => 'Passività: Carta di credito', @@ -973,7 +974,7 @@ return [ 'errors' => 'Errori', 'debt_start_date' => 'Data di inizio del debito', 'debt_start_amount' => 'Importo iniziale del debito', - 'debt_start_amount_help' => 'Inserisci l\'importo originario di questa passività come un numero positivo. Puoi anche inserire l\'importo corrente. Assicurati di modificare i dati sottostanti in modo che corrispondano.', + 'debt_start_amount_help' => 'Se tu devi dei soldi è meglio che inserisci un importo negativo poiché questo influenza il tuo patrimonio netto. Se ti devono dei soldi, vale la stessa cosa. Controlla le pagine d\'aiuto per maggiori informazioni.', 'store_new_liabilities_account' => 'Memorizza nuova passività', 'edit_liabilities_account' => 'Modica passività ":name"', @@ -1145,6 +1146,7 @@ return [ 'deleted_piggy_bank' => 'Salvadanaio eliminato ":name"', 'added_amount_to_piggy' => 'Aggiunto :amount a ":name"', 'removed_amount_from_piggy' => 'Rimosso :amount a ":name"', + 'piggy_events' => 'Salvadanai correlati', // tags 'delete_tag' => 'Elimina etichetta ":tag"', @@ -1154,47 +1156,57 @@ return [ 'updated_tag' => 'Etichetta ":tag" aggiornata', 'created_tag' => 'Etichetta ":tag" creata correttamente', - 'transaction_journal_information' => 'Informazioni transazione', - 'transaction_journal_meta' => 'Meta informazioni', - 'total_amount' => 'Importo totale', - 'number_of_decimals' => 'Cifre decimali', + 'transaction_journal_information' => 'Informazioni transazione', + 'transaction_journal_meta' => 'Meta informazioni', + 'transaction_journal_more' => 'Altre informazioni', + 'att_part_of_journal' => 'Memorizzato sotto ":journal"', + 'total_amount' => 'Importo totale', + 'number_of_decimals' => 'Cifre decimali', // administration - 'administration' => 'Amministrazione', - 'user_administration' => 'Amministrazione utenti', - 'list_all_users' => 'Tutti gli utenti', - 'all_users' => 'Tutti gli utenti', - 'instance_configuration' => 'Configurazione', - 'firefly_instance_configuration' => 'Opzioni di configurazione di Firefly III', - 'setting_single_user_mode' => 'Modo utente singolo', - 'setting_single_user_mode_explain' => 'Per impostazione predefinita, Firefly III accetta solo una (1) registrazione: tu. Questa è una misura di sicurezza, che impedisce ad altri di usare la tua istanza a meno che tu non le autorizzi. Le future registrazioni sono bloccate. Bene! quando deselezioni questa casella, gli altri possono usare la tua istanza, supponendo che possano raggiungerla (quando è connessa a Internet).', - 'store_configuration' => 'Salva configurazione', - 'single_user_administration' => 'Amministrazione utenti per :email', - 'edit_user' => 'Modifica utente :email', - 'hidden_fields_preferences' => 'Non tutti i campi sono visibili in questo momento. Devi attivarli nelle impostazioni.', - 'user_data_information' => 'Dati utente', - 'user_information' => 'Informazioni Utente', - 'total_size' => 'dimensione totale', - 'budget_or_budgets' => 'budget', - 'budgets_with_limits' => 'budget con importo configurato', - 'nr_of_rules_in_total_groups' => ':count_rules regole in :count_groups gruppi di regole', - 'tag_or_tags' => 'etichette', - 'configuration_updated' => 'La configurazione è stata aggiornata', - 'setting_is_demo_site' => 'Sito Demo', - 'setting_is_demo_site_explain' => 'Se si seleziona questa casella, questa installazione si comporterà come se fosse il sito demo, che può avere strani effetti collaterali.', - 'block_code_bounced' => 'Messaggi email respinti', - 'block_code_expired' => 'Conto demo scaduto', - 'no_block_code' => 'Nessun motivo per bloccare o non bloccare un utente', - 'block_code_email_changed' => 'L\'utente non ha ancora confermato il nuovo indirizzo emails', - 'admin_update_email' => 'Contrariamente alla pagina del profilo, l\'utente NON riceverà alcuna notifica al proprio indirizzo email!', - 'update_user' => 'Aggiorna utente', - 'updated_user' => 'I dati dell\'utente sono stati modificati.', - 'delete_user' => 'Elimina utente :email', - 'user_deleted' => 'L\'utente è stato eliminato', - 'send_test_email' => 'Invia un messaggio di posta elettronica di prova', - 'send_test_email_text' => 'Per vedere se la tua installazione è in grado di inviare e-mail, ti preghiamo di premere questo pulsante. Qui non vedrai un errore (se presente), i file di log rifletteranno eventuali errori. Puoi premere questo pulsante tutte le volte che vuoi. Non c\'è controllo dello spam. Il messaggio verrà inviato a :email e dovrebbe arrivare a breve.', - 'send_message' => 'Invia messaggio', - 'send_test_triggered' => 'Il test è stato attivato. Controlla la tua casella di posta e i file di log.', + 'administration' => 'Amministrazione', + 'user_administration' => 'Amministrazione utenti', + 'list_all_users' => 'Tutti gli utenti', + 'all_users' => 'Tutti gli utenti', + 'instance_configuration' => 'Configurazione', + 'firefly_instance_configuration' => 'Opzioni di configurazione di Firefly III', + 'setting_single_user_mode' => 'Modo utente singolo', + 'setting_single_user_mode_explain' => 'Per impostazione predefinita, Firefly III accetta solo una (1) registrazione: tu. Questa è una misura di sicurezza, che impedisce ad altri di usare la tua istanza a meno che tu non le autorizzi. Le future registrazioni sono bloccate. Bene! quando deselezioni questa casella, gli altri possono usare la tua istanza, supponendo che possano raggiungerla (quando è connessa a Internet).', + 'store_configuration' => 'Salva configurazione', + 'single_user_administration' => 'Amministrazione utenti per :email', + 'edit_user' => 'Modifica utente :email', + 'hidden_fields_preferences' => 'Puoi abilitare maggiori opzioni per le transazioni nelle tue impostazioni.', + 'user_data_information' => 'Dati utente', + 'user_information' => 'Informazioni Utente', + 'total_size' => 'dimensione totale', + 'budget_or_budgets' => 'budget', + 'budgets_with_limits' => 'budget con importo configurato', + 'nr_of_rules_in_total_groups' => ':count_rules regole in :count_groups gruppi di regole', + 'tag_or_tags' => 'etichette', + 'configuration_updated' => 'La configurazione è stata aggiornata', + 'setting_is_demo_site' => 'Sito Demo', + 'setting_is_demo_site_explain' => 'Se si seleziona questa casella, questa installazione si comporterà come se fosse il sito demo, che può avere strani effetti collaterali.', + 'block_code_bounced' => 'Messaggi email respinti', + 'block_code_expired' => 'Conto demo scaduto', + 'no_block_code' => 'Nessun motivo per bloccare o non bloccare un utente', + 'block_code_email_changed' => 'L\'utente non ha ancora confermato il nuovo indirizzo emails', + 'admin_update_email' => 'Contrariamente alla pagina del profilo, l\'utente NON riceverà alcuna notifica al proprio indirizzo email!', + 'update_user' => 'Aggiorna utente', + 'updated_user' => 'I dati dell\'utente sono stati modificati.', + 'delete_user' => 'Elimina utente :email', + 'user_deleted' => 'L\'utente è stato eliminato', + 'send_test_email' => 'Invia un messaggio di posta elettronica di prova', + 'send_test_email_text' => 'Per vedere se la tua installazione è in grado di inviare e-mail, ti preghiamo di premere questo pulsante. Qui non vedrai un errore (se presente), i file di log rifletteranno eventuali errori. Puoi premere questo pulsante tutte le volte che vuoi. Non c\'è controllo dello spam. Il messaggio verrà inviato a :email e dovrebbe arrivare a breve.', + 'send_message' => 'Invia messaggio', + 'send_test_triggered' => 'Il test è stato attivato. Controlla la tua casella di posta e i file di log.', + + 'split_transaction_title' => 'Descrizione della transazione suddivisa', + 'split_title_help' => 'Se crei una transazione suddivisa è necessario che ci sia una descrizione globale per tutte le suddivisioni della transazione.', + 'transaction_information' => 'Informazioni transazione', + 'you_create_transfer' => 'Stai creando un trasferimento.', + 'you_create_withdrawal' => 'Stai creando un prelievo.', + 'you_create_deposit' => 'Stai creando una entrata.', + // links 'journal_link_configuration' => 'Configurazione dei collegamenti di una transazione', @@ -1214,12 +1226,13 @@ return [ 'do_not_save_connection' => '(non salvare la connessione)', 'link_transaction' => 'Collega transazione', 'link_to_other_transaction' => 'Collega questa transazione a un\'altra transazione', - 'select_transaction_to_link' => 'Seleziona una transazione per collegare questa transazione a', + 'select_transaction_to_link' => 'Select a transaction to link this transaction to. The links are currently unused in Firefly III (apart from being shown), but I plan to change this in the future. Use the search box to select a transaction either by title or by ID. If you want to add custom link types, check out the administration section.', 'this_transaction' => 'Questa transazione', 'transaction' => 'Transazione', 'comments' => 'Commenti', - 'to_link_not_found' => 'Se la transazione a cui desideri effettuare il collegamento non è elencata, inserisci semplicemente il suo ID.', + 'link_notes' => 'Any notes you wish to store with the link.', 'invalid_link_selection' => 'Impossibile collegare queste transazioni', + 'selected_transaction' => 'Selected transaction', 'journals_linked' => 'Le transazioni sono collegate.', 'journals_error_linked' => 'Queste transazioni sono già collegate.', 'journals_link_to_self' => 'Non puoi collegare una transazione con se stessa', @@ -1258,12 +1271,11 @@ return [ 'split_this_withdrawal' => 'Dividi questo prelievo', 'split_this_deposit' => 'Dividi questa entrata', 'split_this_transfer' => 'Dividi questo trasferimento', - 'cannot_edit_multiple_source' => 'Non è possibile modificare la transazione n. #:id con la descrizione ":description" perché contiene più conti di origine.', - 'cannot_edit_multiple_dest' => 'Non è possibile modificare la transazione n. #:id con la descrizione ":description" perché contiene più conti di destinazione.', - 'cannot_edit_reconciled' => 'Non è possibile modificare la transazione n. #:id con la descrizione ":description" perché è stata contrassegnata come riconciliata.', 'cannot_edit_opening_balance' => 'Non è possibile modificare il saldo di apertura di un conto.', 'no_edit_multiple_left' => 'Non hai selezionato transazioni valide da modificare.', - 'cannot_convert_split_journal' => 'Impossibile convertire una transazione divisa', + 'breadcrumb_convert_group' => 'Convert transaction', + 'convert_invalid_source' => 'Source information is invalid for transaction #%d.', + 'convert_invalid_destination' => 'Destination information is invalid for transaction #%d.', // Import page (general strings only) 'import_index_title' => 'Importa le transazioni in Firefly III', @@ -1389,4 +1401,15 @@ return [ 'will_jump_monday' => 'Verrà creata il lunedì anziché il fine settimana.', 'except_weekends' => 'Tranne il fine settimana', 'recurrence_deleted' => 'La transazione ricorrente ":title" è stata eliminata', + + // new lines for summary controller. + 'box_balance_in_currency' => 'Saldo (:currency)', + 'box_spent_in_currency' => 'Spesi (:currency)', + 'box_earned_in_currency' => 'Guadagnati (:currency)', + 'box_bill_paid_in_currency' => 'Bollette pagate (:currency)', + 'box_bill_unpaid_in_currency' => 'Bollette non pagate (:currency)', + 'box_left_to_spend_in_currency' => 'Disponibile da spendere (:currency)', + 'box_net_worth_in_currency' => 'Patrimonio netto (:currency)', + 'box_spend_per_day' => 'Disponibile da spendere per giorno: :amount', + ]; diff --git a/resources/lang/it_IT/form.php b/resources/lang/it_IT/form.php index 5e5a2a2fd9..4ffb0c408b 100644 --- a/resources/lang/it_IT/form.php +++ b/resources/lang/it_IT/form.php @@ -57,20 +57,21 @@ return [ 'asset_source_account' => 'Conto di origine', 'journal_description' => 'Descrizione', 'note' => 'Note', + 'store_new_transaction' => 'Memorizza la nuova transazione', 'split_journal' => 'Dividi questa transazione', 'split_journal_explanation' => 'Dividi questa transazione in più parti', 'currency' => 'Valuta', 'account_id' => 'Conto attività', 'budget_id' => 'Budget', - 'openingBalance' => 'Saldo di apertura', + 'opening_balance' => 'Saldo di apertura', 'tagMode' => 'Modalità etichetta', 'tag_position' => 'Posizione etichetta', - 'virtualBalance' => 'Saldo virtuale', + 'virtual_balance' => 'Saldo virtuale', 'targetamount' => 'Importo obiettivo', - 'accountRole' => 'Ruolo del conto', - 'openingBalanceDate' => 'Data saldo di apertura', - 'ccType' => 'Piano di pagamento della carta di credito', - 'ccMonthlyPaymentDate' => 'Data di addebito mensile della carta di credito', + 'account_role' => 'Ruolo del conto', + 'opening_balance_date' => 'Data saldo di apertura', + 'cc_type' => 'Piano di pagamento della carta di credito', + 'cc_monthly_payment_date' => 'Data di addebito mensile della carta di credito', 'piggy_bank_id' => 'Salvadanaio', 'returnHere' => 'Ritorna qui', 'returnHereExplanation' => 'Dopo averlo archiviato, torna qui per crearne un altro.', @@ -118,7 +119,7 @@ return [ 'symbol' => 'Simbolo', 'code' => 'Codice', 'iban' => 'IBAN', - 'accountNumber' => 'Numero conto', + 'account_number' => 'Numero conto', 'creditCardNumber' => 'Numero carta di credito', 'has_headers' => 'Intestazioni', 'date_format' => 'Formato data', @@ -139,12 +140,8 @@ return [ 'stop_processing' => 'Interrompere l\'elaborazione', 'start_date' => 'Inizio intervallo', 'end_date' => 'Fine intervallo', - 'export_start_range' => 'Data iniziale dell\'intervallo d\'esportazione', - 'export_end_range' => 'Data finale dell\'intervallo d\'esportazione', - 'export_format' => 'Formato file', 'include_attachments' => 'Includi allegati caricati', 'include_old_uploads' => 'Includi dati importati', - 'accounts' => 'Esporta le transazioni da questi conti', 'delete_account' => 'Elimina conto ":name"', 'delete_bill' => 'Elimina bolletta ":name"', 'delete_budget' => 'Elimina budget ":name"', @@ -256,4 +253,7 @@ return [ 'weekend' => 'Fine settimana', 'client_secret' => 'Segreto del client', + 'withdrawal_destination_id' => 'Destination account', + 'deposit_source_id' => 'Source account', + ]; diff --git a/resources/lang/it_IT/import.php b/resources/lang/it_IT/import.php index 72652ab150..32e07fcf8a 100644 --- a/resources/lang/it_IT/import.php +++ b/resources/lang/it_IT/import.php @@ -207,6 +207,10 @@ return [ 'specific_rabo_descr' => 'Risolvi i possibili problemi con i file Rabobank', 'specific_pres_name' => 'CA finanziaria scelta dal Presidente', 'specific_pres_descr' => 'Risolvi i possibili problemi con i file da PC', + 'specific_belfius_name' => 'Belfius BE', + 'specific_belfius_descr' => 'Risolve possibili problemi con file di Belfius', + 'specific_ingbelgium_name' => 'ING BE', + 'specific_ingbelgium_descr' => 'Fixes potential problems with ING Belgium files', // job configuration for file provider (stage: roles) 'job_config_roles_title' => 'Configurazione di importazione (3/4) - Definisci il ruolo di ogni colonna', 'job_config_roles_text' => 'Ogni colonna nel tuo file CSV contiene determinati dati. Si prega di indicare il tipo di dati che l\'importatore dovrebbe aspettarsi. L\'opzione per "mappare" i dati significa che collegherete ogni voce trovata nella colonna con un valore nel vostro database. Una colonna spesso mappata è la colonna che contiene l\'IBAN del conto. Questo può essere facilmente abbinato all\'IBAN già presente nel tuo database.', @@ -291,14 +295,14 @@ return [ 'column_rabo-debit-credit' => 'Indicatore di addebito/accredito specifico di Rabobank', 'column_ing-debit-credit' => 'Indicatore di debito/credito specifico di ING', 'column_generic-debit-credit' => 'Indicatore generico di debito/credito bancario', - 'column_sepa-ct-id' => 'ID end-to-end del bonifico SEPA', - 'column_sepa-ct-op' => 'Identificatore SEPA conto controparte', - 'column_sepa-db' => 'Addebito diretto SEPA', - 'column_sepa-cc' => 'Codice Compensazione SEPA', - 'column_sepa-ci' => 'Identificativo Creditore SEPA', - 'column_sepa-ep' => 'SEPA External Purpose', - 'column_sepa-country' => 'Codice Paese SEPA', - 'column_sepa-batch-id' => 'ID Batch SEPA', + 'column_sepa_ct_id' => 'Identificativo End-To-End SEPA', + 'column_sepa_ct_op' => 'Identificatore SEPA conto controparte', + 'column_sepa_db' => 'Identificativo Mandato SEPA', + 'column_sepa_cc' => 'Codice Compensazione SEPA', + 'column_sepa_ci' => 'Identificativo Creditore SEPA', + 'column_sepa_ep' => 'SEPA External Purpose', + 'column_sepa_country' => 'Codice Paese SEPA', + 'column_sepa_batch_id' => 'ID Batch SEPA', 'column_tags-comma' => 'Etichette (separate da virgola)', 'column_tags-space' => 'Etichette (separate con spazio)', 'column_account-number' => 'Conto attività (numero conto)', @@ -306,4 +310,7 @@ return [ 'column_note' => 'Note', 'column_internal-reference' => 'Riferimento interno', + // error message + 'duplicate_row' => 'Row #:row (":description") could not be imported. It already exists.', + ]; diff --git a/resources/lang/it_IT/intro.php b/resources/lang/it_IT/intro.php index d983ea5d98..7628324471 100644 --- a/resources/lang/it_IT/intro.php +++ b/resources/lang/it_IT/intro.php @@ -24,39 +24,61 @@ declare(strict_types=1); return [ // index - 'index_intro' => 'Benvenuti nella pagina indice di Firefly III. Si prega di prendersi il tempo necessario per questa introduzione per avere un\'idea di come funziona Firefly III.', - 'index_accounts-chart' => 'Questo grafico mostra il saldo attuale dei conti attività. Puoi selezionare i conti visibili qui nelle tue preferenze.', - 'index_box_out_holder' => 'Questa piccola casella e le caselle accanto a questa ti daranno una rapida panoramica della tua situazione finanziaria.', - 'index_help' => 'Se hai bisogno di aiuto per una pagina o un modulo, premi questo pulsante.', - 'index_outro' => 'La maggior parte delle pagine di Firefly III inizieranno con un piccolo tour come questo. Vi prego di contattarci quando avete domande o commenti. Grazie!', - 'index_sidebar-toggle' => 'Per creare nuove transazioni, conto o altre cose, usa il menu sotto questa icona.', + 'index_intro' => 'Benvenuti nella pagina indice di Firefly III. Si prega di prendersi il tempo necessario per questa introduzione per avere un\'idea di come funziona Firefly III.', + 'index_accounts-chart' => 'Questo grafico mostra il saldo attuale dei conti attività. Puoi selezionare i conti visibili qui nelle tue preferenze.', + 'index_box_out_holder' => 'Questa piccola casella e le caselle accanto a questa ti daranno una rapida panoramica della tua situazione finanziaria.', + 'index_help' => 'Se hai bisogno di aiuto per una pagina o un modulo, premi questo pulsante.', + 'index_outro' => 'La maggior parte delle pagine di Firefly III inizieranno con un piccolo tour come questo. Vi prego di contattarci quando avete domande o commenti. Grazie!', + 'index_sidebar-toggle' => 'Per creare nuove transazioni, conto o altre cose, usa il menu sotto questa icona.', + 'index_cash_account' => 'These are the accounts created so far. You can use the cash account to track cash expenses but it\'s not mandatory of course.', + + // transactions (withdrawal) + 'transactions_create_withdrawal_source' => 'Select your favorite asset account or liability from this dropdown.', + 'transactions_create_withdrawal_destination' => 'Select an expense account here. Leave it empty if you want to make a cash expense.', + 'transactions_create_withdrawal_foreign_currency' => 'Use this field to set a foreign currency and amount.', + 'transactions_create_withdrawal_more_meta' => 'Plenty of other meta data you set in these fields.', + 'transactions_create_withdrawal_split_add' => 'If you want to split a transaction, add more splits with this button', + + // transactions (deposit) + 'transactions_create_deposit_source' => 'Select or type the payee in this auto-completing dropdown/textbox. Leave it empty if you want to make a cash deposit.', + 'transactions_create_deposit_destination' => 'Select an asset or liability account here.', + 'transactions_create_deposit_foreign_currency' => 'Use this field to set a foreign currency and amount.', + 'transactions_create_deposit_more_meta' => 'Plenty of other meta data you set in these fields.', + 'transactions_create_deposit_split_add' => 'If you want to split a transaction, add more splits with this button', + + // transactions (transfer) + 'transactions_create_transfer_source' => 'Select the source asset account here.', + 'transactions_create_transfer_destination' => 'Select the destination asset account here.', + 'transactions_create_transfer_foreign_currency' => 'Use this field to set a foreign currency and amount.', + 'transactions_create_transfer_more_meta' => 'Plenty of other meta data you set in these fields.', + 'transactions_create_transfer_split_add' => 'If you want to split a transaction, add more splits with this button', // create account: - 'accounts_create_iban' => 'Dai ai tuoi conti un IBAN valido. Ciò potrebbe rendere molto facile l\'importazione dei dati in futuro.', - 'accounts_create_asset_opening_balance' => 'I conti attività possono avere un "saldo di apertura", che indica l\'inizio della cronologia di questo conto in Firefly III.', - 'accounts_create_asset_currency' => 'Firefly III supporta più valute. I conti attività hanno una valuta principale, che devi impostare qui.', - 'accounts_create_asset_virtual' => 'A volte può aiutare a fornire al tuo conto un saldo virtuale: un ulteriore importo sempre aggiunto o rimosso dal saldo effettivo.', + 'accounts_create_iban' => 'Dai ai tuoi conti un IBAN valido. Ciò potrebbe rendere molto facile l\'importazione dei dati in futuro.', + 'accounts_create_asset_opening_balance' => 'I conti attività possono avere un "saldo di apertura", che indica l\'inizio della cronologia di questo conto in Firefly III.', + 'accounts_create_asset_currency' => 'Firefly III supporta più valute. I conti attività hanno una valuta principale, che devi impostare qui.', + 'accounts_create_asset_virtual' => 'A volte può aiutare a fornire al tuo conto un saldo virtuale: un ulteriore importo sempre aggiunto o rimosso dal saldo effettivo.', // budgets index - 'budgets_index_intro' => 'I budget sono usati per gestire le tue finanze e formano una delle funzioni principali di Firefly III.', - 'budgets_index_set_budget' => 'Imposta il tuo budget totale per ogni periodo in modo che Firefly III possa dirti se hai messo a bilancio tutti i soldi disponibili.', - 'budgets_index_see_expenses_bar' => 'Le spese effettuate riempiranno lentamente questa barra.', - 'budgets_index_navigate_periods' => 'Naviga attraverso i periodi per impostare facilmente i budget in anticipo.', - 'budgets_index_new_budget' => 'Crea nuovi budget come meglio credi.', - 'budgets_index_list_of_budgets' => 'Usa questa tabella per impostare gli importi per ciascun budget e vedere l\'andamento.', - 'budgets_index_outro' => 'Per saperne di più sui budget, controlla l\'icona della guida nell\'angolo in alto a destra.', + 'budgets_index_intro' => 'I budget sono usati per gestire le tue finanze e formano una delle funzioni principali di Firefly III.', + 'budgets_index_set_budget' => 'Imposta il tuo budget totale per ogni periodo in modo che Firefly III possa dirti se hai messo a bilancio tutti i soldi disponibili.', + 'budgets_index_see_expenses_bar' => 'Le spese effettuate riempiranno lentamente questa barra.', + 'budgets_index_navigate_periods' => 'Naviga attraverso i periodi per impostare facilmente i budget in anticipo.', + 'budgets_index_new_budget' => 'Crea nuovi budget come meglio credi.', + 'budgets_index_list_of_budgets' => 'Usa questa tabella per impostare gli importi per ciascun budget e vedere l\'andamento.', + 'budgets_index_outro' => 'Per saperne di più sui budget, controlla l\'icona della guida nell\'angolo in alto a destra.', // reports (index) - 'reports_index_intro' => 'Utilizza questi resoconti per ottenere informazioni dettagliate sulle tue finanze.', - 'reports_index_inputReportType' => 'Scegli un tipo di resoconto. Consulta le pagine della guida per vedere cosa ti mostra ciascuna resoconto.', - 'reports_index_inputAccountsSelect' => 'Puoi escludere o includere i conti attività come ritieni opportuno.', - 'reports_index_inputDateRange' => 'L\'intervallo di date selezionato dipende interamente da te: da un giorno a 10 anni.', - 'reports_index_extra-options-box' => 'A seconda del resoconto che hai selezionato, puoi selezionare filtri e opzioni aggiuntive qui. Guarda questa casella quando cambi i tipi di resoconto.', + 'reports_index_intro' => 'Utilizza questi resoconti per ottenere informazioni dettagliate sulle tue finanze.', + 'reports_index_inputReportType' => 'Scegli un tipo di resoconto. Consulta le pagine della guida per vedere cosa ti mostra ciascuna resoconto.', + 'reports_index_inputAccountsSelect' => 'Puoi escludere o includere i conti attività come ritieni opportuno.', + 'reports_index_inputDateRange' => 'L\'intervallo di date selezionato dipende interamente da te: da un giorno a 10 anni.', + 'reports_index_extra-options-box' => 'A seconda del resoconto che hai selezionato, puoi selezionare filtri e opzioni aggiuntive qui. Guarda questa casella quando cambi i tipi di resoconto.', // reports (reports) - 'reports_report_default_intro' => 'Questo resoconto ti fornirà una panoramica rapida e completa delle tue finanze. Se desideri vedere qualcos\'altro, per favore non esitare a contattarmi!', - 'reports_report_audit_intro' => 'Questo resoconto ti fornirà approfondimenti dettagliati sui tuoi conti attività.', - 'reports_report_audit_optionsBox' => 'Utilizza queste caselle di controllo per mostrare o nascondere le colonne che ti interessano.', + 'reports_report_default_intro' => 'Questo resoconto ti fornirà una panoramica rapida e completa delle tue finanze. Se desideri vedere qualcos\'altro, per favore non esitare a contattarmi!', + 'reports_report_audit_intro' => 'Questo resoconto ti fornirà approfondimenti dettagliati sui tuoi conti attività.', + 'reports_report_audit_optionsBox' => 'Utilizza queste caselle di controllo per mostrare o nascondere le colonne che ti interessano.', 'reports_report_category_intro' => 'Questo resoconto ti fornirà informazioni su una o più categorie.', 'reports_report_category_pieCharts' => 'Questi grafici ti daranno un\'idea delle spese e delle entrate per categoria o per conto.', @@ -129,7 +151,7 @@ return [ 'preferences_index_tabs' => 'Altre opzioni sono disponibili dietro queste schede.', // currencies - 'currencies_index_intro' => 'Firefly III supporta più valute, che è possibile modificare su questa pagina.', + 'currencies_index_intro' => 'Firefly III supporta più valute, che è possibile modificare in questa pagina.', 'currencies_index_default' => 'Firefly III ha una valuta predefinita.', 'currencies_index_buttons' => 'Utilizza questi pulsanti per cambiare la valuta predefinita o per abilitare altre valute.', diff --git a/resources/lang/it_IT/list.php b/resources/lang/it_IT/list.php index 20edbb69e0..ac46292191 100644 --- a/resources/lang/it_IT/list.php +++ b/resources/lang/it_IT/list.php @@ -23,71 +23,71 @@ declare(strict_types=1); return [ - 'buttons' => 'Pulsanti', - 'icon' => 'Icona', - 'id' => 'ID', - 'create_date' => 'Creato il', - 'update_date' => 'Aggiornato il', - 'updated_at' => 'Aggiornato il', - 'balance_before' => 'Saldo precedente', - 'balance_after' => 'Saldo successivo', - 'name' => 'Nome', - 'role' => 'Ruolo', - 'currentBalance' => 'Saldo corrente', - 'linked_to_rules' => 'Regole rilevanti', - 'active' => 'Attivo', - 'lastActivity' => 'Ultima attività', - 'balanceDiff' => 'Differenze saldi', - 'matchesOn' => 'Abbinato con', - 'account_type' => 'Tipo conto', - 'created_at' => 'Creato il', - 'account' => 'Conto', - 'matchingAmount' => 'Importo', - 'split_number' => 'Diviso #', - 'destination' => 'Destinazione', - 'source' => 'Origine', - 'next_expected_match' => 'Prossimo abbinamento previsto', - 'automatch' => 'Abbinamento automatico?', - 'repeat_freq' => 'Si ripete', - 'description' => 'Descrizione', - 'amount' => 'Importo', - 'internal_reference' => 'Riferimento interno', - 'date' => 'Data', - 'interest_date' => 'Data interessi', - 'book_date' => 'Data contabile', - 'process_date' => 'Data lavorazione', - 'due_date' => 'Data scadenza', - 'payment_date' => 'Data pagamento', - 'invoice_date' => 'Data fatturazione', - 'interal_reference' => 'Riferimento interno', - 'notes' => 'Note', - 'from' => 'Da', - 'piggy_bank' => 'Salvadanaio', - 'to' => 'A', - 'budget' => 'Budget', - 'category' => 'Categoria', - 'bill' => 'Bolletta', - 'withdrawal' => 'Prelievo', - 'deposit' => 'Deposito', - 'transfer' => 'Trasferimento', - 'type' => 'Tipo', - 'completed' => 'Completato', - 'iban' => 'IBAN', - 'paid_current_period' => 'Pagati in questo periodo', - 'email' => 'Email', - 'registered_at' => 'Registrato il', - 'is_blocked' => 'È bloccato', - 'is_admin' => 'È amministratore', - 'has_two_factor' => 'Ha 2FA', - 'blocked_code' => 'Codice blocco', - 'source_account' => 'Conto di origine', + 'buttons' => 'Pulsanti', + 'icon' => 'Icona', + 'id' => 'ID', + 'create_date' => 'Creato il', + 'update_date' => 'Aggiornato il', + 'updated_at' => 'Aggiornato il', + 'balance_before' => 'Saldo precedente', + 'balance_after' => 'Saldo successivo', + 'name' => 'Nome', + 'role' => 'Ruolo', + 'currentBalance' => 'Saldo corrente', + 'linked_to_rules' => 'Regole rilevanti', + 'active' => 'Attivo', + 'transaction_type' => 'Type', + 'lastActivity' => 'Ultima attività', + 'balanceDiff' => 'Differenze saldi', + 'matchesOn' => 'Abbinato con', + 'account_type' => 'Tipo conto', + 'created_at' => 'Creato il', + 'account' => 'Conto', + 'matchingAmount' => 'Importo', + 'split_number' => 'Diviso #', + 'destination' => 'Destinazione', + 'source' => 'Origine', + 'next_expected_match' => 'Prossimo abbinamento previsto', + 'automatch' => 'Abbinamento automatico?', + 'repeat_freq' => 'Si ripete', + 'description' => 'Descrizione', + 'amount' => 'Importo', + 'internal_reference' => 'Riferimento interno', + 'date' => 'Data', + 'interest_date' => 'Data interessi', + 'book_date' => 'Data contabile', + 'process_date' => 'Data lavorazione', + 'due_date' => 'Data scadenza', + 'payment_date' => 'Data pagamento', + 'invoice_date' => 'Data fatturazione', + 'interal_reference' => 'Riferimento interno', + 'notes' => 'Note', + 'from' => 'Da', + 'piggy_bank' => 'Salvadanaio', + 'to' => 'A', + 'budget' => 'Budget', + 'category' => 'Categoria', + 'bill' => 'Bolletta', + 'withdrawal' => 'Prelievo', + 'deposit' => 'Deposito', + 'transfer' => 'Trasferimento', + 'type' => 'Tipo', + 'completed' => 'Completato', + 'iban' => 'IBAN', + 'paid_current_period' => 'Pagati in questo periodo', + 'email' => 'Email', + 'registered_at' => 'Registrato il', + 'is_blocked' => 'È bloccato', + 'is_admin' => 'È amministratore', + 'has_two_factor' => 'Ha 2FA', + 'blocked_code' => 'Codice blocco', + 'source_account' => 'Conto di origine', 'destination_account' => 'Conto destinazione', 'accounts_count' => 'Numero di conti', 'journals_count' => 'Numero di transazioni', 'attachments_count' => 'Numero di allegati', 'bills_count' => 'Numero di bollette', 'categories_count' => 'Numero di categorie', - 'export_jobs_count' => 'Numero delle operazioni di esportazione', 'import_jobs_count' => 'Numero delle operazioni di importazione', 'budget_count' => 'Numero di budget', 'rule_and_groups_count' => 'Numero di regole e gruppi di regole', @@ -106,14 +106,14 @@ return [ 'account_on_spectre' => 'Conto (Spectre)', 'account_on_ynab' => 'Conto (YNAB)', 'do_import' => 'Importo da questo conto', - 'sepa-ct-id' => 'Identificativo End-To-End SEPA', - 'sepa-ct-op' => 'Identificativo SEPA Conto Controparte', - 'sepa-db' => 'Identificativo Mandato SEPA', - 'sepa-country' => 'Paese SEPA', - 'sepa-cc' => 'Codice Compensazione SEPA', - 'sepa-ep' => 'SEPA External Purpose', - 'sepa-ci' => 'Identificativo Creditore SEPA', - 'sepa-batch-id' => 'ID Batch SEPA', + 'sepa_ct_id' => 'Identificativo End-To-End SEPA', + 'sepa_ct_op' => 'Identificativo SEPA Conto Controparte', + 'sepa_db' => 'Identificativo Mandato SEPA', + 'sepa_country' => 'Codice Paese SEPA', + 'sepa_cc' => 'Codice Compensazione SEPA', + 'sepa_ep' => 'SEPA External Purpose', + 'sepa_ci' => 'Identificativo Creditore SEPA', + 'sepa_batch_id' => 'ID Batch SEPA', 'external_id' => 'ID esterno', 'account_at_bunq' => 'Conto con Bunq', 'file_name' => 'Nome del file', diff --git a/resources/lang/it_IT/validation.php b/resources/lang/it_IT/validation.php index 60673df3c6..5aa7f9cfcb 100644 --- a/resources/lang/it_IT/validation.php +++ b/resources/lang/it_IT/validation.php @@ -36,12 +36,16 @@ return [ 'file_attached' => 'File caricato con successo ":name".', 'must_exist' => 'L\'ID nel campo :attribute non esiste nel database.', 'all_accounts_equal' => 'Tutti i conti in questo campo devono essere uguali.', + 'group_title_mandatory' => 'Il titolo del gruppo è obbligatorio quando ci sono più di una transazione.', + 'transaction_types_equal' => 'Tutte le suddivisioni devono essere dello stesso tipo.', + 'invalid_transaction_type' => 'Tipo della transazione non valido.', 'invalid_selection' => 'La tua selezione non è valida.', 'belongs_user' => 'Questo valore non è valido per questo campo.', 'at_least_one_transaction' => 'Hai bisogno di almeno una transazione.', 'at_least_one_repetition' => 'È necessaria almeno una ripetizione.', 'require_repeat_until' => 'Richiede un numero di ripetizioni o una data di fine (ripeti fino al), non entrambi.', 'require_currency_info' => 'Il contenuto di questo campo non è valido senza informazioni sulla valuta.', + 'require_currency_amount' => 'Il contenuto di questo campo non è valido senza le informazioni sull\'importo estero.', 'equal_description' => 'La descrizione della transazione non deve essere uguale alla descrizione globale.', 'file_invalid_mime' => 'Il file ":name" è di tipo ":mime" che non è accettato come nuovo caricamento.', 'file_too_large' => 'Il file ":name" è troppo grande.', @@ -135,8 +139,8 @@ return [ 'name' => 'nome', 'piggy_bank_id' => 'ID salvadanaio', 'targetamount' => 'importo obiettivo', - 'openingBalanceDate' => 'data saldo di apertura', - 'openingBalance' => 'saldo di apertura', + 'opening_balance_date' => 'data saldo di apertura', + 'opening_balance' => 'saldo di apertura', 'match' => 'abbinamento', 'amount_min' => 'importo minimo', 'amount_max' => 'importo massimo', @@ -164,4 +168,28 @@ return [ 'rule-trigger.4' => 'trigger #4 della regola', 'rule-trigger.5' => 'trigger #5 della regola', ], + + // validation of accounts: + 'withdrawal_source_need_data' => 'È necessario ottenere un ID e/o un nome del conto di origine validi per continuare.', + 'withdrawal_source_bad_data' => 'Non è stato possibile trovare un conto d\'origine valido effettuando la ricerca con l\'ID ":id" o il nome ":name".', + 'withdrawal_dest_need_data' => 'È necessario ottenere un ID e/o un nome del conto di destinazione validi per continuare.', + 'withdrawal_dest_bad_data' => 'Non è stato possibile trovare un conto di destinazione valido effettuando la ricerca con l\'ID ":id" o il nome ":name".', + + 'deposit_source_need_data' => 'È necessario ottenere un ID e/o un nome del conto di origine validi per continuare.', + 'deposit_source_bad_data' => 'Non è stato possibile trovare un conto d\'origine valido effettuando la ricerca con l\'ID ":id" o il nome ":name".', + 'deposit_dest_need_data' => 'È necessario ottenere un ID e/o un nome del conto di destinazione validi per continuare.', + 'deposit_dest_bad_data' => 'Non è stato possibile trovare un conto di destinazione valido effettuando la ricerca con l\'ID ":id" o il nome ":name".', + + 'transfer_source_need_data' => 'È necessario ottenere un ID e/o un nome del conto di origine validi per continuare.', + 'transfer_source_bad_data' => 'Non è stato possibile trovare un conto d\'origine valido effettuando la ricerca con l\'ID ":id" o il nome ":name".', + 'transfer_dest_need_data' => 'È necessario ottenere un ID e/o un nome del conto di destinazione validi per continuare.', + 'transfer_dest_bad_data' => 'Non è stato possibile trovare un conto di destinazione valido effettuando la ricerca con l\'ID ":id" o il nome ":name".', + 'need_id_in_edit' => 'Ogni suddivisione deve avere un "transaction_journal_id" (un ID valido oppure 0).', + + 'ob_source_need_data' => 'È necessario ottenere un ID e/o un nome del conto di origine validi per continuare.', + 'ob_dest_need_data' => 'È necessario ottenere un ID e/o un nome del conto di destinazione validi per continuare.', + 'ob_dest_bad_data' => 'Non è stato possibile trovare un conto di destinazione valido effettuando la ricerca con l\'ID ":id" o il nome ":name".', + + 'generic_invalid_source' => 'You can\'t use this account as the source account.', + 'generic_invalid_destination' => 'You can\'t use this account as the destination account.', ]; diff --git a/resources/lang/nb_NO/breadcrumbs.php b/resources/lang/nb_NO/breadcrumbs.php index 72f1ca93ce..1b2da01252 100644 --- a/resources/lang/nb_NO/breadcrumbs.php +++ b/resources/lang/nb_NO/breadcrumbs.php @@ -23,35 +23,37 @@ declare(strict_types=1); return [ - 'home' => 'Hjem', - 'edit_currency' => 'Rediger valuta ":name"', - 'delete_currency' => 'Slett valuta ":name"', - 'newPiggyBank' => 'Lag en ny sparegris', - 'edit_piggyBank' => 'Rediger sparegris ":name"', - 'preferences' => 'Innstillinger', - 'profile' => 'Profil', - 'changePassword' => 'Endre passord', - 'change_email' => 'Endre din epostadresse', - 'bills' => 'Regninger', - 'newBill' => 'Ny regning', - 'edit_bill' => 'Rediger regning ":name"', - 'delete_bill' => 'Slett regning ":name"', - 'reports' => 'Rapporter', - 'search_result' => 'Søkeresultater for ":query"', - 'withdrawal_list' => 'Utgifter', - 'deposit_list' => 'Inntekter og innskudd', - 'transfer_list' => 'Overføringer', - 'transfers_list' => 'Overføringer', - 'reconciliation_list' => 'Avstemminger', - 'create_withdrawal' => 'Opprett nytt uttak', - 'create_deposit' => 'Opprett nytt innskudd', - 'create_transfer' => 'Opprett ny overføring', - 'edit_journal' => 'Rediger transaksjon ":description"', - 'edit_reconciliation' => 'Endre ":description"', - 'delete_journal' => 'Slett transaksjon ":description"', - 'tags' => 'Tagger', - 'createTag' => 'Opprett ny tagg', - 'edit_tag' => 'Rediger tagg ":tag"', - 'delete_tag' => 'Slett tagg ":tag"', - 'delete_journal_link' => 'Slett kobling mellom transaksjoner', + 'home' => 'Hjem', + 'edit_currency' => 'Rediger valuta ":name"', + 'delete_currency' => 'Slett valuta ":name"', + 'newPiggyBank' => 'Lag en ny sparegris', + 'edit_piggyBank' => 'Rediger sparegris ":name"', + 'preferences' => 'Innstillinger', + 'profile' => 'Profil', + 'changePassword' => 'Endre passord', + 'change_email' => 'Endre din epostadresse', + 'bills' => 'Regninger', + 'newBill' => 'Ny regning', + 'edit_bill' => 'Rediger regning ":name"', + 'delete_bill' => 'Slett regning ":name"', + 'reports' => 'Rapporter', + 'search_result' => 'Søkeresultater for ":query"', + 'withdrawal_list' => 'Utgifter', + 'Withdrawal_list' => 'Expenses', + 'deposit_list' => 'Inntekter og innskudd', + 'transfer_list' => 'Overføringer', + 'transfers_list' => 'Overføringer', + 'reconciliation_list' => 'Avstemminger', + 'create_withdrawal' => 'Opprett nytt uttak', + 'create_deposit' => 'Opprett nytt innskudd', + 'create_transfer' => 'Opprett ny overføring', + 'create_new_transaction' => 'Create a new transaction', + 'edit_journal' => 'Rediger transaksjon ":description"', + 'edit_reconciliation' => 'Endre ":description"', + 'delete_journal' => 'Slett transaksjon ":description"', + 'tags' => 'Tagger', + 'createTag' => 'Opprett ny tagg', + 'edit_tag' => 'Rediger tagg ":tag"', + 'delete_tag' => 'Slett tagg ":tag"', + 'delete_journal_link' => 'Slett kobling mellom transaksjoner', ]; diff --git a/resources/lang/nb_NO/firefly.php b/resources/lang/nb_NO/firefly.php index d3f47f6202..9b7b8f7954 100644 --- a/resources/lang/nb_NO/firefly.php +++ b/resources/lang/nb_NO/firefly.php @@ -33,7 +33,7 @@ return [ 'last_seven_days' => 'Syv siste dager', 'last_thirty_days' => 'Tredve siste dager', 'welcomeBack' => 'Hvordan går det?', - 'welcome_back' => 'Hvordan går det?', + 'welcome_back' => 'Hvordan går det?', 'everything' => 'Alt', 'today' => 'i dag', 'customRange' => 'Egendefinert utvalg', @@ -54,6 +54,7 @@ return [ 'create_new_stuff' => 'Lag nye ting', 'new_withdrawal' => 'Nytt uttak', 'create_new_transaction' => 'Opprett ny transaksjon', + 'new_transaction' => 'New transaction', 'go_to_asset_accounts' => 'Se aktivakontoene dine', 'go_to_budgets' => 'Gå til budsjettene dine', 'go_to_categories' => 'Gå til kategoriene dine', @@ -82,28 +83,32 @@ return [ 'help_for_this_page' => 'Hjelp for denne siden', 'no_help_could_be_found' => 'Ingen hjelpetekst ble funnet.', 'no_help_title' => 'Beklager, en feil har oppstått.', - 'two_factor_welcome' => 'Hei, :user!', - 'two_factor_enter_code' => 'Vennligst skriv inn tofaktorautentiseringskoden for å fortsette. Appen på telefonen kan generere den for deg.', - 'two_factor_code_here' => 'Skriv koden her', - 'two_factor_title' => 'To-faktor autentisering', - 'authenticate' => 'Autentisere', - 'two_factor_forgot_title' => 'Mistet tofaktorautentisering', - 'two_factor_forgot' => 'Jeg har glemt/mistet min tofaktor-ting.', - 'two_factor_lost_header' => 'Mistet tofaktorautentisering?', - 'two_factor_lost_intro' => 'Dessverre er ikke dette noe du kan tilbakestille fra webgrensesnittet. Du har to valg.', - 'two_factor_lost_fix_self' => 'Hvis du kjører din egen installasjon av Firefly III, kan du sjekke loggene i storage/logs for instruksjoner.', - 'two_factor_lost_fix_owner' => 'Ellers sender du e-postadressen eieren, :site_owner og ber vedkommende om å tilbakestille tofaktorautentiseringen din.', - 'warning_much_data' => ':days dager med data kan ta litt tid å laste.', - 'registered' => 'Registreringen var vellykket!', - 'Default asset account' => 'Standard aktivakonto', - 'no_budget_pointer' => 'Det ser ikke ut til at du har noen budsjett ennå. Du bør opprette noen på budsjett-siden. Budsjett kan hjelpe deg med å holde oversikt over utgifter.', - 'Savings account' => 'Sparekonto', - 'Credit card' => 'Kredittkort', - 'source_accounts' => 'Kildekonto(er)', - 'destination_accounts' => 'Målkonto(er)', - 'user_id_is' => 'Din bruker-ID er :user', - 'field_supports_markdown' => 'Dette feltet støtter Markdown.', - 'need_more_help' => 'Hvis du trenger mer hjelp med å bruke Firefly III, vennligst opprett en sak på Github.', + 'two_factor_welcome' => 'Hello!', + 'two_factor_enter_code' => 'Vennligst skriv inn tofaktorautentiseringskoden for å fortsette. Appen på telefonen kan generere den for deg.', + 'two_factor_code_here' => 'Skriv koden her', + 'two_factor_title' => 'To-faktor autentisering', + 'authenticate' => 'Autentisere', + 'two_factor_forgot_title' => 'Mistet tofaktorautentisering', + 'two_factor_forgot' => 'Jeg har glemt/mistet min tofaktor-ting.', + 'two_factor_lost_header' => 'Mistet tofaktorautentisering?', + 'two_factor_lost_intro' => 'If you lost your backup codes as well, you have bad luck. This is not something you can fix from the web interface. You have two choices.', + 'two_factor_lost_fix_self' => 'If you run your own instance of Firefly III, check the logs in storage/logs for instructions, or run docker logs <container_id> to see the instructions (refresh this page).', + 'two_factor_lost_fix_owner' => 'Ellers sender du e-postadressen eieren, :site_owner og ber vedkommende om å tilbakestille tofaktorautentiseringen din.', + 'mfa_backup_code' => 'You have used a backup code to login to Firefly III. It can\'t be used again, so cross it from your list.', + 'pref_two_factor_new_backup_codes' => 'Get new backup codes', + 'pref_two_factor_backup_code_count' => 'You have :count valid backup code(s).', + '2fa_i_have_them' => 'I stored them!', + 'warning_much_data' => ':days dager med data kan ta litt tid å laste.', + 'registered' => 'Registreringen var vellykket!', + 'Default asset account' => 'Standard aktivakonto', + 'no_budget_pointer' => 'Det ser ikke ut til at du har noen budsjett ennå. Du bør opprette noen på budsjett-siden. Budsjett kan hjelpe deg med å holde oversikt over utgifter.', + 'Savings account' => 'Sparekonto', + 'Credit card' => 'Kredittkort', + 'source_accounts' => 'Kildekonto(er)', + 'destination_accounts' => 'Målkonto(er)', + 'user_id_is' => 'Din bruker-ID er :user', + 'field_supports_markdown' => 'Dette feltet støtter Markdown.', + 'need_more_help' => 'Hvis du trenger mer hjelp med å bruke Firefly III, vennligst opprett en sak på Github.', 'reenable_intro_text' => 'Du kan også reaktivere introduksjonsveiledningen.', 'intro_boxes_after_refresh' => 'Innføringsboksene vil dukke opp igjen når du laster siden på nytt.', 'show_all_no_filter' => 'Vis alle transaksjoner uten å gruppere dem etter dato.', @@ -220,21 +225,23 @@ return [ 'search_query' => 'Spørring', 'search_found_transactions' => 'Firefly III fant :count transaksjoner i :time sekunder.', 'search_for_query' => 'Firefly III leter etter transaksjoner med disse ordene: :query', - 'search_modifier_amount_is' => 'Beløpet er eksakt :value', - 'search_modifier_amount' => 'Beløpet er eksakt :value', - 'search_modifier_amount_max' => 'Beløpet er på det største :value', - 'search_modifier_amount_min' => 'Beløpet er på det minste: :value', - 'search_modifier_amount_less' => 'Beløpet er mindre enn :value', - 'search_modifier_amount_more' => 'Beløpet er større enn :value', - 'search_modifier_source' => 'Kildekonto er :value', - 'search_modifier_destination' => 'Målkonto er :value', - 'search_modifier_category' => 'Kategori er :value', - 'search_modifier_budget' => 'Budsjett er :value', - 'search_modifier_bill' => 'Regning er :value', - 'search_modifier_type' => 'Transaksjons type er :value', - 'search_modifier_date' => 'Transaksjons dato er :value', - 'search_modifier_date_before' => 'Transaksjons dato er før :value', - 'search_modifier_date_after' => 'Transaksjons dato er etter :value', + 'search_modifier_amount_is' => 'Beløpet er eksakt :value', + 'search_modifier_amount' => 'Beløpet er eksakt :value', + 'search_modifier_amount_max' => 'Beløpet er på det største :value', + 'search_modifier_amount_min' => 'Beløpet er på det minste: :value', + 'search_modifier_amount_less' => 'Beløpet er mindre enn :value', + 'search_modifier_amount_more' => 'Beløpet er større enn :value', + 'search_modifier_source' => 'Kildekonto er :value', + 'search_modifier_from' => 'Source account is :value', + 'search_modifier_destination' => 'Målkonto er :value', + 'search_modifier_to' => 'Destination account is :value', + 'search_modifier_category' => 'Kategori er :value', + 'search_modifier_budget' => 'Budsjett er :value', + 'search_modifier_bill' => 'Regning er :value', + 'search_modifier_type' => 'Transaksjons type er :value', + 'search_modifier_date' => 'Transaksjons dato er :value', + 'search_modifier_date_before' => 'Transaksjons dato er før :value', + 'search_modifier_date_after' => 'Transaksjons dato er etter :value', 'search_modifier_on' => 'Transaksjons dato er :value', 'search_modifier_before' => 'Transaksjons dato er før :value', 'search_modifier_after' => 'Transaksjons er etter :value', @@ -257,33 +264,6 @@ return [ 'half-year' => 'hvert halvår', 'yearly' => 'årlig', - // export data: - 'import_and_export' => 'Import og eksport', - 'export_data' => 'Eksporter data', - 'export_and_backup_data' => 'Eksporter data', - 'export_data_intro' => 'Bruk de eksporterte dataene for å flytte til en ny finansapplikasjon. Vær oppmerksom på at disse filene ikke er ment som noen sikkerhetskopi. De inneholder ikke nok metadata for å fullt ut gjenopprette en ny Firefly III-installasjon. Hvis du vil sikkerhetskopiere dataene dine, må du sikkerhetskopiere databasen direkte.', - 'export_format' => 'Eksportformat', - 'export_format_csv' => 'Kommaseparerte verdier (CSV-fil)', - 'export_format_mt940' => 'MT940-kompatibelt format', - 'include_old_uploads_help' => 'Firefly III sletter ikke de opprinnelige CSV-filene du har importert tidligere. Du kan inkludere dem i eksporten din.', - 'do_export' => 'Eksporter', - 'export_status_never_started' => 'Eksporteringen har ikke startet ennå', - 'export_status_make_exporter' => 'Lager eksportørting...', - 'export_status_collecting_journals' => 'Samler transaksjonene dine...', - 'export_status_collected_journals' => 'Samlet transaksjonene dine!', - 'export_status_converting_to_export_format' => 'Konverterer dine transaksjoner...', - 'export_status_converted_to_export_format' => 'Dine transaksjoner ble konvertert!', - 'export_status_creating_journal_file' => 'Oppretter eksportfil...', - 'export_status_created_journal_file' => 'Eksportfilen ble opprettet!', - 'export_status_collecting_attachments' => 'Samler alle dine vedlegg...', - 'export_status_collected_attachments' => 'Samlet alle dine vedlegg!', - 'export_status_collecting_old_uploads' => 'Samler alle dine tidligere opplastinger...', - 'export_status_collected_old_uploads' => 'Samlet alle dine tidligere opplastinger!', - 'export_status_creating_zip_file' => 'Oppretter en zip-fil...', - 'export_status_created_zip_file' => 'Opprettet en zip-fil!', - 'export_status_finished' => 'Eksporten var vellykket! Jippi!', - 'export_data_please_wait' => 'Vennligst vent...', - // rules 'rules' => 'Regler', 'rule_name' => 'Navn på regel', @@ -502,30 +482,33 @@ return [ 'pref_custom_fiscal_year_help' => 'I land som bruker et annet regnskapsår enn 1. januar til 31. desember, kan du slå på dette og angi start- og sluttdager for regnskapsåret', 'pref_fiscal_year_start_label' => 'Regnskapsårets startdato', 'pref_two_factor_auth' => '2-trinnsverifisering', - 'pref_two_factor_auth_help' => 'Når du aktiverer 2-trinnsverifisering (også kjent som tofaktorautentisering), legger du til et ekstra sikkerhetslag på kontoen din. Du logger deg på med noe du vet (ditt passord) og noe du har (en bekreftelseskode). Bekreftelseskoder genereres av et program på telefonen, for eksempel Authy eller Google Authenticator.', - 'pref_enable_two_factor_auth' => 'Skru på 2-trinnsverifisering', - 'pref_two_factor_auth_disabled' => '2-trinnsverifisering er fjernet og deaktivert', - 'pref_two_factor_auth_remove_it' => 'Ikke glem å fjerne kontoen fra godkjenningsappen din!', - 'pref_two_factor_auth_code' => 'Bekreftelseskode', - 'pref_two_factor_auth_code_help' => 'Skann QR-koden med et program på telefonen, for eksempel Authy eller Google Authenticator, og skriv inn den genererte koden.', - 'pref_two_factor_auth_reset_code' => 'Tilbakestill verifiseringskoden', - 'pref_two_factor_auth_disable_2fa' => 'Deaktiver 2FA', - '2fa_use_secret_instead' => 'Hvis du ikke kan skanne QR code, bruk gjerne "secret" istedet: :secret.', - 'pref_save_settings' => 'Lagre innstillinger', - 'saved_preferences' => 'Innstillinger lagret!', - 'preferences_general' => 'Generelt', - 'preferences_frontpage' => 'Startskjermen', - 'preferences_security' => 'Sikkerhet', - 'preferences_layout' => 'Oppsett', - 'pref_home_show_deposits' => 'Vis innskudd på startskjermen', - 'pref_home_show_deposits_info' => 'Startskjermen viser allerede kostnadskontoene dine. Skal den også vise inntektskontoene dine?', - 'pref_home_do_show_deposits' => 'Ja, vis dem', - 'successful_count' => 'hvorav :count var vellykket', - 'list_page_size_title' => 'Sidestørrelse', - 'list_page_size_help' => 'En liste over ting (kontoer, transaksjoner osv.). Viser maksimalt dette antall elementer per side.', - 'list_page_size_label' => 'Sidestørrelse', - 'between_dates' => '(:start og :end)', - 'pref_optional_fields_transaction' => 'Valgfrie felt for transaksjoner', + 'pref_two_factor_auth_help' => 'Når du aktiverer 2-trinnsverifisering (også kjent som tofaktorautentisering), legger du til et ekstra sikkerhetslag på kontoen din. Du logger deg på med noe du vet (ditt passord) og noe du har (en bekreftelseskode). Bekreftelseskoder genereres av et program på telefonen, for eksempel Authy eller Google Authenticator.', + 'pref_enable_two_factor_auth' => 'Skru på 2-trinnsverifisering', + 'pref_two_factor_auth_disabled' => '2-trinnsverifisering er fjernet og deaktivert', + 'pref_two_factor_auth_remove_it' => 'Ikke glem å fjerne kontoen fra godkjenningsappen din!', + 'pref_two_factor_auth_code' => 'Bekreftelseskode', + 'pref_two_factor_auth_code_help' => 'Skann QR-koden med et program på telefonen, for eksempel Authy eller Google Authenticator, og skriv inn den genererte koden.', + 'pref_two_factor_auth_reset_code' => 'Tilbakestill verifiseringskoden', + 'pref_two_factor_auth_disable_2fa' => 'Deaktiver 2FA', + '2fa_use_secret_instead' => 'If you cannot scan the QR code, feel free to use the secret instead: :secret.', + '2fa_backup_codes' => 'Store these backup codes for access in case you lose your device.', + '2fa_already_enabled' => '2-step verification is already enabled.', + 'wrong_mfa_code' => 'This MFA code is not valid.', + 'pref_save_settings' => 'Lagre innstillinger', + 'saved_preferences' => 'Innstillinger lagret!', + 'preferences_general' => 'Generelt', + 'preferences_frontpage' => 'Startskjermen', + 'preferences_security' => 'Sikkerhet', + 'preferences_layout' => 'Oppsett', + 'pref_home_show_deposits' => 'Vis innskudd på startskjermen', + 'pref_home_show_deposits_info' => 'Startskjermen viser allerede kostnadskontoene dine. Skal den også vise inntektskontoene dine?', + 'pref_home_do_show_deposits' => 'Ja, vis dem', + 'successful_count' => 'hvorav :count var vellykket', + 'list_page_size_title' => 'Sidestørrelse', + 'list_page_size_help' => 'En liste over ting (kontoer, transaksjoner osv.). Viser maksimalt dette antall elementer per side.', + 'list_page_size_label' => 'Sidestørrelse', + 'between_dates' => '(:start og :end)', + 'pref_optional_fields_transaction' => 'Valgfrie felt for transaksjoner', 'pref_optional_fields_transaction_help' => 'Som standard er ikke alle felt aktivert når du oppretter en ny transaksjon (for å unngå forvirring). Nedenfor kan du aktivere disse feltene hvis du tror de kan være nyttige for deg. Selvfølgelig vil et felt som er deaktivert, men allerede fylt inn, være synlig, uavhengig av innstillingen.', 'optional_tj_date_fields' => 'Datofelter', 'optional_tj_business_fields' => 'Forretningsfelter', @@ -579,24 +562,25 @@ return [ 'login_with_new_email' => 'Du kan nå logge inn med din nye e-postadresse.', 'login_with_old_email' => 'Du kan nå logge inn med din gamle e-postadresse igjen.', 'login_provider_local_only' => 'Denne handlingen er ikke tilgjengelig ved godkjenning gjennom ":login_provider".', - 'delete_local_info_only' => 'Fordi du godkjenner gjennom ":login_provider", vil dette bare slette lokal informasjon i Firefly III.', + 'delete_local_info_only' => 'Fordi du godkjenner gjennom ":login_provider", vil dette bare slette lokal informasjon i Firefly III.', // attachments - 'nr_of_attachments' => 'Ett vedlegg|:count vedlegg', - 'attachments' => 'Vedlegg', - 'edit_attachment' => 'Rediger vedlegg ":name"', - 'update_attachment' => 'Oppdater vedlegget', - 'delete_attachment' => 'Slett vedlegg ":name"', - 'attachment_deleted' => 'Slettet vedlegg ":name"', - 'attachment_updated' => 'Oppdatert vedlegg ":name"', - 'upload_max_file_size' => 'Maks filstørrelse: :size', - 'list_all_attachments' => 'Liste over alle vedlegg', + 'nr_of_attachments' => 'Ett vedlegg|:count vedlegg', + 'attachments' => 'Vedlegg', + 'edit_attachment' => 'Rediger vedlegg ":name"', + 'update_attachment' => 'Oppdater vedlegget', + 'delete_attachment' => 'Slett vedlegg ":name"', + 'attachment_deleted' => 'Slettet vedlegg ":name"', + 'liabilities_deleted' => 'Deleted liability ":name"', + 'attachment_updated' => 'Oppdatert vedlegg ":name"', + 'upload_max_file_size' => 'Maks filstørrelse: :size', + 'list_all_attachments' => 'Liste over alle vedlegg', // transaction index - 'title_expenses' => 'Utgifter', - 'title_withdrawal' => 'Utgifter', - 'title_revenue' => 'Inntekt', - 'title_deposit' => 'Inntekt', + 'title_expenses' => 'Utgifter', + 'title_withdrawal' => 'Utgifter', + 'title_revenue' => 'Inntekt', + 'title_deposit' => 'Inntekt', 'title_transfer' => 'Overføringer', 'title_transfers' => 'Overføringer', @@ -634,6 +618,9 @@ return [ 'converted_to_Transfer' => 'Transaksjonen er konvertert til en overføring', 'invalid_convert_selection' => 'Kontoen du har valgt er allerede brukt i denne transaksjonen eller finnes ikke.', 'source_or_dest_invalid' => 'Finner ikke riktig transaksjonsdetaljer. Konvertering er ikke mulig.', + 'convert_to_withdrawal' => 'Convert to a withdrawal', + 'convert_to_deposit' => 'Convert to a deposit', + 'convert_to_transfer' => 'Convert to a transfer', // create new stuff: 'create_new_withdrawal' => 'Opprett nytt uttak', @@ -695,7 +682,9 @@ return [ 'available_amount_indication' => 'Bruk disse beløpene for å få en indikasjon på hva ditt totale budsjett kan være.', 'suggested' => 'Foreslått', 'average_between' => 'Gjennomsnitt mellom :start og :end', - 'over_budget_warn' => ' Normalt budsjetterer du ca. :amount per day. Dette er :over_amount pr. dag.', + 'over_budget_warn' => ' Usually you budget about :amount per day. This time it\'s :over_amount per day. Are you sure?', + 'transferred_in' => 'Transferred (in)', + 'transferred_away' => 'Transferred (away)', // bills: 'match_between_amounts' => 'Regning matcher transaksjoner mellom :low og :high.', @@ -779,6 +768,7 @@ return [ 'reconcile_options' => 'Avstemmingsalternativer', 'reconcile_range' => 'Avstemningsområde', 'start_reconcile' => 'Start avstemming', + 'cash_account_type' => 'Cash', 'cash' => 'kontanter', 'account_type' => 'Kontotype', 'save_transactions_by_moving' => 'Lagre disse transaksjonene ved å flytte dem til en annen konto:', @@ -803,7 +793,9 @@ return [ 'reconcile_go_back' => 'Du kan alltid redigere eller slette en korreksjon senere.', 'must_be_asset_account' => 'Du kan bare avstemme aktivakontoer', 'reconciliation_stored' => 'Avstemming lagret', - 'reconcilliation_transaction_title' => 'Rekonsiliasjon (:from til :to)', + 'reconciliation_error' => 'Due to an error the transactions were marked as reconciled but the correction has not been stored: :error.', + 'reconciliation_transaction_title' => 'Reconciliation (:from to :to)', + 'sum_of_reconciliation' => 'Sum of reconciliation', 'reconcile_this_account' => 'Avstem denne kontoen', 'confirm_reconciliation' => 'Bekreft avstemming', 'submitted_start_balance' => 'Innsendt startsaldo', @@ -815,7 +807,7 @@ return [ 'interest_calc_daily' => 'Per dag', 'interest_calc_monthly' => 'Per måned', 'interest_calc_yearly' => 'Per år', - 'initial_balance_account' => 'Start saldo for konto :name', + 'initial_balance_account' => 'Initial balance account of :account', // categories: 'new_category' => 'Ny kategori', @@ -847,6 +839,8 @@ return [ 'deleted_deposit' => 'Slettet innskudd ":description"', 'deleted_transfer' => 'Slettet overføring ":description"', 'stored_journal' => 'Opprettet ny transaksjon ":description"', + 'stored_journal_no_descr' => 'Successfully created your new transaction', + 'updated_journal_no_descr' => 'Successfully updated your transaction', 'select_transactions' => 'Velg transaksjoner', 'rule_group_select_transactions' => 'Påfør ":title" på transaksjonene', 'rule_select_transactions' => 'Påfør ":title" på transaksjonene', @@ -855,26 +849,33 @@ return [ 'mass_delete_journals' => 'Slett flere transaksjoner', 'mass_edit_journals' => 'Rediger flere transaksjoner', 'mass_bulk_journals' => 'Rediger flere transaksjoner samtidig', - 'mass_bulk_journals_explain' => 'Hvis du ikke ønsker å endre transaksjonene dine en-etter-en, kan du redigere flere av dem på en gang ved hjelp av massedigeringsfunksjonen. Bare velg kategori, tagg eller budsjett i feltene under, så vil alle transaksjonene i tabellen bli oppdatert.', + 'mass_bulk_journals_explain' => 'This form allows you to change properties of the transactions listed below in one sweeping update. All the transactions in the table will be updated when you change the parameters you see here.', + 'part_of_split' => 'This transaction is part of a split transaction. If you have not selected all the splits, you may end up with changing only half the transaction.', 'bulk_set_new_values' => 'Bruk innstillingene nedenfor for å angi nye verdier. Hvis du lar dem stå tomme, forblir de tomme. Vær også oppmerksom på at kun uttak vil bli gitt et budsjett.', 'no_bulk_category' => 'Ikke oppdater kategori', 'no_bulk_budget' => 'Ikke oppdater budsjett', - 'no_bulk_tags' => 'Ikke oppdater tagg(er)', - 'bulk_edit' => 'Masseredigering', - 'cannot_edit_other_fields' => 'Du kan ikke masseredigere andre felt enn de som er her, fordi det er ikke plass til å vise dem. Vennligst følg linken og rediger disse en om gangen hvis du må redigere disse feltene.', - 'no_budget' => '(ingen budsjett)', - 'no_budget_squared' => '(ingen budsjett)', - 'perm-delete-many' => 'Å slette mange elementer på en gang kan skape problemer. Vær forsiktig.', - 'mass_deleted_transactions_success' => 'Slettet :amount transaksjon(er).', - 'mass_edited_transactions_success' => 'Oppdatert :amount transaksjon(er)', - 'opt_group_' => '(ingen kontotype)', + 'no_bulk_tags' => 'Ikke oppdater tagg(er)', + 'mass_edit' => 'Edit selected individually', + 'bulk_edit' => 'Edit selected in bulk', + 'mass_delete' => 'Delete selected', + 'cannot_edit_other_fields' => 'Du kan ikke masseredigere andre felt enn de som er her, fordi det er ikke plass til å vise dem. Vennligst følg linken og rediger disse en om gangen hvis du må redigere disse feltene.', + 'cannot_change_amount_reconciled' => 'You can\'t change the amount of reconciled transactions.', + 'no_budget' => '(ingen budsjett)', + 'no_budget_squared' => '(ingen budsjett)', + 'perm-delete-many' => 'Deleting many items in one go can be very disruptive. Please be cautious. You can delete part of a split transaction from this page, so take care.', + 'mass_deleted_transactions_success' => 'Slettet :amount transaksjon(er).', + 'mass_edited_transactions_success' => 'Oppdatert :amount transaksjon(er)', + 'opt_group_' => '(ingen kontotype)', 'opt_group_no_account_type' => '(ingen kontotype)', 'opt_group_defaultAsset' => 'Standard aktivakontoer', 'opt_group_savingAsset' => 'Sparekontoer', 'opt_group_sharedAsset' => 'Delte aktivakontoer', 'opt_group_ccAsset' => 'Kredittkort', 'opt_group_cashWalletAsset' => 'Kontant lommebok', + 'opt_group_expense_account' => 'Expense accounts', + 'opt_group_revenue_account' => 'Revenue accounts', 'opt_group_l_Loan' => 'Gjeld: Lån', + 'opt_group_cash_account' => 'Cash account', 'opt_group_l_Debt' => 'Gjeld: Gjeld', 'opt_group_l_Mortgage' => 'Gjeld: Huslån', 'opt_group_l_Credit card' => 'Gjeld: Kredittkort', @@ -973,7 +974,7 @@ return [ 'errors' => 'Feil', 'debt_start_date' => 'Startdato for gjeld', 'debt_start_amount' => 'Start beløp for gjeld', - 'debt_start_amount_help' => 'Skriv inn opprinnelig beløp for denne gjeld som et positivt tall. Du kan også angi gjeldende beløp. Pass på å redigere datoen nedenfor slik at det passer.', + 'debt_start_amount_help' => 'If you owe an amount its best to enter a negative amount, because it influences your net worth. If you\'re owed an amount the same applies. Check out the help pages for more information.', 'store_new_liabilities_account' => 'Lagre ny gjeld', 'edit_liabilities_account' => 'Rediger gjeld ":name"', @@ -1145,6 +1146,7 @@ return [ 'deleted_piggy_bank' => 'Slettet sparegris ":name"', 'added_amount_to_piggy' => 'La til :amount i ":name"', 'removed_amount_from_piggy' => 'Fjernet :amount fra ":name"', + 'piggy_events' => 'Related piggy banks', // tags 'delete_tag' => 'Slett tag ":tag"', @@ -1154,47 +1156,57 @@ return [ 'updated_tag' => 'Oppdatert tag ":tag"', 'created_tag' => 'Tag ":tag" er opprettet!', - 'transaction_journal_information' => 'Transaksjonsinformasjon', - 'transaction_journal_meta' => 'Metainformasjon', - 'total_amount' => 'Totalbeløp', - 'number_of_decimals' => 'Antall desimaler', + 'transaction_journal_information' => 'Transaksjonsinformasjon', + 'transaction_journal_meta' => 'Metainformasjon', + 'transaction_journal_more' => 'More information', + 'att_part_of_journal' => 'Stored under ":journal"', + 'total_amount' => 'Totalbeløp', + 'number_of_decimals' => 'Antall desimaler', // administration - 'administration' => 'Administrasjon', - 'user_administration' => 'Brukeradministrasjon', - 'list_all_users' => 'Alle brukere', - 'all_users' => 'Alle brukere', - 'instance_configuration' => 'Konfigurasjon', - 'firefly_instance_configuration' => 'Konfigurasjonsalternativer for Firefly III', - 'setting_single_user_mode' => 'Enkeltbrukermodus', - 'setting_single_user_mode_explain' => 'Som standard godtar Firefly III bare en (1) registrering: deg. Dette er et sikkerhetstiltak, som hindrer andre i å bruke din instans hvis du ikke tillater dem det. Fremtidige registreringer er blokkert. Når du krysser av denne ruten kan andre bruke din forekomst, forutsatt at de kan nå serveren (når den er koblet til Internett).', - 'store_configuration' => 'Lagre konfigurasjon', - 'single_user_administration' => 'Brukeradministrasjon for :email', - 'edit_user' => 'Rediger bruker :email', - 'hidden_fields_preferences' => 'Ikke alle feltene er synlige akkurat nå. Du må aktivere dem under innstillinger.', - 'user_data_information' => 'Brukerdata', - 'user_information' => 'Brukerinformasjon', - 'total_size' => 'totalstørrelse', - 'budget_or_budgets' => 'budsjett', - 'budgets_with_limits' => 'budsjett med konfigurert beløp', - 'nr_of_rules_in_total_groups' => ':count_rules regler i :count_groups regel gruppe(r)', - 'tag_or_tags' => 'tag(s)', - 'configuration_updated' => 'Konfigurasjonen er oppdatert', - 'setting_is_demo_site' => 'Demo nettsted', - 'setting_is_demo_site_explain' => 'Hvis du avhuker denne boksen, vil installasjonen oppføre seg som om det er en demo site, som kan ha rare bivirkninger.', - 'block_code_bounced' => 'Epostmelding(er) kunne ikke leveres', - 'block_code_expired' => 'Demo-konto utløpt', - 'no_block_code' => 'Ingen grunn for utestengelse eller brukeren er ikke utestengt', - 'block_code_email_changed' => 'Brukeren har ikke bekreftet ny e-postadresse ennå', - 'admin_update_email' => 'I motsetning til profilsiden, vil brukeren IKKE bli varslet om at e-postadressen er endret!', - 'update_user' => 'Oppdater bruker', - 'updated_user' => 'Brukerdata er endret.', - 'delete_user' => 'Slett bruker :email', - 'user_deleted' => 'Brukeren er slettet', - 'send_test_email' => 'Send test-epostmelding', - 'send_test_email_text' => 'For å se om installasjonen er kapabel til å sende mail, vennligst trykk på denne knappen. Du vil ikke se en feilmelding her, (hvis det kommer en) kun logg filene som vil vise feilmeldinger. Du kan trykke på denne knappen så mange ganger du ønsker, det er ingen spam kontroll. Meldingen vil bli sent til :email og bør ankomme fort.', - 'send_message' => 'Send melding', - 'send_test_triggered' => 'Test ble utløst. Sjekk innboksen din og loggfilene.', + 'administration' => 'Administrasjon', + 'user_administration' => 'Brukeradministrasjon', + 'list_all_users' => 'Alle brukere', + 'all_users' => 'Alle brukere', + 'instance_configuration' => 'Konfigurasjon', + 'firefly_instance_configuration' => 'Konfigurasjonsalternativer for Firefly III', + 'setting_single_user_mode' => 'Enkeltbrukermodus', + 'setting_single_user_mode_explain' => 'Som standard godtar Firefly III bare en (1) registrering: deg. Dette er et sikkerhetstiltak, som hindrer andre i å bruke din instans hvis du ikke tillater dem det. Fremtidige registreringer er blokkert. Når du krysser av denne ruten kan andre bruke din forekomst, forutsatt at de kan nå serveren (når den er koblet til Internett).', + 'store_configuration' => 'Lagre konfigurasjon', + 'single_user_administration' => 'Brukeradministrasjon for :email', + 'edit_user' => 'Rediger bruker :email', + 'hidden_fields_preferences' => 'You can enable more transaction options in your settings.', + 'user_data_information' => 'Brukerdata', + 'user_information' => 'Brukerinformasjon', + 'total_size' => 'totalstørrelse', + 'budget_or_budgets' => 'budsjett', + 'budgets_with_limits' => 'budsjett med konfigurert beløp', + 'nr_of_rules_in_total_groups' => ':count_rules regler i :count_groups regel gruppe(r)', + 'tag_or_tags' => 'tag(s)', + 'configuration_updated' => 'Konfigurasjonen er oppdatert', + 'setting_is_demo_site' => 'Demo nettsted', + 'setting_is_demo_site_explain' => 'Hvis du avhuker denne boksen, vil installasjonen oppføre seg som om det er en demo site, som kan ha rare bivirkninger.', + 'block_code_bounced' => 'Epostmelding(er) kunne ikke leveres', + 'block_code_expired' => 'Demo-konto utløpt', + 'no_block_code' => 'Ingen grunn for utestengelse eller brukeren er ikke utestengt', + 'block_code_email_changed' => 'Brukeren har ikke bekreftet ny e-postadresse ennå', + 'admin_update_email' => 'I motsetning til profilsiden, vil brukeren IKKE bli varslet om at e-postadressen er endret!', + 'update_user' => 'Oppdater bruker', + 'updated_user' => 'Brukerdata er endret.', + 'delete_user' => 'Slett bruker :email', + 'user_deleted' => 'Brukeren er slettet', + 'send_test_email' => 'Send test-epostmelding', + 'send_test_email_text' => 'For å se om installasjonen er kapabel til å sende mail, vennligst trykk på denne knappen. Du vil ikke se en feilmelding her, (hvis det kommer en) kun logg filene som vil vise feilmeldinger. Du kan trykke på denne knappen så mange ganger du ønsker, det er ingen spam kontroll. Meldingen vil bli sent til :email og bør ankomme fort.', + 'send_message' => 'Send melding', + 'send_test_triggered' => 'Test ble utløst. Sjekk innboksen din og loggfilene.', + + 'split_transaction_title' => 'Description of the split transaction', + 'split_title_help' => 'If you create a split transaction, there must be a global description for all splits of the transaction.', + 'transaction_information' => 'Transaction information', + 'you_create_transfer' => 'You\'re creating a transfer.', + 'you_create_withdrawal' => 'You\'re creating a withdrawal.', + 'you_create_deposit' => 'You\'re creating a deposit.', + // links 'journal_link_configuration' => 'Konfigurasjon av transaksjonslenker', @@ -1214,12 +1226,13 @@ return [ 'do_not_save_connection' => '(ikke lagre kobling)', 'link_transaction' => 'Sammenkoble transaksjon', 'link_to_other_transaction' => 'Koble denne transaksjonen til en annen transaksjon', - 'select_transaction_to_link' => 'Velg en transaksjon å koble til denne transaksjonen', + 'select_transaction_to_link' => 'Select a transaction to link this transaction to. The links are currently unused in Firefly III (apart from being shown), but I plan to change this in the future. Use the search box to select a transaction either by title or by ID. If you want to add custom link types, check out the administration section.', 'this_transaction' => 'Denne transaksjonen', 'transaction' => 'Transaksjon', 'comments' => 'Kommentarer', - 'to_link_not_found' => 'Hvis transaksjonen du vil koble til ikke vises, kan du skrive inn en ID.', + 'link_notes' => 'Any notes you wish to store with the link.', 'invalid_link_selection' => 'Kan ikke koble disse transaksjonene sammen', + 'selected_transaction' => 'Selected transaction', 'journals_linked' => 'Transaksjonene er knyttet sammen.', 'journals_error_linked' => 'Disse transaksjonene er allerede knyttet sammen.', 'journals_link_to_self' => 'Du kan ikke koble en transaksjon til seg selv', @@ -1258,12 +1271,11 @@ return [ 'split_this_withdrawal' => 'Del dette uttaket', 'split_this_deposit' => 'Del dette innskuddet', 'split_this_transfer' => 'Del denne overføringen', - 'cannot_edit_multiple_source' => 'Du kan ikke redigere splittet transaksjon #:id med beskrivelse ":description" fordi den inneholder flere kontokilder.', - 'cannot_edit_multiple_dest' => 'Du kan ikke redigere splittet transaksjon #:id med beskrivelse ":description" fordi den inneholder flere kontodestinasjoner.', - 'cannot_edit_reconciled' => 'Du kan ikke redigere transaksjon #:id med beskrivelse ":description" fordi den er blitt merket som rekonsiliert (behandlet).', 'cannot_edit_opening_balance' => 'Du kan ikke redigere åpningssaldoen til en konto.', 'no_edit_multiple_left' => 'Du har ikke valgt en tillatt transaksjon for redigering.', - 'cannot_convert_split_journal' => 'Kan ikke konvertere en splittet transaksjon', + 'breadcrumb_convert_group' => 'Convert transaction', + 'convert_invalid_source' => 'Source information is invalid for transaction #%d.', + 'convert_invalid_destination' => 'Destination information is invalid for transaction #%d.', // Import page (general strings only) 'import_index_title' => 'Importer transaksjoner til Firefly III', @@ -1389,4 +1401,15 @@ return [ 'will_jump_monday' => 'Opprettes på Mandag i stedet for i helgene.', 'except_weekends' => 'Unntatt helger', 'recurrence_deleted' => 'Gjentakende transaksjon ":title" slettet', + + // new lines for summary controller. + 'box_balance_in_currency' => 'Balance (:currency)', + 'box_spent_in_currency' => 'Spent (:currency)', + 'box_earned_in_currency' => 'Earned (:currency)', + 'box_bill_paid_in_currency' => 'Bills paid (:currency)', + 'box_bill_unpaid_in_currency' => 'Bills unpaid (:currency)', + 'box_left_to_spend_in_currency' => 'Left to spend (:currency)', + 'box_net_worth_in_currency' => 'Net worth (:currency)', + 'box_spend_per_day' => 'Left to spend per day: :amount', + ]; diff --git a/resources/lang/nb_NO/form.php b/resources/lang/nb_NO/form.php index 764a2419f9..b289f7db07 100644 --- a/resources/lang/nb_NO/form.php +++ b/resources/lang/nb_NO/form.php @@ -57,20 +57,21 @@ return [ 'asset_source_account' => 'Kildekonto', 'journal_description' => 'Beskrivelse', 'note' => 'Notater', + 'store_new_transaction' => 'Store new transaction', 'split_journal' => 'Del opp denne transaksjonen', 'split_journal_explanation' => 'Del denne transaksjonen opp i flere deler', 'currency' => 'Valuta', 'account_id' => 'Aktivakonto', 'budget_id' => 'Busjett', - 'openingBalance' => 'Startsaldo', + 'opening_balance' => 'Opening balance', 'tagMode' => 'Taggmodus', 'tag_position' => 'Stedtagg', - 'virtualBalance' => 'Virtuell balanse', + 'virtual_balance' => 'Virtual balance', 'targetamount' => 'Målbeløp', - 'accountRole' => 'Kontorolle', - 'openingBalanceDate' => 'Startsaldodato', - 'ccType' => 'Betalingsplan for kredittkort', - 'ccMonthlyPaymentDate' => 'Månedlig betalingsdato for kredittkort', + 'account_role' => 'Account role', + 'opening_balance_date' => 'Opening balance date', + 'cc_type' => 'Credit card payment plan', + 'cc_monthly_payment_date' => 'Credit card monthly payment date', 'piggy_bank_id' => 'Sparegris', 'returnHere' => 'Gå tilbake hit', 'returnHereExplanation' => 'Gå tilbake hit etter lagring for å legge til på nytt.', @@ -118,7 +119,7 @@ return [ 'symbol' => 'Symbol', 'code' => 'Kode', 'iban' => 'IBAN', - 'accountNumber' => 'Kontonummer', + 'account_number' => 'Account number', 'creditCardNumber' => 'Kredittkortnummer', 'has_headers' => 'Overskrifter', 'date_format' => 'Datoformat', @@ -139,12 +140,8 @@ return [ 'stop_processing' => 'Stopp prosessering', 'start_date' => 'Startgrense', 'end_date' => 'Sluttgrense', - 'export_start_range' => 'Start på eksportgrense', - 'export_end_range' => 'Slutt på eksportgrense', - 'export_format' => 'Filformat', 'include_attachments' => 'Ta med opplastede vedlegg', 'include_old_uploads' => 'Ta med importert data', - 'accounts' => 'Eksporter transaksjoner fra disse kontoene', 'delete_account' => 'Slett konto ":name"', 'delete_bill' => 'Slett regning ":name"', 'delete_budget' => 'Slett budsjett ":name"', @@ -256,4 +253,7 @@ return [ 'weekend' => 'Helg', 'client_secret' => 'Client Secret', + 'withdrawal_destination_id' => 'Destination account', + 'deposit_source_id' => 'Source account', + ]; diff --git a/resources/lang/nb_NO/import.php b/resources/lang/nb_NO/import.php index 24548fa9ef..48c788156a 100644 --- a/resources/lang/nb_NO/import.php +++ b/resources/lang/nb_NO/import.php @@ -207,6 +207,10 @@ return [ 'specific_rabo_descr' => 'Fikser potensielle problemer med Rabobank filer', 'specific_pres_name' => 'President\'s Choice Financial CA', 'specific_pres_descr' => 'Fikser potensielle problemer med PC filer', + 'specific_belfius_name' => 'Belfius BE', + 'specific_belfius_descr' => 'Fikser potensielle problemer med Belfius filer', + 'specific_ingbelgium_name' => 'ING BE', + 'specific_ingbelgium_descr' => 'Fixes potential problems with ING Belgium files', // job configuration for file provider (stage: roles) 'job_config_roles_title' => 'Importoppsett (3/4) - Definer hver kolonnes rolle', 'job_config_roles_text' => 'Hver kolonne i CSV filen inneholder visse data. Vennligst indiker hvilken type data importen kan forvente. "Map" valget indikerer at du vil knytte hver oppføring funnet i kolonnen til en verdi in databasen. En ofte knyttet kolonne is kolonnen som inneholder IBAN til motstående konto. Dette kan enkelt matches mot IBAN verdier som er i databasen allerede.', @@ -291,14 +295,14 @@ return [ 'column_rabo-debit-credit' => 'Rabobank spesifikk debet/kreditt indikator', 'column_ing-debit-credit' => 'ING spesifikk debet/kreditt indikator', 'column_generic-debit-credit' => 'Generisk bank debet/kreditt indikator', - 'column_sepa-ct-id' => 'SEPA ende-til-ende identifikator', - 'column_sepa-ct-op' => 'SEPA Motstående kontoidentifikator', - 'column_sepa-db' => 'SEPA Mandat identifikator', - 'column_sepa-cc' => 'SEPA klareringskode', - 'column_sepa-ci' => 'SEPA kreditoridentifikator', - 'column_sepa-ep' => 'SEPA Eksternt formål', - 'column_sepa-country' => 'SEPA landskode', - 'column_sepa-batch-id' => 'SEPA sats/parti ID', + 'column_sepa_ct_id' => 'SEPA ende-til-ende identifikator', + 'column_sepa_ct_op' => 'SEPA Motstående kontoidentifikator', + 'column_sepa_db' => 'SEPA Mandat identifikator', + 'column_sepa_cc' => 'SEPA klareringskode', + 'column_sepa_ci' => 'SEPA kreditoridentifikator', + 'column_sepa_ep' => 'SEPA Eksternt formål', + 'column_sepa_country' => 'SEPA landskode', + 'column_sepa_batch_id' => 'SEPA Batch ID', 'column_tags-comma' => 'Tagger (kommaseparerte)', 'column_tags-space' => 'Tagger (oppdelt med mellomrom)', 'column_account-number' => 'Aktivakonto (kontonummer)', @@ -306,4 +310,7 @@ return [ 'column_note' => 'Notat(er)', 'column_internal-reference' => 'Intern referanse', + // error message + 'duplicate_row' => 'Row #:row (":description") could not be imported. It already exists.', + ]; diff --git a/resources/lang/nb_NO/intro.php b/resources/lang/nb_NO/intro.php index 6b8986a9c3..93f86fba17 100644 --- a/resources/lang/nb_NO/intro.php +++ b/resources/lang/nb_NO/intro.php @@ -24,39 +24,61 @@ declare(strict_types=1); return [ // index - 'index_intro' => 'Velkommen til forsiden til Firefly III. Ta deg tid til å gå gjennom denne introen for å få en følelse av hvordan Firefly III fungerer.', - 'index_accounts-chart' => 'Dette diagrammet viser gjeldende saldo på aktivakontoene dine. Du kan velge kontoene som er synlige her under innstillinger.', - 'index_box_out_holder' => 'Denne lille boksen og boksene ved siden av gir deg rask oversikt over din økonomiske situasjon.', - 'index_help' => 'Hvis du trenger hjelp til en side eller et skjema, trykker du på denne knappen.', - 'index_outro' => 'De fleste sidene av Firefly III vil starte med en liten gjennomgang slik som denne. Ta kontakt med meg hvis du har spørsmål eller kommentarer. Sett igang!', - 'index_sidebar-toggle' => 'For å opprette nye transaksjoner, kontoer eller andre ting, bruk menyen under dette ikonet.', + 'index_intro' => 'Velkommen til forsiden til Firefly III. Ta deg tid til å gå gjennom denne introen for å få en følelse av hvordan Firefly III fungerer.', + 'index_accounts-chart' => 'Dette diagrammet viser gjeldende saldo på aktivakontoene dine. Du kan velge kontoene som er synlige her under innstillinger.', + 'index_box_out_holder' => 'Denne lille boksen og boksene ved siden av gir deg rask oversikt over din økonomiske situasjon.', + 'index_help' => 'Hvis du trenger hjelp til en side eller et skjema, trykker du på denne knappen.', + 'index_outro' => 'De fleste sidene av Firefly III vil starte med en liten gjennomgang slik som denne. Ta kontakt med meg hvis du har spørsmål eller kommentarer. Sett igang!', + 'index_sidebar-toggle' => 'For å opprette nye transaksjoner, kontoer eller andre ting, bruk menyen under dette ikonet.', + 'index_cash_account' => 'These are the accounts created so far. You can use the cash account to track cash expenses but it\'s not mandatory of course.', + + // transactions (withdrawal) + 'transactions_create_withdrawal_source' => 'Select your favorite asset account or liability from this dropdown.', + 'transactions_create_withdrawal_destination' => 'Select an expense account here. Leave it empty if you want to make a cash expense.', + 'transactions_create_withdrawal_foreign_currency' => 'Use this field to set a foreign currency and amount.', + 'transactions_create_withdrawal_more_meta' => 'Plenty of other meta data you set in these fields.', + 'transactions_create_withdrawal_split_add' => 'If you want to split a transaction, add more splits with this button', + + // transactions (deposit) + 'transactions_create_deposit_source' => 'Select or type the payee in this auto-completing dropdown/textbox. Leave it empty if you want to make a cash deposit.', + 'transactions_create_deposit_destination' => 'Select an asset or liability account here.', + 'transactions_create_deposit_foreign_currency' => 'Use this field to set a foreign currency and amount.', + 'transactions_create_deposit_more_meta' => 'Plenty of other meta data you set in these fields.', + 'transactions_create_deposit_split_add' => 'If you want to split a transaction, add more splits with this button', + + // transactions (transfer) + 'transactions_create_transfer_source' => 'Select the source asset account here.', + 'transactions_create_transfer_destination' => 'Select the destination asset account here.', + 'transactions_create_transfer_foreign_currency' => 'Use this field to set a foreign currency and amount.', + 'transactions_create_transfer_more_meta' => 'Plenty of other meta data you set in these fields.', + 'transactions_create_transfer_split_add' => 'If you want to split a transaction, add more splits with this button', // create account: - 'accounts_create_iban' => 'Gi kontoene dine en gyldig IBAN. Dette gjør dataimport lettere i fremtiden.', - 'accounts_create_asset_opening_balance' => 'Aktivakontoer kan ha en "åpningssaldo" som indikerer starten på denne kontoens historie i Firefly III.', - 'accounts_create_asset_currency' => 'Firefly III støtter flere valutaer. Aktivakontoer har en hovedvaluta, som du må sette her.', - 'accounts_create_asset_virtual' => 'Det kan noen ganger hjelpe å gi kontoen din en virtuell saldo: et ekstra beløp blir alltid lagt til eller fjernet fra den faktiske saldoen.', + 'accounts_create_iban' => 'Gi kontoene dine en gyldig IBAN. Dette gjør dataimport lettere i fremtiden.', + 'accounts_create_asset_opening_balance' => 'Aktivakontoer kan ha en "åpningssaldo" som indikerer starten på denne kontoens historie i Firefly III.', + 'accounts_create_asset_currency' => 'Firefly III støtter flere valutaer. Aktivakontoer har en hovedvaluta, som du må sette her.', + 'accounts_create_asset_virtual' => 'Det kan noen ganger hjelpe å gi kontoen din en virtuell saldo: et ekstra beløp blir alltid lagt til eller fjernet fra den faktiske saldoen.', // budgets index - 'budgets_index_intro' => 'Budsjetter brukes til å styre din økonomi og er en av kjernefunksjonene i Firefly III.', - 'budgets_index_set_budget' => 'Sett ditt totale budsjett for hver periode, så Firefly III kan fortelle deg om du har budsjettert med alle tilgjengelige penger.', - 'budgets_index_see_expenses_bar' => 'Når du bruker penger vil denne linjen fylles opp.', - 'budgets_index_navigate_periods' => 'Naviger gjennom perioder for å enkelt sette budsjetter på forhånd.', - 'budgets_index_new_budget' => 'Opprett nye budsjetter etter behov.', - 'budgets_index_list_of_budgets' => 'Bruk denne tabellen til å angi beløp for hvert budsjett og se hvordan du klarer deg.', - 'budgets_index_outro' => 'Hvis du vil vite mer om budsjettering, trykk på hjelp-ikonet øverst til høyre.', + 'budgets_index_intro' => 'Budsjetter brukes til å styre din økonomi og er en av kjernefunksjonene i Firefly III.', + 'budgets_index_set_budget' => 'Sett ditt totale budsjett for hver periode, så Firefly III kan fortelle deg om du har budsjettert med alle tilgjengelige penger.', + 'budgets_index_see_expenses_bar' => 'Når du bruker penger vil denne linjen fylles opp.', + 'budgets_index_navigate_periods' => 'Naviger gjennom perioder for å enkelt sette budsjetter på forhånd.', + 'budgets_index_new_budget' => 'Opprett nye budsjetter etter behov.', + 'budgets_index_list_of_budgets' => 'Bruk denne tabellen til å angi beløp for hvert budsjett og se hvordan du klarer deg.', + 'budgets_index_outro' => 'Hvis du vil vite mer om budsjettering, trykk på hjelp-ikonet øverst til høyre.', // reports (index) - 'reports_index_intro' => 'Bruk disse rapportene for å få detaljert innsikt i din økonomi.', - 'reports_index_inputReportType' => 'Velg en rapporttype. Sjekk ut hjelpesidene for å se hva hver rapport viser deg.', - 'reports_index_inputAccountsSelect' => 'Du kan ekskludere eller inkludere aktivakontoer etter eget ønske.', - 'reports_index_inputDateRange' => 'Den valgte datoperioden er helt opp til deg: fra en dag, og opptil 10 år.', - 'reports_index_extra-options-box' => 'Avhengig av hvilken rapport du har valgt, kan du velge ekstra filtre og alternativer her. Følg med på denne boksen når du endrer rapporttyper.', + 'reports_index_intro' => 'Bruk disse rapportene for å få detaljert innsikt i din økonomi.', + 'reports_index_inputReportType' => 'Velg en rapporttype. Sjekk ut hjelpesidene for å se hva hver rapport viser deg.', + 'reports_index_inputAccountsSelect' => 'Du kan ekskludere eller inkludere aktivakontoer etter eget ønske.', + 'reports_index_inputDateRange' => 'Den valgte datoperioden er helt opp til deg: fra en dag, og opptil 10 år.', + 'reports_index_extra-options-box' => 'Avhengig av hvilken rapport du har valgt, kan du velge ekstra filtre og alternativer her. Følg med på denne boksen når du endrer rapporttyper.', // reports (reports) - 'reports_report_default_intro' => 'Denne rapporten gir deg en rask og omfattende oversikt over økonomien din. Hvis du ønsker å se noe annet her, ikke nøl med å kontakte meg!', - 'reports_report_audit_intro' => 'Denne rapporten gir deg detaljert innsikt i aktivakontoene dine.', - 'reports_report_audit_optionsBox' => 'Bruk disse avkrysningssboksene for å vise eller skjule kolonnene du er interessert i.', + 'reports_report_default_intro' => 'Denne rapporten gir deg en rask og omfattende oversikt over økonomien din. Hvis du ønsker å se noe annet her, ikke nøl med å kontakte meg!', + 'reports_report_audit_intro' => 'Denne rapporten gir deg detaljert innsikt i aktivakontoene dine.', + 'reports_report_audit_optionsBox' => 'Bruk disse avkrysningssboksene for å vise eller skjule kolonnene du er interessert i.', 'reports_report_category_intro' => 'Denne rapporten gir deg innblikk i en eller flere kategorier.', 'reports_report_category_pieCharts' => 'Disse diagrammene gir deg innblikk i utgifter og inntekt per kategori eller per konto.', diff --git a/resources/lang/nb_NO/list.php b/resources/lang/nb_NO/list.php index 319409ce0e..1d37e68cc5 100644 --- a/resources/lang/nb_NO/list.php +++ b/resources/lang/nb_NO/list.php @@ -23,71 +23,71 @@ declare(strict_types=1); return [ - 'buttons' => 'Knapper', - 'icon' => 'Ikon', - 'id' => 'ID', - 'create_date' => 'Opprettet', - 'update_date' => 'Oppdatert', - 'updated_at' => 'Oppdatert', - 'balance_before' => 'Saldo før', - 'balance_after' => 'Saldo etter', - 'name' => 'Navn', - 'role' => 'Rolle', - 'currentBalance' => 'Nåværende saldo', - 'linked_to_rules' => 'Relevante regler', - 'active' => 'Er aktiv?', - 'lastActivity' => 'Siste aktivitet', - 'balanceDiff' => 'Saldodifferanse', - 'matchesOn' => 'Traff på', - 'account_type' => 'Kontotype', - 'created_at' => 'Opprettet', - 'account' => 'Konto', - 'matchingAmount' => 'Beløp', - 'split_number' => 'Del #', - 'destination' => 'Mål', - 'source' => 'Kilde', - 'next_expected_match' => 'Neste forventede treff', - 'automatch' => 'Automatisk treff?', - 'repeat_freq' => 'Gjentas', - 'description' => 'Beskrivelse', - 'amount' => 'Beløp', - 'internal_reference' => 'Intern referanse', - 'date' => 'Dato', - 'interest_date' => 'Rentedato', - 'book_date' => 'Bokføringsdato', - 'process_date' => 'Prosesseringsdato', - 'due_date' => 'Forfallsdato', - 'payment_date' => 'Betalingsdato', - 'invoice_date' => 'Fakturadato', - 'interal_reference' => 'Intern referanse', - 'notes' => 'Notater', - 'from' => 'Fra', - 'piggy_bank' => 'Sparegris', - 'to' => 'Til', - 'budget' => 'Busjett', - 'category' => 'Kategori', - 'bill' => 'Regning', - 'withdrawal' => 'Uttak', - 'deposit' => 'Innskudd', - 'transfer' => 'Overføring', - 'type' => 'Type', - 'completed' => 'Ferdig', - 'iban' => 'IBAN', - 'paid_current_period' => 'Betalt denne perioden', - 'email' => 'Epost', - 'registered_at' => 'Registrert den', - 'is_blocked' => 'Er sperret', - 'is_admin' => 'Er admin', - 'has_two_factor' => 'Har 2FA', - 'blocked_code' => 'Blokkert kode', - 'source_account' => 'Kildekonto', + 'buttons' => 'Knapper', + 'icon' => 'Ikon', + 'id' => 'ID', + 'create_date' => 'Opprettet', + 'update_date' => 'Oppdatert', + 'updated_at' => 'Oppdatert', + 'balance_before' => 'Saldo før', + 'balance_after' => 'Saldo etter', + 'name' => 'Navn', + 'role' => 'Rolle', + 'currentBalance' => 'Nåværende saldo', + 'linked_to_rules' => 'Relevante regler', + 'active' => 'Er aktiv?', + 'transaction_type' => 'Type', + 'lastActivity' => 'Siste aktivitet', + 'balanceDiff' => 'Saldodifferanse', + 'matchesOn' => 'Traff på', + 'account_type' => 'Kontotype', + 'created_at' => 'Opprettet', + 'account' => 'Konto', + 'matchingAmount' => 'Beløp', + 'split_number' => 'Del #', + 'destination' => 'Mål', + 'source' => 'Kilde', + 'next_expected_match' => 'Neste forventede treff', + 'automatch' => 'Automatisk treff?', + 'repeat_freq' => 'Gjentas', + 'description' => 'Beskrivelse', + 'amount' => 'Beløp', + 'internal_reference' => 'Intern referanse', + 'date' => 'Dato', + 'interest_date' => 'Rentedato', + 'book_date' => 'Bokføringsdato', + 'process_date' => 'Prosesseringsdato', + 'due_date' => 'Forfallsdato', + 'payment_date' => 'Betalingsdato', + 'invoice_date' => 'Fakturadato', + 'interal_reference' => 'Intern referanse', + 'notes' => 'Notater', + 'from' => 'Fra', + 'piggy_bank' => 'Sparegris', + 'to' => 'Til', + 'budget' => 'Busjett', + 'category' => 'Kategori', + 'bill' => 'Regning', + 'withdrawal' => 'Uttak', + 'deposit' => 'Innskudd', + 'transfer' => 'Overføring', + 'type' => 'Type', + 'completed' => 'Ferdig', + 'iban' => 'IBAN', + 'paid_current_period' => 'Betalt denne perioden', + 'email' => 'Epost', + 'registered_at' => 'Registrert den', + 'is_blocked' => 'Er sperret', + 'is_admin' => 'Er admin', + 'has_two_factor' => 'Har 2FA', + 'blocked_code' => 'Blokkert kode', + 'source_account' => 'Kildekonto', 'destination_account' => 'Målkonto', 'accounts_count' => 'Antall kontoer', 'journals_count' => 'Antall transaksjoner', 'attachments_count' => 'Antall vedlegg', 'bills_count' => 'Antall regninger', 'categories_count' => 'Antall kategorier', - 'export_jobs_count' => 'Antall eksportjobber', 'import_jobs_count' => 'Antall importjobber', 'budget_count' => 'Antall budsjetter', 'rule_and_groups_count' => 'Antall regler og regelgrupper', @@ -106,14 +106,14 @@ return [ 'account_on_spectre' => 'Konto (Spectre)', 'account_on_ynab' => 'Konto (YNAB)', 'do_import' => 'Importer fra denne kontoen', - 'sepa-ct-id' => 'SEPA ende-til-ende-identifikator', - 'sepa-ct-op' => 'SEPA Motkonto-identifikator', - 'sepa-db' => 'SEPA Mandat-identifikator', - 'sepa-country' => 'SEPA-land', - 'sepa-cc' => 'SEPA-klareringskode', - 'sepa-ep' => 'SEPA Eksternt formål', - 'sepa-ci' => 'SEPA kreditoridentifikator', - 'sepa-batch-id' => 'SEPA sats/parti ID', + 'sepa_ct_id' => 'SEPA ende-til-ende identifikator', + 'sepa_ct_op' => 'SEPA Motstående kontoidentifikator', + 'sepa_db' => 'SEPA Mandat identifikator', + 'sepa_country' => 'SEPA land', + 'sepa_cc' => 'SEPA klareringskode', + 'sepa_ep' => 'SEPA Eksternt formål', + 'sepa_ci' => 'SEPA kreditoridentifikator', + 'sepa_batch_id' => 'SEPA Batch ID', 'external_id' => 'Ekstern ID', 'account_at_bunq' => 'Konto med bunq', 'file_name' => 'Filnavn', diff --git a/resources/lang/nb_NO/validation.php b/resources/lang/nb_NO/validation.php index 49f1294235..14de7a4c24 100644 --- a/resources/lang/nb_NO/validation.php +++ b/resources/lang/nb_NO/validation.php @@ -36,12 +36,16 @@ return [ 'file_attached' => 'Opplasting av fil ":name" var vellykket.', 'must_exist' => 'IDen i feltet :attribute finnes ikke i databasen.', 'all_accounts_equal' => 'Alle kontoer i dette feltet må være like.', + 'group_title_mandatory' => 'En gruppetittel er obligatorisk når det er mer enn én transaksjon.', + 'transaction_types_equal' => 'Alle deler må være av samme type.', + 'invalid_transaction_type' => 'Ugyldig transaksjonstype.', 'invalid_selection' => 'Dine valg er ugyldig.', 'belongs_user' => 'Denne verdien er ugyldig for dette feltet.', 'at_least_one_transaction' => 'Trenger minst én transaksjon.', 'at_least_one_repetition' => 'Trenger minst en gjentagelse.', 'require_repeat_until' => 'Krever enten et antall repetisjoner eller en slutt dato (gjentas til). Ikke begge.', 'require_currency_info' => 'Innholdet i dette feltet er ugyldig uten valutainformasjon.', + 'require_currency_amount' => 'The content of this field is invalid without foreign amount information.', 'equal_description' => 'Transaksjonsbeskrivelsen bør ikke være lik global beskrivelse.', 'file_invalid_mime' => 'Kan ikke akseptere fil ":name" av typen ":mime" for opplasting.', 'file_too_large' => '":name"-filen er for stor.', @@ -135,8 +139,8 @@ return [ 'name' => 'navn', 'piggy_bank_id' => 'sparegris ID', 'targetamount' => 'målbeløp', - 'openingBalanceDate' => 'åpningssaldo dato', - 'openingBalance' => 'åpningssaldo', + 'opening_balance_date' => 'opening balance date', + 'opening_balance' => 'opening balance', 'match' => 'match', 'amount_min' => 'minimumsbeløp', 'amount_max' => 'maksimumsbeløp', @@ -164,4 +168,28 @@ return [ 'rule-trigger.4' => 'regel trigger #4', 'rule-trigger.5' => 'regel trigger #5', ], + + // validation of accounts: + 'withdrawal_source_need_data' => 'Trenger en gyldig kilde konto-ID og/eller gyldig kilde kontonavn for å fortsette.', + 'withdrawal_source_bad_data' => 'Finner ikke en gyldig kilde-konto ved å søke etter ID ":id" eller navn ":name".', + 'withdrawal_dest_need_data' => 'Trenger en gyldig destinasjons konto-ID og/eller gyldig destinasjons kontonavn for å fortsette.', + 'withdrawal_dest_bad_data' => 'Could not find a valid destination account when searching for ID ":id" or name ":name".', + + 'deposit_source_need_data' => 'Trenger en gyldig kilde konto-ID og/eller gyldig kilde kontonavn for å fortsette.', + 'deposit_source_bad_data' => 'Kunne ikke finne en gyldig kilde-konto ved å søke etter ID ":id" eller navn ":name".', + 'deposit_dest_need_data' => 'Trenger en gyldig destinasjons konto-ID og/eller gyldig destinasjons kontonavn for å fortsette.', + 'deposit_dest_bad_data' => 'Kunne ikke finne en gyldig destinasjons konto ved å søke etter ID ":id" eller navn ":name".', + + 'transfer_source_need_data' => 'Trenger en gyldig kilde konto-ID og/eller gyldig kilde kontonavn for å fortsette.', + 'transfer_source_bad_data' => 'Finner ikke en gyldig kilde-konto ved å søke etter ID ":id" eller navn ":name".', + 'transfer_dest_need_data' => 'Trenger en gyldig destinasjons konto-ID og/eller gyldig destinasjons kontonavn for å fortsette.', + 'transfer_dest_bad_data' => 'Kunne ikke finne en gyldig destinasjons konto ved å søke etter ID ":id" eller navn ":name".', + 'need_id_in_edit' => 'Each split must have transaction_journal_id (either valid ID or 0).', + + 'ob_source_need_data' => 'Need to get a valid source account ID and/or valid source account name to continue.', + 'ob_dest_need_data' => 'Need to get a valid destination account ID and/or valid destination account name to continue.', + 'ob_dest_bad_data' => 'Could not find a valid destination account when searching for ID ":id" or name ":name".', + + 'generic_invalid_source' => 'You can\'t use this account as the source account.', + 'generic_invalid_destination' => 'You can\'t use this account as the destination account.', ]; diff --git a/resources/lang/nl_NL/breadcrumbs.php b/resources/lang/nl_NL/breadcrumbs.php index 81dd5186bc..a660d40f2d 100644 --- a/resources/lang/nl_NL/breadcrumbs.php +++ b/resources/lang/nl_NL/breadcrumbs.php @@ -23,35 +23,37 @@ declare(strict_types=1); return [ - 'home' => 'Home', - 'edit_currency' => 'Wijzig valuta ":name"', - 'delete_currency' => 'Verwijder valuta ":name"', - 'newPiggyBank' => 'Nieuw spaarpotje', - 'edit_piggyBank' => 'Wijzig spaarpotje ":name"', - 'preferences' => 'Voorkeuren', - 'profile' => 'Profiel', - 'changePassword' => 'Verander je wachtwoord', - 'change_email' => 'Verander je emailadres', - 'bills' => 'Contracten', - 'newBill' => 'Nieuw contract', - 'edit_bill' => 'Wijzig contract ":name"', - 'delete_bill' => 'Verwijder contract ":name"', - 'reports' => 'Overzichten', - 'search_result' => 'Zoekresultaten voor ":query"', - 'withdrawal_list' => 'Uitgaven', - 'deposit_list' => 'Inkomsten', - 'transfer_list' => 'Overschrijvingen', - 'transfers_list' => 'Overschrijvingen', - 'reconciliation_list' => 'Afstemmingen', - 'create_withdrawal' => 'Sla nieuwe uitgave op', - 'create_deposit' => 'Sla nieuwe inkomsten op', - 'create_transfer' => 'Sla nieuwe overschrijving op', - 'edit_journal' => 'Wijzig transactie ":description"', - 'edit_reconciliation' => 'Wijzig ":description"', - 'delete_journal' => 'Verwijder transactie ":description"', - 'tags' => 'Tags', - 'createTag' => 'Maak nieuwe tag', - 'edit_tag' => 'Wijzig tag ":tag"', - 'delete_tag' => 'Verwijder tag ":tag"', - 'delete_journal_link' => 'Verwijder koppeling tussen transacties', + 'home' => 'Home', + 'edit_currency' => 'Wijzig valuta ":name"', + 'delete_currency' => 'Verwijder valuta ":name"', + 'newPiggyBank' => 'Nieuw spaarpotje', + 'edit_piggyBank' => 'Wijzig spaarpotje ":name"', + 'preferences' => 'Voorkeuren', + 'profile' => 'Profiel', + 'changePassword' => 'Verander je wachtwoord', + 'change_email' => 'Verander je emailadres', + 'bills' => 'Contracten', + 'newBill' => 'Nieuw contract', + 'edit_bill' => 'Wijzig contract ":name"', + 'delete_bill' => 'Verwijder contract ":name"', + 'reports' => 'Overzichten', + 'search_result' => 'Zoekresultaten voor ":query"', + 'withdrawal_list' => 'Uitgaven', + 'Withdrawal_list' => 'Uitgaven', + 'deposit_list' => 'Inkomsten', + 'transfer_list' => 'Overschrijvingen', + 'transfers_list' => 'Overschrijvingen', + 'reconciliation_list' => 'Afstemmingen', + 'create_withdrawal' => 'Sla nieuwe uitgave op', + 'create_deposit' => 'Sla nieuwe inkomsten op', + 'create_transfer' => 'Sla nieuwe overschrijving op', + 'create_new_transaction' => 'Maak een nieuwe transactie', + 'edit_journal' => 'Wijzig transactie ":description"', + 'edit_reconciliation' => 'Wijzig ":description"', + 'delete_journal' => 'Verwijder transactie ":description"', + 'tags' => 'Tags', + 'createTag' => 'Maak nieuwe tag', + 'edit_tag' => 'Wijzig tag ":tag"', + 'delete_tag' => 'Verwijder tag ":tag"', + 'delete_journal_link' => 'Verwijder koppeling tussen transacties', ]; diff --git a/resources/lang/nl_NL/firefly.php b/resources/lang/nl_NL/firefly.php index 2e0c619552..070694d23b 100644 --- a/resources/lang/nl_NL/firefly.php +++ b/resources/lang/nl_NL/firefly.php @@ -33,7 +33,7 @@ return [ 'last_seven_days' => 'Laatste zeven dagen', 'last_thirty_days' => 'Laatste dertig dagen', 'welcomeBack' => 'Hoe staat het er voor?', - 'welcome_back' => 'What\'s playing?', + 'welcome_back' => 'Hoe staat het er voor?', 'everything' => 'Alles', 'today' => 'vandaag', 'customRange' => 'Zelf bereik kiezen', @@ -54,6 +54,7 @@ return [ 'create_new_stuff' => 'Nieuw', 'new_withdrawal' => 'Nieuwe uitgave', 'create_new_transaction' => 'Maak nieuwe transactie', + 'new_transaction' => 'Nieuwe transactie', 'go_to_asset_accounts' => 'Bekijk je betaalrekeningen', 'go_to_budgets' => 'Ga naar je budgetten', 'go_to_categories' => 'Ga naar je categorieën', @@ -82,28 +83,32 @@ return [ 'help_for_this_page' => 'Hulp bij deze pagina', 'no_help_could_be_found' => 'Er kon geen helptekst worden gevonden.', 'no_help_title' => 'Sorry, er ging wat mis.', - 'two_factor_welcome' => 'Hoi :user!', - 'two_factor_enter_code' => 'Vul je authenticatiecode in. Je authenticatieapplicatie kan deze voor je genereren.', - 'two_factor_code_here' => 'Code', - 'two_factor_title' => 'Authenticatie in twee stappen', - 'authenticate' => 'Inloggen', - 'two_factor_forgot_title' => 'Authenticatie in twee stappen werkt niet meer', - 'two_factor_forgot' => 'Ik kan geen codes meer genereren.', - 'two_factor_lost_header' => 'Kan je geen codes meer genereren?', - 'two_factor_lost_intro' => 'Dit is helaas niet iets dat je kan resetten vanaf de site. Je hebt twee keuzes.', - 'two_factor_lost_fix_self' => 'Als dit jouw installatie van Firefly III is, vind je in de logboeken (storage/logs) instructies.', - 'two_factor_lost_fix_owner' => 'Zo niet, stuur dan een e-mail naar :site_owner en vraag of ze je authenticatie in twee stappen willen resetten.', - 'warning_much_data' => 'Het kan even duren voor :days dagen aan gegevens geladen zijn.', - 'registered' => 'Je bent geregistreerd!', - 'Default asset account' => 'Standaard betaalrekening', - 'no_budget_pointer' => 'Je hebt nog geen budgetten. Maak er een aantal op de budgetten-pagina. Met budgetten kan je je uitgaven beter bijhouden.', - 'Savings account' => 'Spaarrekening', - 'Credit card' => 'Credit card', - 'source_accounts' => 'Bronrekening(en)', - 'destination_accounts' => 'Doelrekening(en)', - 'user_id_is' => 'Je gebruikersnummer is :user', - 'field_supports_markdown' => 'Dit veld ondersteunt Markdown.', - 'need_more_help' => 'Als je meer hulp nodig hebt met Firefly III, open dan een ticket op Github.', + 'two_factor_welcome' => 'Hallo!', + 'two_factor_enter_code' => 'Vul je authenticatiecode in. Je authenticatieapplicatie kan deze voor je genereren.', + 'two_factor_code_here' => 'Code', + 'two_factor_title' => 'Authenticatie in twee stappen', + 'authenticate' => 'Inloggen', + 'two_factor_forgot_title' => 'Authenticatie in twee stappen werkt niet meer', + 'two_factor_forgot' => 'Ik kan geen codes meer genereren.', + 'two_factor_lost_header' => 'Kan je geen codes meer genereren?', + 'two_factor_lost_intro' => 'Als je ook je backupcodes kwijt bent heb je pech gehad. Dit kan je niet via de web-interface fixen. Je kan kiezen.', + 'two_factor_lost_fix_self' => 'Als je eigen installatie van Firefly III draait check je de logs in storage/logs voor instructies, of draai docker logs <container_id> voor instructies (ververs de pagina).', + 'two_factor_lost_fix_owner' => 'Zo niet, stuur dan een e-mail naar :site_owner en vraag of ze je authenticatie in twee stappen willen resetten.', + 'mfa_backup_code' => 'Je hebt een backupcode gebruikt om in te loggen op Firefly III. Deze kan je niet meer gebruiken dus streep hem weg.', + 'pref_two_factor_new_backup_codes' => 'Nieuwe backupcodes genereren', + 'pref_two_factor_backup_code_count' => 'Je hebt :count geldige backupcode(s).', + '2fa_i_have_them' => 'Ik heb ze opgeslagen!', + 'warning_much_data' => 'Het kan even duren voor :days dagen aan gegevens geladen zijn.', + 'registered' => 'Je bent geregistreerd!', + 'Default asset account' => 'Standaard betaalrekening', + 'no_budget_pointer' => 'Je hebt nog geen budgetten. Maak er een aantal op de budgetten-pagina. Met budgetten kan je je uitgaven beter bijhouden.', + 'Savings account' => 'Spaarrekening', + 'Credit card' => 'Credit card', + 'source_accounts' => 'Bronrekening(en)', + 'destination_accounts' => 'Doelrekening(en)', + 'user_id_is' => 'Je gebruikersnummer is :user', + 'field_supports_markdown' => 'Dit veld ondersteunt Markdown.', + 'need_more_help' => 'Als je meer hulp nodig hebt met Firefly III, open dan een ticket op Github.', 'reenable_intro_text' => 'Je kan de introductie-popupjes ook weer aan zetten.', 'intro_boxes_after_refresh' => 'De introductie-popupjes komen tevoorschijn als je de pagina opnieuw laadt.', 'show_all_no_filter' => 'Laat alle transacties zien, zonder te groeperen op datum.', @@ -220,21 +225,23 @@ return [ 'search_query' => 'Zoekopdracht', 'search_found_transactions' => 'Firefly III vond :count transactie(s) in :time seconden.', 'search_for_query' => 'Firefly III zoekt transacties met al deze woorden: :query', - 'search_modifier_amount_is' => 'Bedrag is precies :value', - 'search_modifier_amount' => 'Bedrag is precies :value', - 'search_modifier_amount_max' => 'Bedrag is hoogstens :value', - 'search_modifier_amount_min' => 'Bedrag is minstens :value', - 'search_modifier_amount_less' => 'Bedrag is minder dan :value', - 'search_modifier_amount_more' => 'Bedrag is meer dan :value', - 'search_modifier_source' => 'Bronrekening is :value', - 'search_modifier_destination' => 'Doelrekening is :value', - 'search_modifier_category' => 'Categorie is :value', - 'search_modifier_budget' => 'Budget is :value', - 'search_modifier_bill' => 'Contract is :value', - 'search_modifier_type' => 'Transactietype is :value', - 'search_modifier_date' => 'Transactiedatum is :value', - 'search_modifier_date_before' => 'Transactiedatum is vóór :value', - 'search_modifier_date_after' => 'Transactiedatum is na :value', + 'search_modifier_amount_is' => 'Bedrag is precies :value', + 'search_modifier_amount' => 'Bedrag is precies :value', + 'search_modifier_amount_max' => 'Bedrag is hoogstens :value', + 'search_modifier_amount_min' => 'Bedrag is minstens :value', + 'search_modifier_amount_less' => 'Bedrag is minder dan :value', + 'search_modifier_amount_more' => 'Bedrag is meer dan :value', + 'search_modifier_source' => 'Bronrekening is :value', + 'search_modifier_from' => 'Bronrekening is :value', + 'search_modifier_destination' => 'Doelrekening is :value', + 'search_modifier_to' => 'Doelrekening is :value', + 'search_modifier_category' => 'Categorie is :value', + 'search_modifier_budget' => 'Budget is :value', + 'search_modifier_bill' => 'Contract is :value', + 'search_modifier_type' => 'Transactietype is :value', + 'search_modifier_date' => 'Transactiedatum is :value', + 'search_modifier_date_before' => 'Transactiedatum is vóór :value', + 'search_modifier_date_after' => 'Transactiedatum is na :value', 'search_modifier_on' => 'Transactiedatum is :value', 'search_modifier_before' => 'Transactiedatum is vóór :value', 'search_modifier_after' => 'Transactiedatum is na :value', @@ -257,33 +264,6 @@ return [ 'half-year' => 'elk half jaar', 'yearly' => 'elk jaar', - // export data: - 'import_and_export' => 'Import en export', - 'export_data' => 'Exporteren', - 'export_and_backup_data' => 'Exporteren', - 'export_data_intro' => 'Gebruik de geëxporteerde data om naar een nieuwe financiële applicatie te gaan. Gebruik deze bestanden niet als backup. Er zit niet genoeg metadata bij om een nieuwe Firefly III mee op te zetten. Als je een backup wilt maken van je gegevens, backup dan de database zelf.', - 'export_format' => 'Exporteerformaat', - 'export_format_csv' => 'Komma-gescheiden bestand (CSV)', - 'export_format_mt940' => 'MT940 bestand', - 'include_old_uploads_help' => 'Firefly III gooit je oude geïmporteerde CSV bestanden niet weg. Je kan ze meenemen in je exportbestand.', - 'do_export' => 'Exporteren', - 'export_status_never_started' => 'Het exporteren is nog niet begonnen', - 'export_status_make_exporter' => 'Exporteerding maken...', - 'export_status_collecting_journals' => 'Transacties verzamelen...', - 'export_status_collected_journals' => 'Transacties verzameld!', - 'export_status_converting_to_export_format' => 'Transacties overzetten...', - 'export_status_converted_to_export_format' => 'Transacties overgezet!', - 'export_status_creating_journal_file' => 'Exportbestand maken...', - 'export_status_created_journal_file' => 'Exportbestand gemaakt!', - 'export_status_collecting_attachments' => 'Bijlagen verzamelen...', - 'export_status_collected_attachments' => 'Bijlagen verzameld!', - 'export_status_collecting_old_uploads' => 'Oude uploads verzamelen...', - 'export_status_collected_old_uploads' => 'Oude uploads verzameld!', - 'export_status_creating_zip_file' => 'Zipbestand maken...', - 'export_status_created_zip_file' => 'Zipbestand gemaakt!', - 'export_status_finished' => 'Klaar met exportbestand! Hoera!', - 'export_data_please_wait' => 'Een ogenblik geduld...', - // rules 'rules' => 'Regels', 'rule_name' => 'Regelnaam', @@ -502,30 +482,33 @@ return [ 'pref_custom_fiscal_year_help' => 'Voor in landen die een boekjaar gebruiken anders dan 1 januari tot 31 december', 'pref_fiscal_year_start_label' => 'Start van boekjaar', 'pref_two_factor_auth' => 'Authenticatie in twee stappen', - 'pref_two_factor_auth_help' => 'Als je authenticatie in twee stappen (ook wel twee-factor authenticatie genoemd) inschakelt voeg je een extra beveiligingslaag toe aan je account. Je logt in met iets dat je weet (je wachtwoord) en iets dat je hebt (een verificatiecode). Verificatiecodes worden gegeneerd door apps op je telefoon, zoals Authy en Google Authenticator.', - 'pref_enable_two_factor_auth' => 'Authenticatie in twee stappen inschakelen', - 'pref_two_factor_auth_disabled' => 'Je verificatiecode voor authenticatie in twee stappen is verwijderd, en uitgeschakeld', - 'pref_two_factor_auth_remove_it' => 'Vergeet niet om je Firefly III account uit je authenticatie appje te verwijderen!', - 'pref_two_factor_auth_code' => 'Bevestig de code', - 'pref_two_factor_auth_code_help' => 'Scan deze QR code met een app op je telefoon (zoals Authy of Google Authenticator). Vul de code die je terug krijgt hier in.', - 'pref_two_factor_auth_reset_code' => 'Reset de verificatiecode', - 'pref_two_factor_auth_disable_2fa' => '2FA uitzetten', - '2fa_use_secret_instead' => 'Als je de QR code niet kan scannen gebruik dan de geheime code: :secret.', - 'pref_save_settings' => 'Instellingen opslaan', - 'saved_preferences' => 'Voorkeuren opgeslagen!', - 'preferences_general' => 'Algemeen', - 'preferences_frontpage' => 'Homepagina', - 'preferences_security' => 'Veiligheid', - 'preferences_layout' => 'Uiterlijk', - 'pref_home_show_deposits' => 'Laat inkomsten zien op de homepagina', - 'pref_home_show_deposits_info' => 'De homepagina laat al crediteuren zien. Wil je ook je debiteuren zien?', - 'pref_home_do_show_deposits' => 'Ja, kom maar op', - 'successful_count' => 'waarvan :count met succes', - 'list_page_size_title' => 'Paginalengte', - 'list_page_size_help' => 'Per lijst met dingen (accounts, transacties, enz.) zie je hooguit zoveel items.', - 'list_page_size_label' => 'Paginalengte', - 'between_dates' => '(:start en :end)', - 'pref_optional_fields_transaction' => 'Optionele velden voor transacties', + 'pref_two_factor_auth_help' => 'Als je authenticatie in twee stappen (ook wel twee-factor authenticatie genoemd) inschakelt voeg je een extra beveiligingslaag toe aan je account. Je logt in met iets dat je weet (je wachtwoord) en iets dat je hebt (een verificatiecode). Verificatiecodes worden gegeneerd door apps op je telefoon, zoals Authy en Google Authenticator.', + 'pref_enable_two_factor_auth' => 'Authenticatie in twee stappen inschakelen', + 'pref_two_factor_auth_disabled' => 'Je verificatiecode voor authenticatie in twee stappen is verwijderd, en uitgeschakeld', + 'pref_two_factor_auth_remove_it' => 'Vergeet niet om je Firefly III account uit je authenticatie appje te verwijderen!', + 'pref_two_factor_auth_code' => 'Bevestig de code', + 'pref_two_factor_auth_code_help' => 'Scan deze QR code met een app op je telefoon (zoals Authy of Google Authenticator). Vul de code die je terug krijgt hier in.', + 'pref_two_factor_auth_reset_code' => 'Reset de verificatiecode', + 'pref_two_factor_auth_disable_2fa' => '2FA uitzetten', + '2fa_use_secret_instead' => 'Als je de QR code niet kan scannen gebruik dan de geheime code: :secret.', + '2fa_backup_codes' => 'Sla deze backupcodes op zodat je toegang houdt als je je apparaat verliest.', + '2fa_already_enabled' => 'Tweestapsverificatie is al ingeschakeld.', + 'wrong_mfa_code' => 'Deze code is niet geldig.', + 'pref_save_settings' => 'Instellingen opslaan', + 'saved_preferences' => 'Voorkeuren opgeslagen!', + 'preferences_general' => 'Algemeen', + 'preferences_frontpage' => 'Homepagina', + 'preferences_security' => 'Veiligheid', + 'preferences_layout' => 'Uiterlijk', + 'pref_home_show_deposits' => 'Laat inkomsten zien op de homepagina', + 'pref_home_show_deposits_info' => 'De homepagina laat al crediteuren zien. Wil je ook je debiteuren zien?', + 'pref_home_do_show_deposits' => 'Ja, kom maar op', + 'successful_count' => 'waarvan :count met succes', + 'list_page_size_title' => 'Paginalengte', + 'list_page_size_help' => 'Per lijst met dingen (accounts, transacties, enz.) zie je hooguit zoveel items.', + 'list_page_size_label' => 'Paginalengte', + 'between_dates' => '(:start en :end)', + 'pref_optional_fields_transaction' => 'Optionele velden voor transacties', 'pref_optional_fields_transaction_help' => 'Standaard staan niet alle velden aan (vanwege het overzicht). Hier kan je zulke extra velden alsnog aanzetten, als je denkt dat ze handig zijn. Als je een veld uitzet, maar deze heeft wel degelijk een waarde, dan is-ie altijd zichtbaar, wat je ook doet.', 'optional_tj_date_fields' => 'Datumvelden', 'optional_tj_business_fields' => 'Zakelijke velden', @@ -579,24 +562,25 @@ return [ 'login_with_new_email' => 'Je kan nu inloggen met je nieuwe emailadres.', 'login_with_old_email' => 'Je kan nu weer inloggen met je oude emailadres.', 'login_provider_local_only' => 'Je kan dit niet doen als je inlogt via ":login_provider".', - 'delete_local_info_only' => 'Omdat je inlogt via ":login_provider" verwijder je alleen lokale Firefly III informatie.', + 'delete_local_info_only' => 'Omdat je inlogt via ":login_provider" verwijder je alleen lokale Firefly III informatie.', // attachments - 'nr_of_attachments' => 'Eén bijlage|:count bijlagen', - 'attachments' => 'Bijlagen', - 'edit_attachment' => 'Wijzig bijlage ":name"', - 'update_attachment' => 'Update bijlage', - 'delete_attachment' => 'Verwijder bijlage ":name"', - 'attachment_deleted' => 'Bijlage ":name" verwijderd', - 'attachment_updated' => 'Attachment ":name" geüpdatet', - 'upload_max_file_size' => 'Maximale grootte: :size', - 'list_all_attachments' => 'Lijst van alle bijlagen', + 'nr_of_attachments' => 'Eén bijlage|:count bijlagen', + 'attachments' => 'Bijlagen', + 'edit_attachment' => 'Wijzig bijlage ":name"', + 'update_attachment' => 'Update bijlage', + 'delete_attachment' => 'Verwijder bijlage ":name"', + 'attachment_deleted' => 'Bijlage ":name" verwijderd', + 'liabilities_deleted' => 'Passiva ":name" verwijderd', + 'attachment_updated' => 'Attachment ":name" geüpdatet', + 'upload_max_file_size' => 'Maximale grootte: :size', + 'list_all_attachments' => 'Lijst van alle bijlagen', // transaction index - 'title_expenses' => 'Uitgaven', - 'title_withdrawal' => 'Uitgaven', - 'title_revenue' => 'Inkomsten', - 'title_deposit' => 'Inkomsten', + 'title_expenses' => 'Uitgaven', + 'title_withdrawal' => 'Uitgaven', + 'title_revenue' => 'Inkomsten', + 'title_deposit' => 'Inkomsten', 'title_transfer' => 'Overschrijvingen', 'title_transfers' => 'Overschrijvingen', @@ -634,6 +618,9 @@ return [ 'converted_to_Transfer' => 'De transactie is veranderd in een overschrijving', 'invalid_convert_selection' => 'De rekening die je hebt geselecteerd wordt al gebruikt in deze transactie, of bestaat niet.', 'source_or_dest_invalid' => 'Kan de juiste transactiegegevens niet vinden. Conversie is niet mogelijk.', + 'convert_to_withdrawal' => 'Converteren naar een uitgave', + 'convert_to_deposit' => 'Converteer naar inkomsten', + 'convert_to_transfer' => 'Converteren naar een overschrijving', // create new stuff: 'create_new_withdrawal' => 'Nieuwe uitgave', @@ -695,7 +682,9 @@ return [ 'available_amount_indication' => 'Gebruik deze bedragen om een indruk te krijgen van wat je totale budget zou kunnen zijn.', 'suggested' => 'Gesuggereerd', 'average_between' => 'Gemiddelde tussen :start en :end', - 'over_budget_warn' => ' Normaalgesproken budgetteer je :amount per dag. Nu sta je op :over_amount per dag.', + 'over_budget_warn' => ' Normaalgesproken budgetteer je :amount per dag. Nu sta je op :over_amount per dag. Zeker weten?', + 'transferred_in' => 'Overgeboekt (inkomend)', + 'transferred_away' => 'Overgeboekt (uitgaand)', // bills: 'match_between_amounts' => 'Contract past bij transacties tussen :low en :high.', @@ -779,6 +768,7 @@ return [ 'reconcile_options' => 'Afstemmingsopties', 'reconcile_range' => 'Afstemmingsbereik', 'start_reconcile' => 'Begin met afstemmen', + 'cash_account_type' => 'Cash', 'cash' => 'contant', 'account_type' => 'Rekeningtype', 'save_transactions_by_moving' => 'Bewaar deze transacties door ze aan een andere rekening te koppelen:', @@ -803,7 +793,9 @@ return [ 'reconcile_go_back' => 'Je kan een afstemmingstransactie later altijd wijzigen.', 'must_be_asset_account' => 'Je kan alleen betaalrekeningen afstemmen', 'reconciliation_stored' => 'Afstemming opgeslagen', - 'reconcilliation_transaction_title' => 'Afstemming (:from tot :to)', + 'reconciliation_error' => 'De correctie is niet opgeslagen, maar de transacties zijn wel aangemerkt als afgestemd: :error.', + 'reconciliation_transaction_title' => 'Afstemming (:from tot :to)', + 'sum_of_reconciliation' => 'Som van afstemming', 'reconcile_this_account' => 'Stem deze rekening af', 'confirm_reconciliation' => 'Bevestig afstemming', 'submitted_start_balance' => 'Ingevoerd startsaldo', @@ -815,7 +807,7 @@ return [ 'interest_calc_daily' => 'Per dag', 'interest_calc_monthly' => 'Per maand', 'interest_calc_yearly' => 'Per jaar', - 'initial_balance_account' => 'Startsaldorekening voor :name', + 'initial_balance_account' => 'Startsaldorekening voor :account', // categories: 'new_category' => 'Nieuwe categorie', @@ -847,6 +839,8 @@ return [ 'deleted_deposit' => 'Inkomsten ":description" verwijderd', 'deleted_transfer' => 'Overschrijving ":description" verwijderd', 'stored_journal' => 'Nieuw transactie ":description" opgeslagen', + 'stored_journal_no_descr' => 'Uw nieuwe transactie is succesvol aangemaakt', + 'updated_journal_no_descr' => 'De transactie is geüpdatet', 'select_transactions' => 'Selecteer transacties', 'rule_group_select_transactions' => '":title" op transacties toepassen', 'rule_select_transactions' => '":title" op transacties toepassen', @@ -855,26 +849,33 @@ return [ 'mass_delete_journals' => 'Verwijder een aantal transacties', 'mass_edit_journals' => 'Wijzig een aantal transacties', 'mass_bulk_journals' => 'Wijzig een aantal transacties in bulk', - 'mass_bulk_journals_explain' => 'Als je je transacties niet één voor één wilt wijzigen met de daarvoor bestemde functie, kan je ze ook allemaal in één keer wijzigen. Selecteer de gewenste categorie, budget of tag(s) hieronder en alle transacties in de tabel zullen deze waarde krijgen.', + 'mass_bulk_journals_explain' => 'Gebruik dit formulier om de onderstaande transacties in één keer aan te passen. Alle transacties die je hier ziet worden geüpdated.', + 'part_of_split' => 'Deze transactie is onderdeel van een gesplitste transactie. Als je niet alle splits hebt geselecteerd dan verander je wellicht maar één stuk van een gesplitste transactie.', 'bulk_set_new_values' => 'Gebruik de velden hieronder voor nieuwe waarden. Als je ze leeg laat, worden ze leeggemaakt voor alle gebruikers. Denk eraan dat alleen uitgaven een budget kunnen krijgen.', 'no_bulk_category' => 'Update categorie niet', 'no_bulk_budget' => 'Update budget niet', - 'no_bulk_tags' => 'Update tag(s) niet', - 'bulk_edit' => 'Wijzig in bulk', - 'cannot_edit_other_fields' => 'Je kan andere velden dan de velden die je hier ziet niet groepsgewijs wijzigen. Er is geen ruimte om ze te laten zien. Als je deze velden toch wilt wijzigen, volg dan de link naast de transactie en wijzig ze stuk voor stuk.', - 'no_budget' => '(geen budget)', - 'no_budget_squared' => '(geen budget)', - 'perm-delete-many' => 'Veel items in één keer verwijderen kan zeer storend zijn. Wees voorzichtig.', - 'mass_deleted_transactions_success' => 'Verwijder :amount transactie(s).', - 'mass_edited_transactions_success' => 'Wijzig :amount transactie(s)', - 'opt_group_' => '(geen rekeningtype)', + 'no_bulk_tags' => 'Update tag(s) niet', + 'mass_edit' => 'Wijzig in lijst', + 'bulk_edit' => 'Wijzig in bulk', + 'mass_delete' => 'Verwijder', + 'cannot_edit_other_fields' => 'Je kan andere velden dan de velden die je hier ziet niet groepsgewijs wijzigen. Er is geen ruimte om ze te laten zien. Als je deze velden toch wilt wijzigen, volg dan de link naast de transactie en wijzig ze stuk voor stuk.', + 'cannot_change_amount_reconciled' => 'Je kan het bedrag van een afgestemde transactie niet aanpassen.', + 'no_budget' => '(geen budget)', + 'no_budget_squared' => '(geen budget)', + 'perm-delete-many' => 'Als je veel transacties in één keer verwijderd kan dit je boekhouding aardig verstoren. Wees dus voorzichtig. Je hebt hier ook de mogelijkheid om één split van een gesplitste transactie weg te gooien, dus let op.', + 'mass_deleted_transactions_success' => 'Verwijder :amount transactie(s).', + 'mass_edited_transactions_success' => 'Wijzig :amount transactie(s)', + 'opt_group_' => '(geen rekeningtype)', 'opt_group_no_account_type' => '(geen rekeningtype)', 'opt_group_defaultAsset' => 'Standaard betaalrekeningen', 'opt_group_savingAsset' => 'Spaarrekeningen', 'opt_group_sharedAsset' => 'Gedeelde betaalrekeningen', 'opt_group_ccAsset' => 'Creditcards', 'opt_group_cashWalletAsset' => 'Cash portomonees', + 'opt_group_expense_account' => 'Crediteuren', + 'opt_group_revenue_account' => 'Debiteuren', 'opt_group_l_Loan' => 'Passiva: lening', + 'opt_group_cash_account' => 'Contant geldrekening', 'opt_group_l_Debt' => 'Passiva: schuld', 'opt_group_l_Mortgage' => 'Passiva: hypotheek', 'opt_group_l_Credit card' => 'Passiva: credit card', @@ -973,7 +974,7 @@ return [ 'errors' => 'Fouten', 'debt_start_date' => 'Begindatum van schuld', 'debt_start_amount' => 'Beginbedrag van schuld', - 'debt_start_amount_help' => 'Voer de originele schuld in als een positief getal. Je mag ook je huidige schuld invullen. Pas de datum hier op aan.', + 'debt_start_amount_help' => 'Vul hier altijd de lening als een positief bedrag in. Check de helppagina\'s voor meer info.', 'store_new_liabilities_account' => 'Nieuwe passiva opslaan', 'edit_liabilities_account' => 'Passiva ":name" wijzigen', @@ -1145,6 +1146,7 @@ return [ 'deleted_piggy_bank' => 'Spaarpotje ":name" verwijderd', 'added_amount_to_piggy' => ':amount aan ":name" toegevoegd', 'removed_amount_from_piggy' => ':amount uit ":name" gehaald', + 'piggy_events' => 'Gerelateerde spaarpotjes', // tags 'delete_tag' => 'Verwijder tag ":tag"', @@ -1154,47 +1156,57 @@ return [ 'updated_tag' => 'Tag ":tag" geüpdatet', 'created_tag' => 'Tag ":tag" opgeslagen!', - 'transaction_journal_information' => 'Transactieinformatie', - 'transaction_journal_meta' => 'Metainformatie', - 'total_amount' => 'Totaalbedrag', - 'number_of_decimals' => 'Aantal decimalen', + 'transaction_journal_information' => 'Transactieinformatie', + 'transaction_journal_meta' => 'Metainformatie', + 'transaction_journal_more' => 'Meer informatie', + 'att_part_of_journal' => 'Opgeslagen onder ":journal"', + 'total_amount' => 'Totaalbedrag', + 'number_of_decimals' => 'Aantal decimalen', // administration - 'administration' => 'Administratie', - 'user_administration' => 'Gebruikersadministratie', - 'list_all_users' => 'Alle gebruikers', - 'all_users' => 'Alle gebruikers', - 'instance_configuration' => 'Instellingen', - 'firefly_instance_configuration' => 'Instellingen voor Firefly III', - 'setting_single_user_mode' => 'Enkele gebruiker-modus', - 'setting_single_user_mode_explain' => 'Standaard accepteert Firefly III maar één (1) gebruiker: jijzelf. Dit is een veiligheidsmaatregel, zodat anderen niet zomaar jouw installatie kunnen gebruiken, tenzij je dit aanzet. Toekomstige registraties zijn nu geblokkeerd. Als je dit vinkje uitzet kunnen anderen jouw installatie ook gebruiken, gegeven dat ze er bij kunnen (je installatie hangt aan het internet).', - 'store_configuration' => 'Configuratie opslaan', - 'single_user_administration' => 'Gebruikersadministratie voor :email', - 'edit_user' => 'Wijzig gebruiker :email', - 'hidden_fields_preferences' => 'Niet alle velden zijn zichtbaar. Zet ze aan in je instellingen.', - 'user_data_information' => 'Gebruikersgegevens', - 'user_information' => 'Gebruikersinformatie', - 'total_size' => 'totale grootte', - 'budget_or_budgets' => 'budget(ten)', - 'budgets_with_limits' => 'budget(ten) met een ingesteld bedrag', - 'nr_of_rules_in_total_groups' => ':count_rules regel(s) in :count_groups regelgroep(en)', - 'tag_or_tags' => 'tag(s)', - 'configuration_updated' => 'De configuratie is bijgewerkt', - 'setting_is_demo_site' => 'Demo website', - 'setting_is_demo_site_explain' => 'Als je dit aanzet doet jouw installatie alsof het een demo-site is, en dat kan problemen opleveren.', - 'block_code_bounced' => 'Email kwam niet aan', - 'block_code_expired' => 'Demo-account verlopen', - 'no_block_code' => 'Geen reden of gebruiker niet geblokkeerd', - 'block_code_email_changed' => 'De gebruiker heeft zijn nieuwe emailadres nog niet bevestigd', - 'admin_update_email' => 'Integendeel tot de profielpagina krijgt de gebruiker hier geen notificatie van!', - 'update_user' => 'Gebruiker bijwerken', - 'updated_user' => 'Gebruikersgegevens zijn gewijzigd.', - 'delete_user' => 'Verwijder gebruiker :email', - 'user_deleted' => 'De gebruiker is verwijderd', - 'send_test_email' => 'Stuur testmail', - 'send_test_email_text' => 'Druk op deze knop om te zien of je installatie mail kan versturen. Je ziet hier geen foutmeldingen (als ze er zijn), deze vind je in de logboeken. Je kan deze knop zo vaak indrukken als je wilt. Er is geen optie die spam voorkomt. Het testbericht wordt verstuurd naar :email en zou vrij vlot aan moeten komen.', - 'send_message' => 'Verstuur bericht', - 'send_test_triggered' => 'Testmail verstuurd. Check je inbox en de logboeken.', + 'administration' => 'Administratie', + 'user_administration' => 'Gebruikersadministratie', + 'list_all_users' => 'Alle gebruikers', + 'all_users' => 'Alle gebruikers', + 'instance_configuration' => 'Instellingen', + 'firefly_instance_configuration' => 'Instellingen voor Firefly III', + 'setting_single_user_mode' => 'Enkele gebruiker-modus', + 'setting_single_user_mode_explain' => 'Standaard accepteert Firefly III maar één (1) gebruiker: jijzelf. Dit is een veiligheidsmaatregel, zodat anderen niet zomaar jouw installatie kunnen gebruiken, tenzij je dit aanzet. Toekomstige registraties zijn nu geblokkeerd. Als je dit vinkje uitzet kunnen anderen jouw installatie ook gebruiken, gegeven dat ze er bij kunnen (je installatie hangt aan het internet).', + 'store_configuration' => 'Configuratie opslaan', + 'single_user_administration' => 'Gebruikersadministratie voor :email', + 'edit_user' => 'Wijzig gebruiker :email', + 'hidden_fields_preferences' => 'U kunt meer transactieopties inschakelen in uw instellingen.', + 'user_data_information' => 'Gebruikersgegevens', + 'user_information' => 'Gebruikersinformatie', + 'total_size' => 'totale grootte', + 'budget_or_budgets' => 'budget(ten)', + 'budgets_with_limits' => 'budget(ten) met een ingesteld bedrag', + 'nr_of_rules_in_total_groups' => ':count_rules regel(s) in :count_groups regelgroep(en)', + 'tag_or_tags' => 'tag(s)', + 'configuration_updated' => 'De configuratie is bijgewerkt', + 'setting_is_demo_site' => 'Demo website', + 'setting_is_demo_site_explain' => 'Als je dit aanzet doet jouw installatie alsof het een demo-site is, en dat kan problemen opleveren.', + 'block_code_bounced' => 'Email kwam niet aan', + 'block_code_expired' => 'Demo-account verlopen', + 'no_block_code' => 'Geen reden of gebruiker niet geblokkeerd', + 'block_code_email_changed' => 'De gebruiker heeft zijn nieuwe emailadres nog niet bevestigd', + 'admin_update_email' => 'Integendeel tot de profielpagina krijgt de gebruiker hier geen notificatie van!', + 'update_user' => 'Gebruiker bijwerken', + 'updated_user' => 'Gebruikersgegevens zijn gewijzigd.', + 'delete_user' => 'Verwijder gebruiker :email', + 'user_deleted' => 'De gebruiker is verwijderd', + 'send_test_email' => 'Stuur testmail', + 'send_test_email_text' => 'Druk op deze knop om te zien of je installatie mail kan versturen. Je ziet hier geen foutmeldingen (als ze er zijn), deze vind je in de logboeken. Je kan deze knop zo vaak indrukken als je wilt. Er is geen optie die spam voorkomt. Het testbericht wordt verstuurd naar :email en zou vrij vlot aan moeten komen.', + 'send_message' => 'Verstuur bericht', + 'send_test_triggered' => 'Testmail verstuurd. Check je inbox en de logboeken.', + + 'split_transaction_title' => 'Beschrijving van de gesplitste transactie', + 'split_title_help' => 'Als u een gesplitste transactie maakt, moet er een algemene beschrijving zijn voor alle splitsingen van de transactie.', + 'transaction_information' => 'Transactieinformatie', + 'you_create_transfer' => 'Je maakt een overschrijving.', + 'you_create_withdrawal' => 'Je maakt een uitgave.', + 'you_create_deposit' => 'Je maakt een inkomsten.', + // links 'journal_link_configuration' => 'Instellingen voor transactiekoppelingen', @@ -1214,12 +1226,13 @@ return [ 'do_not_save_connection' => '(gooi ook koppelingen weg)', 'link_transaction' => 'Koppel transactie', 'link_to_other_transaction' => 'Koppel deze transactie aan een andere transactie', - 'select_transaction_to_link' => 'Selecteer een transactie om deze transactie aan te koppelen', + 'select_transaction_to_link' => 'Selecteer een transactie om te koppelen. De links worden door Firefly III verder niet gebruikt (behalve voor de show), maar dit gaat nog veranderen. Gebruik de box om een transactie te selecteren op ID of beschrijving. Als je je eigen linktypes wilt toevoegen moet je even in de administratie kijken.', 'this_transaction' => 'Deze transactie', 'transaction' => 'Transactie', 'comments' => 'Opmerkingen', - 'to_link_not_found' => 'Als de transactie die je wilt koppelen niet gevonden wordt, voer dan het ID in.', + 'link_notes' => 'Notities voor bij de link.', 'invalid_link_selection' => 'Deze transacties kunnen niet worden gekoppeld', + 'selected_transaction' => 'Geselecteerde transactie', 'journals_linked' => 'De transacties zijn gekoppeld.', 'journals_error_linked' => 'Deze transacties zijn al gekoppeld.', 'journals_link_to_self' => 'Je kan een transactie niet aan zichzelf koppelen', @@ -1258,12 +1271,11 @@ return [ 'split_this_withdrawal' => 'Splits deze uitgave', 'split_this_deposit' => 'Splits deze inkomsten', 'split_this_transfer' => 'Splits deze overschrijving', - 'cannot_edit_multiple_source' => 'Je kan transactie #:id met omschrijving ":description" niet splitsen, want deze bevat meerdere bronrekeningen.', - 'cannot_edit_multiple_dest' => 'Je kan transactie #:id met omschrijving ":description" niet wijzigen, want deze bevat meerdere doelrekeningen.', - 'cannot_edit_reconciled' => 'Je kunt transactie #:id met omschrijving ":description" niet bewerken omdat deze is gemarkeerd als afgestemd (verrekend).', 'cannot_edit_opening_balance' => 'Je kan het startsaldo van een rekening niet wijzigen via dit scherm.', 'no_edit_multiple_left' => 'Je hebt geen geldige transacties geselecteerd.', - 'cannot_convert_split_journal' => 'Kan geen gesplitste transactie omzetten', + 'breadcrumb_convert_group' => 'Verander transactie', + 'convert_invalid_source' => 'Broninformatie is ongeldig voor transactie #%d.', + 'convert_invalid_destination' => 'Doelrekeninginformatie is ongeldig voor transactie #%d.', // Import page (general strings only) 'import_index_title' => 'Transacties importeren in Firefly III', @@ -1389,4 +1401,15 @@ return [ 'will_jump_monday' => 'Wordt op de maandag erna gemaakt i. p. v. in het weekend.', 'except_weekends' => 'Behalve de weekenden', 'recurrence_deleted' => 'Periodieke transactie ":title" verwijderd', + + // new lines for summary controller. + 'box_balance_in_currency' => 'Saldo (:currency)', + 'box_spent_in_currency' => 'Uitgegeven (:currency)', + 'box_earned_in_currency' => 'Verdiend (:currency)', + 'box_bill_paid_in_currency' => 'Betaalde rekeningen (:currency)', + 'box_bill_unpaid_in_currency' => 'Onbetaalde rekeningen (:currency)', + 'box_left_to_spend_in_currency' => 'Uit te geven (:currency)', + 'box_net_worth_in_currency' => 'Kapitaal (:currency)', + 'box_spend_per_day' => 'Te besteden per dag: :amount', + ]; diff --git a/resources/lang/nl_NL/form.php b/resources/lang/nl_NL/form.php index 87180e6f06..58a451c13f 100644 --- a/resources/lang/nl_NL/form.php +++ b/resources/lang/nl_NL/form.php @@ -57,20 +57,21 @@ return [ 'asset_source_account' => 'Bronrekening', 'journal_description' => 'Omschrijving', 'note' => 'Notities', + 'store_new_transaction' => 'Nieuwe transactie opslaan', 'split_journal' => 'Splits deze transactie', 'split_journal_explanation' => 'Splits deze transactie in meerdere stukken', 'currency' => 'Valuta', 'account_id' => 'Betaalrekening', 'budget_id' => 'Budget', - 'openingBalance' => 'Startsaldo', + 'opening_balance' => 'Startsaldo', 'tagMode' => 'Tag modus', 'tag_position' => 'Taglocatie', - 'virtualBalance' => 'Virtuele saldo', + 'virtual_balance' => 'Virtueel saldo', 'targetamount' => 'Doelbedrag', - 'accountRole' => 'Rol van rekening', - 'openingBalanceDate' => 'Startsaldodatum', - 'ccType' => 'Betaalplan', - 'ccMonthlyPaymentDate' => 'Betaaldatum', + 'account_role' => 'Rol van rekening', + 'opening_balance_date' => 'Startsaldodatum', + 'cc_type' => 'Betaalplan', + 'cc_monthly_payment_date' => 'Betaaldatum', 'piggy_bank_id' => 'Spaarpotje', 'returnHere' => 'Keer terug', 'returnHereExplanation' => 'Terug naar deze pagina na het opslaan.', @@ -118,7 +119,7 @@ return [ 'symbol' => 'Symbool', 'code' => 'Code', 'iban' => 'IBAN', - 'accountNumber' => 'Rekeningnummer', + 'account_number' => 'Rekeningnummer', 'creditCardNumber' => 'Creditcardnummer', 'has_headers' => 'Kolomnamen op de eerste rij?', 'date_format' => 'Datumformaat', @@ -139,12 +140,8 @@ return [ 'stop_processing' => 'Stop met verwerken', 'start_date' => 'Start van bereik', 'end_date' => 'Einde van bereik', - 'export_start_range' => 'Start van exportbereik', - 'export_end_range' => 'Einde van exportbereik', - 'export_format' => 'Bestandsformaat', 'include_attachments' => 'Sla ook geüploade bijlagen op', 'include_old_uploads' => 'Sla ook geïmporteerde bestanden op', - 'accounts' => 'Exporteer boekingen van deze rekeningen', 'delete_account' => 'Verwijder rekening ":name"', 'delete_bill' => 'Verwijder contract ":name"', 'delete_budget' => 'Verwijder budget ":name"', @@ -256,4 +253,7 @@ return [ 'weekend' => 'Weekend', 'client_secret' => 'Client secret', + 'withdrawal_destination_id' => 'Doelrekening', + 'deposit_source_id' => 'Bronrekening', + ]; diff --git a/resources/lang/nl_NL/import.php b/resources/lang/nl_NL/import.php index 3619add400..bb8f8b3c0e 100644 --- a/resources/lang/nl_NL/import.php +++ b/resources/lang/nl_NL/import.php @@ -207,6 +207,10 @@ return [ 'specific_rabo_descr' => 'Lost mogelijke problemen op met Rabobank txt-bestanden', 'specific_pres_name' => 'President\'s Choice Financial CA', 'specific_pres_descr' => 'Lost mogelijke problemen op met PC bestanden', + 'specific_belfius_name' => 'Belfius BE', + 'specific_belfius_descr' => 'Lost mogelijke problemen op met Belfius-bestanden', + 'specific_ingbelgium_name' => 'ING BE', + 'specific_ingbelgium_descr' => 'Lost mogelijke problemen op met ING België bestanden', // job configuration for file provider (stage: roles) 'job_config_roles_title' => 'Importinstellingen (3/4) - rol van elke kolom definiëren', 'job_config_roles_text' => 'Elke kolom in je CSV-bestand bevat bepaalde gegevens. Gelieve aan te geven wat voor soort gegevens de import-routine kan verwachten. De optie "maak een link" betekent dat u elke vermelding in die kolom linkt aan een waarde uit je database. Een vaak gelinkte kolom is die met de IBAN-code van de tegenrekening. Die kan je dan linken aan de IBAN in jouw database.', @@ -291,14 +295,14 @@ return [ 'column_rabo-debit-credit' => 'Rabobankspecifiek bij/af indicator', 'column_ing-debit-credit' => 'ING-specifieke bij/af indicator', 'column_generic-debit-credit' => 'Generieke bank debet/credit indicator', - 'column_sepa-ct-id' => 'SEPA end-to-end identificatie', - 'column_sepa-ct-op' => 'SEPA identificatie tegenpartij', - 'column_sepa-db' => 'SEPA mandaatidentificatie', - 'column_sepa-cc' => 'SEPA vrijwaringscode', - 'column_sepa-ci' => 'SEPA crediteuridentificatie', - 'column_sepa-ep' => 'SEPA transactiedoeleinde', - 'column_sepa-country' => 'SEPA landcode', - 'column_sepa-batch-id' => 'SEPA batchnummer', + 'column_sepa_ct_id' => 'SEPA end-to-end identificatie', + 'column_sepa_ct_op' => 'SEPA tegenrekening identificatie', + 'column_sepa_db' => 'SEPA mandaatidentificatie', + 'column_sepa_cc' => 'SEPA vrijwaringscode', + 'column_sepa_ci' => 'SEPA crediteuridentificatie', + 'column_sepa_ep' => 'SEPA transactiedoeleinde', + 'column_sepa_country' => 'SEPA landcode', + 'column_sepa_batch_id' => 'SEPA batchnummer', 'column_tags-comma' => 'Tags (kommagescheiden)', 'column_tags-space' => 'Tags (spatiegescheiden)', 'column_account-number' => 'Betaalrekening (rekeningnummer)', @@ -306,4 +310,7 @@ return [ 'column_note' => 'Opmerking(en)', 'column_internal-reference' => 'Interne referentie', + // error message + 'duplicate_row' => 'Rij #:row (":description) kan niet worden geïmporteerd. Deze bestaat al.', + ]; diff --git a/resources/lang/nl_NL/intro.php b/resources/lang/nl_NL/intro.php index 808f481c81..5cc71a26e7 100644 --- a/resources/lang/nl_NL/intro.php +++ b/resources/lang/nl_NL/intro.php @@ -24,39 +24,61 @@ declare(strict_types=1); return [ // index - 'index_intro' => 'Welkom op de homepage van Firefly III. Neem even de tijd voor deze introductie zodat je Firefly III leert kennen.', - 'index_accounts-chart' => 'Deze grafiek toont het saldo van je betaalrekening(en). Welke rekeningen zichtbaar zijn kan je aangeven bij de instellingen.', - 'index_box_out_holder' => 'Dit vakje en de vakjes er naast geven een snel overzicht van je financiële situatie.', - 'index_help' => 'Als je ooit hulp nodig hebt, klik dan hier.', - 'index_outro' => 'De meeste pagina\'s in Firefly III beginnen met een kleine rondleiding zoals deze. Zoek me op als je vragen of commentaar hebt. Veel plezier!', - 'index_sidebar-toggle' => 'Nieuwe transacties, rekeningen en andere dingen maak je met het menu onder deze knop.', + 'index_intro' => 'Welkom op de homepage van Firefly III. Neem even de tijd voor deze introductie zodat je Firefly III leert kennen.', + 'index_accounts-chart' => 'Deze grafiek toont het saldo van je betaalrekening(en). Welke rekeningen zichtbaar zijn kan je aangeven bij de instellingen.', + 'index_box_out_holder' => 'Dit vakje en de vakjes er naast geven een snel overzicht van je financiële situatie.', + 'index_help' => 'Als je ooit hulp nodig hebt, klik dan hier.', + 'index_outro' => 'De meeste pagina\'s in Firefly III beginnen met een kleine rondleiding zoals deze. Zoek me op als je vragen of commentaar hebt. Veel plezier!', + 'index_sidebar-toggle' => 'Nieuwe transacties, rekeningen en andere dingen maak je met het menu onder deze knop.', + 'index_cash_account' => 'Dit zijn de rekeningen die tot nu toe zijn gemaakt. Je kan de cashgeldrekening gebruiken om cash geld te volgen, maar dat hoeft natuurlijk niet.', + + // transactions (withdrawal) + 'transactions_create_withdrawal_source' => 'Kies je lievelingsrekening of passiva uit deze lijst.', + 'transactions_create_withdrawal_destination' => 'Kies hier een debiteur. Laat deze leeg als het een cashbetaling is.', + 'transactions_create_withdrawal_foreign_currency' => 'Gebruik dit veld voor vreemde valuta.', + 'transactions_create_withdrawal_more_meta' => 'Alle andere meta-gegevens stop je in deze velden.', + 'transactions_create_withdrawal_split_add' => 'Als je een transactie wilt splitsen, gebruik dan deze knop om meer splits toe te voegen', + + // transactions (deposit) + 'transactions_create_deposit_source' => 'Selecteer of type de naam van degene die jou betaalt. Laat deze leeg als het om een contante storting gaat.', + 'transactions_create_deposit_destination' => 'Kies hier een betaalrekening of passiva.', + 'transactions_create_deposit_foreign_currency' => 'Gebruik dit veld voor vreemde valuta.', + 'transactions_create_deposit_more_meta' => 'Alle andere meta-gegevens stop je in deze velden.', + 'transactions_create_deposit_split_add' => 'Als je een transactie wilt splitsen, gebruik dan deze knop om meer splits toe te voegen', + + // transactions (transfer) + 'transactions_create_transfer_source' => 'Selecteer hier de bronbetaalrekening.', + 'transactions_create_transfer_destination' => 'Selecteer hier de doelrekening.', + 'transactions_create_transfer_foreign_currency' => 'Gebruik dit veld voor vreemde valuta.', + 'transactions_create_transfer_more_meta' => 'Alle andere meta-gegevens stop je in deze velden.', + 'transactions_create_transfer_split_add' => 'Als je een transactie wilt splitsen, gebruik dan deze knop om meer splits toe te voegen', // create account: - 'accounts_create_iban' => 'Geef je rekeningen een geldige IBAN. Dat scheelt met importeren van data.', - 'accounts_create_asset_opening_balance' => 'Betaalrekeningen kunnen een startsaldo hebben, waarmee het begin van deze rekening in Firefly III wordt aangegeven.', - 'accounts_create_asset_currency' => 'Firefly III ondersteunt meerdere valuta. Hier stel je de valuta in van je betaalrekening.', - 'accounts_create_asset_virtual' => 'Soms is het handig om je betaalrekening een virtueel saldo te geven: een extra bedrag dat altijd bij het daadwerkelijke saldo wordt opgeteld.', + 'accounts_create_iban' => 'Geef je rekeningen een geldige IBAN. Dat scheelt met importeren van data.', + 'accounts_create_asset_opening_balance' => 'Betaalrekeningen kunnen een startsaldo hebben, waarmee het begin van deze rekening in Firefly III wordt aangegeven.', + 'accounts_create_asset_currency' => 'Firefly III ondersteunt meerdere valuta. Hier stel je de valuta in van je betaalrekening.', + 'accounts_create_asset_virtual' => 'Soms is het handig om je betaalrekening een virtueel saldo te geven: een extra bedrag dat altijd bij het daadwerkelijke saldo wordt opgeteld.', // budgets index - 'budgets_index_intro' => 'Budgetten worden gebruikt om je financiën te beheren en vormen een van de kernfuncties van Firefly III.', - 'budgets_index_set_budget' => 'Stel je totale budget voor elke periode in, zodat Firefly III je kan vertellen of je alle beschikbare geld hebt gebudgetteerd.', - 'budgets_index_see_expenses_bar' => 'Het besteden van geld zal deze balk langzaam vullen.', - 'budgets_index_navigate_periods' => 'Navigeer door periodes heen om je budget vooraf te bepalen.', - 'budgets_index_new_budget' => 'Maak nieuwe budgetten naar wens.', - 'budgets_index_list_of_budgets' => 'Gebruik deze tabel om de bedragen voor elk budget vast te stellen en te zien hoe je er voor staat.', - 'budgets_index_outro' => 'Om meer te leren over budgetteren, klik dan op het help-icoontje rechtsboven.', + 'budgets_index_intro' => 'Budgetten worden gebruikt om je financiën te beheren en vormen een van de kernfuncties van Firefly III.', + 'budgets_index_set_budget' => 'Stel je totale budget voor elke periode in, zodat Firefly III je kan vertellen of je alle beschikbare geld hebt gebudgetteerd.', + 'budgets_index_see_expenses_bar' => 'Het besteden van geld zal deze balk langzaam vullen.', + 'budgets_index_navigate_periods' => 'Navigeer door periodes heen om je budget vooraf te bepalen.', + 'budgets_index_new_budget' => 'Maak nieuwe budgetten naar wens.', + 'budgets_index_list_of_budgets' => 'Gebruik deze tabel om de bedragen voor elk budget vast te stellen en te zien hoe je er voor staat.', + 'budgets_index_outro' => 'Om meer te leren over budgetteren, klik dan op het help-icoontje rechtsboven.', // reports (index) - 'reports_index_intro' => 'Gebruik deze rapporten om gedetailleerde inzicht in je financiën te krijgen.', - 'reports_index_inputReportType' => 'Kies een rapporttype. Bekijk de helppagina\'s om te zien wat elk rapport laat zien.', - 'reports_index_inputAccountsSelect' => 'Je kunt naar keuze betaalrekeningen meenemen (of niet).', - 'reports_index_inputDateRange' => 'Kies zelf een datumbereik: van een dag tot tien jaar.', - 'reports_index_extra-options-box' => 'Sommige rapporten bieden extra filters en opties. Kies een rapporttype en kijk of hier iets verandert.', + 'reports_index_intro' => 'Gebruik deze rapporten om gedetailleerde inzicht in je financiën te krijgen.', + 'reports_index_inputReportType' => 'Kies een rapporttype. Bekijk de helppagina\'s om te zien wat elk rapport laat zien.', + 'reports_index_inputAccountsSelect' => 'Je kunt naar keuze betaalrekeningen meenemen (of niet).', + 'reports_index_inputDateRange' => 'Kies zelf een datumbereik: van een dag tot tien jaar.', + 'reports_index_extra-options-box' => 'Sommige rapporten bieden extra filters en opties. Kies een rapporttype en kijk of hier iets verandert.', // reports (reports) - 'reports_report_default_intro' => 'Dit rapport geeft je een snel en uitgebreid overzicht van je financiën. Laat het me weten als je hier dingen mist!', - 'reports_report_audit_intro' => 'Dit rapport geeft je gedetailleerde inzichten in je betaalrekeningen.', - 'reports_report_audit_optionsBox' => 'Gebruik deze vinkjes om voor jou interessante kolommen te laten zien of te verbergen.', + 'reports_report_default_intro' => 'Dit rapport geeft je een snel en uitgebreid overzicht van je financiën. Laat het me weten als je hier dingen mist!', + 'reports_report_audit_intro' => 'Dit rapport geeft je gedetailleerde inzichten in je betaalrekeningen.', + 'reports_report_audit_optionsBox' => 'Gebruik deze vinkjes om voor jou interessante kolommen te laten zien of te verbergen.', 'reports_report_category_intro' => 'Dit rapport geeft je inzicht in één of meerdere categorieën.', 'reports_report_category_pieCharts' => 'Deze grafieken geven je inzicht in de uitgaven en inkomsten per categorie of per rekening.', diff --git a/resources/lang/nl_NL/list.php b/resources/lang/nl_NL/list.php index 5709ea9900..c2d8098f00 100644 --- a/resources/lang/nl_NL/list.php +++ b/resources/lang/nl_NL/list.php @@ -23,71 +23,71 @@ declare(strict_types=1); return [ - 'buttons' => 'Knoppen', - 'icon' => 'Icoon', - 'id' => 'ID', - 'create_date' => 'Aangemaakt op', - 'update_date' => 'Bijgewerkt op', - 'updated_at' => 'Bijgewerkt op', - 'balance_before' => 'Saldo voor', - 'balance_after' => 'Saldo na', - 'name' => 'Naam', - 'role' => 'Rol', - 'currentBalance' => 'Huidig saldo', - 'linked_to_rules' => 'Relevante regels', - 'active' => 'Actief?', - 'lastActivity' => 'Laatste activiteit', - 'balanceDiff' => 'Saldoverschil', - 'matchesOn' => 'Wordt herkend', - 'account_type' => 'Accounttype', - 'created_at' => 'Gemaakt op', - 'account' => 'Rekening', - 'matchingAmount' => 'Bedrag', - 'split_number' => 'Split #', - 'destination' => 'Doel', - 'source' => 'Bron', - 'next_expected_match' => 'Volgende verwachte match', - 'automatch' => 'Automatisch herkennen?', - 'repeat_freq' => 'Herhaling', - 'description' => 'Omschrijving', - 'amount' => 'Bedrag', - 'internal_reference' => 'Interne referentie', - 'date' => 'Datum', - 'interest_date' => 'Rentedatum', - 'book_date' => 'Boekdatum', - 'process_date' => 'Verwerkingsdatum', - 'due_date' => 'Vervaldatum', - 'payment_date' => 'Betalingsdatum', - 'invoice_date' => 'Factuurdatum', - 'interal_reference' => 'Interne verwijzing', - 'notes' => 'Notities', - 'from' => 'Van', - 'piggy_bank' => 'Spaarpotje', - 'to' => 'Naar', - 'budget' => 'Budget', - 'category' => 'Categorie', - 'bill' => 'Contract', - 'withdrawal' => 'Uitgave', - 'deposit' => 'Inkomsten', - 'transfer' => 'Overschrijving', - 'type' => 'Type', - 'completed' => 'Opgeslagen', - 'iban' => 'IBAN', - 'paid_current_period' => 'Betaald deze periode', - 'email' => 'E-mail', - 'registered_at' => 'Geregistreerd op', - 'is_blocked' => 'Is geblokkeerd', - 'is_admin' => 'Is beheerder', - 'has_two_factor' => 'Heeft 2FA', - 'blocked_code' => 'Reden voor blokkade', - 'source_account' => 'Bronrekening', + 'buttons' => 'Knoppen', + 'icon' => 'Icoon', + 'id' => 'ID', + 'create_date' => 'Aangemaakt op', + 'update_date' => 'Bijgewerkt op', + 'updated_at' => 'Bijgewerkt op', + 'balance_before' => 'Saldo voor', + 'balance_after' => 'Saldo na', + 'name' => 'Naam', + 'role' => 'Rol', + 'currentBalance' => 'Huidig saldo', + 'linked_to_rules' => 'Relevante regels', + 'active' => 'Actief?', + 'transaction_type' => 'Type', + 'lastActivity' => 'Laatste activiteit', + 'balanceDiff' => 'Saldoverschil', + 'matchesOn' => 'Wordt herkend', + 'account_type' => 'Accounttype', + 'created_at' => 'Gemaakt op', + 'account' => 'Rekening', + 'matchingAmount' => 'Bedrag', + 'split_number' => 'Split #', + 'destination' => 'Doel', + 'source' => 'Bron', + 'next_expected_match' => 'Volgende verwachte match', + 'automatch' => 'Automatisch herkennen?', + 'repeat_freq' => 'Herhaling', + 'description' => 'Omschrijving', + 'amount' => 'Bedrag', + 'internal_reference' => 'Interne referentie', + 'date' => 'Datum', + 'interest_date' => 'Rentedatum', + 'book_date' => 'Boekdatum', + 'process_date' => 'Verwerkingsdatum', + 'due_date' => 'Vervaldatum', + 'payment_date' => 'Betalingsdatum', + 'invoice_date' => 'Factuurdatum', + 'interal_reference' => 'Interne verwijzing', + 'notes' => 'Notities', + 'from' => 'Van', + 'piggy_bank' => 'Spaarpotje', + 'to' => 'Naar', + 'budget' => 'Budget', + 'category' => 'Categorie', + 'bill' => 'Contract', + 'withdrawal' => 'Uitgave', + 'deposit' => 'Inkomsten', + 'transfer' => 'Overschrijving', + 'type' => 'Type', + 'completed' => 'Opgeslagen', + 'iban' => 'IBAN', + 'paid_current_period' => 'Betaald deze periode', + 'email' => 'E-mail', + 'registered_at' => 'Geregistreerd op', + 'is_blocked' => 'Is geblokkeerd', + 'is_admin' => 'Is beheerder', + 'has_two_factor' => 'Heeft 2FA', + 'blocked_code' => 'Reden voor blokkade', + 'source_account' => 'Bronrekening', 'destination_account' => 'Doelrekening', 'accounts_count' => 'Aantal rekeningen', 'journals_count' => 'Aantal transacties', 'attachments_count' => 'Aantal bijlagen', 'bills_count' => 'Aantal contracten', 'categories_count' => 'Aantal categorieën', - 'export_jobs_count' => 'Aantal export-jobs', 'import_jobs_count' => 'Aantal import-jobs', 'budget_count' => 'Aantal budgetten', 'rule_and_groups_count' => 'Aantal regels en regelgroepen', @@ -106,14 +106,14 @@ return [ 'account_on_spectre' => 'Rekening (Spectre)', 'account_on_ynab' => 'Rekening (YNAB)', 'do_import' => 'Importeer van deze rekening', - 'sepa-ct-id' => 'SEPA end-to-end identificatie', - 'sepa-ct-op' => 'SEPA identificatie tegenpartij', - 'sepa-db' => 'SEPA mandaatidentificatie', - 'sepa-country' => 'SEPA landcode', - 'sepa-cc' => 'SEPA vrijwaringscode', - 'sepa-ep' => 'SEPA transactiedoeleinde', - 'sepa-ci' => 'SEPA crediteuridentificatie', - 'sepa-batch-id' => 'SEPA batchnummer', + 'sepa_ct_id' => 'SEPA end-to-end identificatie', + 'sepa_ct_op' => 'SEPA identificatie tegenpartij', + 'sepa_db' => 'SEPA mandaatidentificatie', + 'sepa_country' => 'SEPA-land', + 'sepa_cc' => 'SEPA vrijwaringscode', + 'sepa_ep' => 'SEPA transactiedoeleinde', + 'sepa_ci' => 'SEPA crediteuridentificatie', + 'sepa_batch_id' => 'SEPA-batchnummer', 'external_id' => 'Externe ID', 'account_at_bunq' => 'Bunq-account', 'file_name' => 'Bestandsnaam', diff --git a/resources/lang/nl_NL/validation.php b/resources/lang/nl_NL/validation.php index 4f5803ef6d..cadd6cdec5 100644 --- a/resources/lang/nl_NL/validation.php +++ b/resources/lang/nl_NL/validation.php @@ -33,15 +33,19 @@ return [ 'rule_trigger_value' => 'Deze waarde is niet geldig voor de geselecteerde trigger.', 'rule_action_value' => 'Deze waarde is niet geldig voor de geselecteerde actie.', 'file_already_attached' => 'Het geuploade bestand ":name" is al gelinkt aan deze transactie.', - 'file_attached' => 'Bestand met naam ":name" is met succes geuploaded.', + 'file_attached' => 'Bestand ":name" is succesvol geüpload.', 'must_exist' => 'Het ID in veld :attribute bestaat niet.', 'all_accounts_equal' => 'Alle rekeningen in dit veld moeten gelijk zijn.', + 'group_title_mandatory' => 'Een groepstitel is verplicht wanneer er meer dan één transactie is.', + 'transaction_types_equal' => 'Alle splits moeten van hetzelfde type zijn.', + 'invalid_transaction_type' => 'Ongeldig transactietype.', 'invalid_selection' => 'Ongeldige selectie.', 'belongs_user' => 'Deze waarde is ongeldig voor dit veld.', 'at_least_one_transaction' => 'Er is op zijn minst één transactie nodig.', 'at_least_one_repetition' => 'Er is op zijn minst één herhaling nodig.', 'require_repeat_until' => 'Je moet een aantal herhalingen opgeven, of een einddatum (repeat_until). Niet beide.', 'require_currency_info' => 'De inhoud van dit veld is ongeldig zonder valutagegevens.', + 'require_currency_amount' => 'De inhoud van dit veld is ongeldig zonder bedrag in vreemde valuta.', 'equal_description' => 'Transactiebeschrijving mag niet gelijk zijn aan globale beschrijving.', 'file_invalid_mime' => 'Bestand ":name" is van het type ":mime", en die kan je niet uploaden.', 'file_too_large' => 'Bestand ":name" is te groot.', @@ -135,8 +139,8 @@ return [ 'name' => 'naam', 'piggy_bank_id' => 'spaarpot ID', 'targetamount' => 'doelbedrag', - 'openingBalanceDate' => 'startsaldodatum', - 'openingBalance' => 'startsaldo', + 'opening_balance_date' => 'startsaldodatum', + 'opening_balance' => 'startsaldo', 'match' => 'overeenkomst', 'amount_min' => 'minimumbedrag', 'amount_max' => 'maximumbedrag', @@ -164,4 +168,28 @@ return [ 'rule-trigger.4' => 'regeltrigger #4', 'rule-trigger.5' => 'regeltrigger #5', ], + + // validation of accounts: + 'withdrawal_source_need_data' => 'Om door te gaan moet een geldige bronrekening ID en/of geldige bronrekeningnaam worden gevonden.', + 'withdrawal_source_bad_data' => 'Kan geen geldige bronrekening vinden bij het zoeken naar ID ":id" of naam ":name".', + 'withdrawal_dest_need_data' => 'Om door te gaan moet een geldig bronrekening ID en/of geldige bronrekeningnaam worden gevonden.', + 'withdrawal_dest_bad_data' => 'Kan geen geldige doelrekening vinden bij het zoeken naar ID ":id" of naam ":name".', + + 'deposit_source_need_data' => 'Om door te gaan moet een geldige bronrekening ID en/of geldige bronrekeningnaam worden gevonden.', + 'deposit_source_bad_data' => 'Kan geen geldige bronrekening vinden bij het zoeken naar ID ":id" of naam ":name".', + 'deposit_dest_need_data' => 'Om door te gaan moet een geldig doelrekening ID en/of geldige doelrekeningnaam worden gevonden.', + 'deposit_dest_bad_data' => 'Kan geen geldige doelrekening vinden bij het zoeken naar ID ":id" of naam ":name".', + + 'transfer_source_need_data' => 'Om door te gaan moet een geldig bronaccount ID en/of geldige bronaccountnaam worden gevonden.', + 'transfer_source_bad_data' => 'Kan geen geldige bronrekening vinden bij het zoeken naar ID ":id" of naam ":name".', + 'transfer_dest_need_data' => 'Om door te gaan moet een geldig doelrekening ID en/of geldige doelrekeningnaam worden gevonden.', + 'transfer_dest_bad_data' => 'Kan geen geldige doelrekening vinden bij het zoeken naar ID ":id" of naam ":name".', + 'need_id_in_edit' => 'Elke split moet een transaction_journal_id hebben (een geldig ID of 0).', + + 'ob_source_need_data' => 'Om door te gaan moet er een geldig bronrekening ID en/of geldige bronrekeningnaam worden gevonden.', + 'ob_dest_need_data' => 'Om door te gaan moet een geldig doelrekening ID en/of geldige doelrekeningnaam worden gevonden.', + 'ob_dest_bad_data' => 'Kan geen geldige doelrekening vinden bij het zoeken naar ID ":id" of naam ":name".', + + 'generic_invalid_source' => 'Je kan deze rekening niet gebruiken als bronrekening.', + 'generic_invalid_destination' => 'Je kan deze rekening niet gebruiken als doelrekening.', ]; diff --git a/resources/lang/pl_PL/breadcrumbs.php b/resources/lang/pl_PL/breadcrumbs.php index 29b5e21884..e025baede2 100644 --- a/resources/lang/pl_PL/breadcrumbs.php +++ b/resources/lang/pl_PL/breadcrumbs.php @@ -23,35 +23,37 @@ declare(strict_types=1); return [ - 'home' => 'Strona główna', - 'edit_currency' => 'Modyfikuj walutę ":name"', - 'delete_currency' => 'Usuń walutę ":name"', - 'newPiggyBank' => 'Utwórz nową skarbonkę', - 'edit_piggyBank' => 'Modyfikuj skarbonkę ":name"', - 'preferences' => 'Preferencje', - 'profile' => 'Profil', - 'changePassword' => 'Zmień swoje hasło', - 'change_email' => 'Zmień swój adres e-mail', - 'bills' => 'Rachunki', - 'newBill' => 'Nowy rachunek', - 'edit_bill' => 'Modyfikuj rachunek ":name"', - 'delete_bill' => 'Usuń rachunek ":name"', - 'reports' => 'Raporty', - 'search_result' => 'Wyniki wyszukiwania dla ":query"', - 'withdrawal_list' => 'Wydatki', - 'deposit_list' => 'Przychody, dochody oraz depozyty', - 'transfer_list' => 'Transfery', - 'transfers_list' => 'Transfery', - 'reconciliation_list' => 'Uzgodnienia', - 'create_withdrawal' => 'Utwórz nową wypłatę', - 'create_deposit' => 'Utwórz nową wpłatę', - 'create_transfer' => 'Utwórz nowy transfer', - 'edit_journal' => 'Modyfikuj transakcję ":description"', - 'edit_reconciliation' => 'Edytuj ":description"', - 'delete_journal' => 'Usuń transakcję ":description"', - 'tags' => 'Tagi', - 'createTag' => 'Utwórz nowy tag', - 'edit_tag' => 'Modyfikuj tag ":tag"', - 'delete_tag' => 'Usuń tag ":tag"', - 'delete_journal_link' => 'Usuń powiązanie między transakcjami', + 'home' => 'Strona główna', + 'edit_currency' => 'Modyfikuj walutę ":name"', + 'delete_currency' => 'Usuń walutę ":name"', + 'newPiggyBank' => 'Utwórz nową skarbonkę', + 'edit_piggyBank' => 'Modyfikuj skarbonkę ":name"', + 'preferences' => 'Preferencje', + 'profile' => 'Profil', + 'changePassword' => 'Zmień swoje hasło', + 'change_email' => 'Zmień swój adres e-mail', + 'bills' => 'Rachunki', + 'newBill' => 'Nowy rachunek', + 'edit_bill' => 'Modyfikuj rachunek ":name"', + 'delete_bill' => 'Usuń rachunek ":name"', + 'reports' => 'Raporty', + 'search_result' => 'Wyniki wyszukiwania dla ":query"', + 'withdrawal_list' => 'Wydatki', + 'Withdrawal_list' => 'Expenses', + 'deposit_list' => 'Przychody, dochody oraz depozyty', + 'transfer_list' => 'Transfery', + 'transfers_list' => 'Transfery', + 'reconciliation_list' => 'Uzgodnienia', + 'create_withdrawal' => 'Utwórz nową wypłatę', + 'create_deposit' => 'Utwórz nową wpłatę', + 'create_transfer' => 'Utwórz nowy transfer', + 'create_new_transaction' => 'Stwórz nową transakcję', + 'edit_journal' => 'Modyfikuj transakcję ":description"', + 'edit_reconciliation' => 'Edytuj ":description"', + 'delete_journal' => 'Usuń transakcję ":description"', + 'tags' => 'Tagi', + 'createTag' => 'Utwórz nowy tag', + 'edit_tag' => 'Modyfikuj tag ":tag"', + 'delete_tag' => 'Usuń tag ":tag"', + 'delete_journal_link' => 'Usuń powiązanie między transakcjami', ]; diff --git a/resources/lang/pl_PL/firefly.php b/resources/lang/pl_PL/firefly.php index 68e792b90c..a5cf603e06 100644 --- a/resources/lang/pl_PL/firefly.php +++ b/resources/lang/pl_PL/firefly.php @@ -33,7 +33,7 @@ return [ 'last_seven_days' => 'Ostatnie 7 dni', 'last_thirty_days' => 'Ostanie 30 dni', 'welcomeBack' => 'Co jest grane?', - 'welcome_back' => 'Co jest grane?', + 'welcome_back' => 'Co jest grane?', 'everything' => 'Wszystko', 'today' => 'dzisiaj', 'customRange' => 'Niestandardowy zakres', @@ -54,6 +54,7 @@ return [ 'create_new_stuff' => 'Utwórz nowe rzeczy', 'new_withdrawal' => 'Nowa wypłata', 'create_new_transaction' => 'Stwórz nową transakcję', + 'new_transaction' => 'Nowa transakcja', 'go_to_asset_accounts' => 'Zobacz swoje konta aktywów', 'go_to_budgets' => 'Przejdź do swoich budżetów', 'go_to_categories' => 'Przejdź do swoich kategorii', @@ -82,28 +83,32 @@ return [ 'help_for_this_page' => 'Pomoc dla tej strony', 'no_help_could_be_found' => 'Nie znaleziono tekstu pomocy.', 'no_help_title' => 'Przepraszamy, wystąpił błąd.', - 'two_factor_welcome' => 'Witaj, :user!', - 'two_factor_enter_code' => 'Aby kontynuować, wprowadź kod uwierzytelniania dwuskładnikowego. Twoja aplikacja może wygenerować go dla Ciebie.', - 'two_factor_code_here' => 'Wprowadź tutaj kod', - 'two_factor_title' => 'Weryfikacja dwuskładnikowa', - 'authenticate' => 'Uwierzytelnij', - 'two_factor_forgot_title' => 'Utracone uwierzytelnianie dwuskładnikowe', - 'two_factor_forgot' => 'Zapomniałem mojego uwierzytelnienia dwuskładnikowego.', - 'two_factor_lost_header' => 'Straciłeś uwierzytelnianie dwuskładnikowe?', - 'two_factor_lost_intro' => 'Niestety to nie jest coś, co można zresetować z poziomu strony www. Masz dwie możliwości.', - 'two_factor_lost_fix_self' => 'Jeśli jesteś właścicielem tej instalacji Firefly III, sprawdź pliki w katalogu storage/logs aby uzyskać instrukcje.', - 'two_factor_lost_fix_owner' => 'W przeciwnym razie, powiadom właściciela strony, :site_owner i poproś go o zresetowanie Twojego uwierzytelnienia dwuskładnikowego.', - 'warning_much_data' => 'Załadowanie danych z :days dni może trochę potrwać.', - 'registered' => 'Zarejestrowałeś się pomyślnie!', - 'Default asset account' => 'Domyślne konto aktywów', - 'no_budget_pointer' => 'Wygląda na to, że nie masz jeszcze żadnych budżetów. Powinieneś stworzyć kilka na stronie - budżety. Budżety pomogą Ci śledzić Twoje wydatki.', - 'Savings account' => 'Konto oszczędnościowe', - 'Credit card' => 'Karta kredytowa', - 'source_accounts' => 'Konto(a) źródłowe', - 'destination_accounts' => 'Konto(a) docelowe', - 'user_id_is' => 'Twój identyfikator użytkownika to :user', - 'field_supports_markdown' => 'To pole obsługuje Markdown.', - 'need_more_help' => 'Jeśli potrzebujesz dodatkowej pomocy w korzystaniu z Firefly III, proszę opisz go w zgłoszeniu na Githubie.', + 'two_factor_welcome' => 'Hello!', + 'two_factor_enter_code' => 'Aby kontynuować, wprowadź kod uwierzytelniania dwuskładnikowego. Twoja aplikacja może wygenerować go dla Ciebie.', + 'two_factor_code_here' => 'Wprowadź tutaj kod', + 'two_factor_title' => 'Weryfikacja dwuskładnikowa', + 'authenticate' => 'Uwierzytelnij', + 'two_factor_forgot_title' => 'Utracone uwierzytelnianie dwuskładnikowe', + 'two_factor_forgot' => 'Zapomniałem mojego uwierzytelnienia dwuskładnikowego.', + 'two_factor_lost_header' => 'Straciłeś uwierzytelnianie dwuskładnikowe?', + 'two_factor_lost_intro' => 'If you lost your backup codes as well, you have bad luck. This is not something you can fix from the web interface. You have two choices.', + 'two_factor_lost_fix_self' => 'If you run your own instance of Firefly III, check the logs in storage/logs for instructions, or run docker logs <container_id> to see the instructions (refresh this page).', + 'two_factor_lost_fix_owner' => 'W przeciwnym razie, powiadom właściciela strony, :site_owner i poproś go o zresetowanie Twojego uwierzytelnienia dwuskładnikowego.', + 'mfa_backup_code' => 'You have used a backup code to login to Firefly III. It can\'t be used again, so cross it from your list.', + 'pref_two_factor_new_backup_codes' => 'Get new backup codes', + 'pref_two_factor_backup_code_count' => 'You have :count valid backup code(s).', + '2fa_i_have_them' => 'I stored them!', + 'warning_much_data' => 'Załadowanie danych z :days dni może trochę potrwać.', + 'registered' => 'Zarejestrowałeś się pomyślnie!', + 'Default asset account' => 'Domyślne konto aktywów', + 'no_budget_pointer' => 'Wygląda na to, że nie masz jeszcze żadnych budżetów. Powinieneś stworzyć kilka na stronie - budżety. Budżety pomogą Ci śledzić Twoje wydatki.', + 'Savings account' => 'Konto oszczędnościowe', + 'Credit card' => 'Karta kredytowa', + 'source_accounts' => 'Konto(a) źródłowe', + 'destination_accounts' => 'Konto(a) docelowe', + 'user_id_is' => 'Twój identyfikator użytkownika to :user', + 'field_supports_markdown' => 'To pole obsługuje Markdown.', + 'need_more_help' => 'Jeśli potrzebujesz dodatkowej pomocy w korzystaniu z Firefly III, proszę opisz go w zgłoszeniu na Githubie.', 'reenable_intro_text' => 'Możesz także ponownie włączyć samouczek.', 'intro_boxes_after_refresh' => 'Samouczek pojawi się po odświeżeniu strony.', 'show_all_no_filter' => 'Pokaż wszystkie transakcje bez grupowania ich według daty.', @@ -220,21 +225,23 @@ return [ 'search_query' => 'Zapytanie', 'search_found_transactions' => 'Firefly III znalazło :count transakcji w :time sekundy.', 'search_for_query' => 'Firefly III szuka transakcji zawierających wszystkie słowa: :query', - 'search_modifier_amount_is' => 'Kwota to dokładnie :value', - 'search_modifier_amount' => 'Kwota to dokładnie :value', - 'search_modifier_amount_max' => 'Kwota to co najwyżej :value', - 'search_modifier_amount_min' => 'Kwota to co najmniej :value', - 'search_modifier_amount_less' => 'Kwota jest mniejsza niż :value', - 'search_modifier_amount_more' => 'Kwota jest większa niż :value', - 'search_modifier_source' => 'Konto źródłowe to :value', - 'search_modifier_destination' => 'Konto docelowe to :value', - 'search_modifier_category' => 'Kategoria to :value', - 'search_modifier_budget' => 'Budżet to :value', - 'search_modifier_bill' => 'Rachunek to :value', - 'search_modifier_type' => 'Transakcja jest typu :value', - 'search_modifier_date' => 'Data transakcji to :value', - 'search_modifier_date_before' => 'Data transakcji jest przed :value', - 'search_modifier_date_after' => 'Data transakcji jest po :value', + 'search_modifier_amount_is' => 'Kwota to dokładnie :value', + 'search_modifier_amount' => 'Kwota to dokładnie :value', + 'search_modifier_amount_max' => 'Kwota to co najwyżej :value', + 'search_modifier_amount_min' => 'Kwota to co najmniej :value', + 'search_modifier_amount_less' => 'Kwota jest mniejsza niż :value', + 'search_modifier_amount_more' => 'Kwota jest większa niż :value', + 'search_modifier_source' => 'Konto źródłowe to :value', + 'search_modifier_from' => 'Source account is :value', + 'search_modifier_destination' => 'Konto docelowe to :value', + 'search_modifier_to' => 'Destination account is :value', + 'search_modifier_category' => 'Kategoria to :value', + 'search_modifier_budget' => 'Budżet to :value', + 'search_modifier_bill' => 'Rachunek to :value', + 'search_modifier_type' => 'Transakcja jest typu :value', + 'search_modifier_date' => 'Data transakcji to :value', + 'search_modifier_date_before' => 'Data transakcji jest przed :value', + 'search_modifier_date_after' => 'Data transakcji jest po :value', 'search_modifier_on' => 'Data transakcji to :value', 'search_modifier_before' => 'Data transakcji jest przed :value', 'search_modifier_after' => 'Data transakcji jest po :value', @@ -257,33 +264,6 @@ return [ 'half-year' => 'co pół roku', 'yearly' => 'rocznie', - // export data: - 'import_and_export' => 'Import / eksport', - 'export_data' => 'Eksportuj dane', - 'export_and_backup_data' => 'Eksportuj dane', - 'export_data_intro' => 'Użyj wyeksportowanych danych, aby przejść do nowej aplikacji finansowej. Należy pamiętać, że te pliki są kopią zapasową. Nie zawierają one wystarczającej ilości metadanych, aby w pełni przywrócić nową instalację Firefly III. Jeśli chcesz wykonać kopię zapasową danych, wykonaj bezpośrednio kopię bazy danych.', - 'export_format' => 'Format eksportu', - 'export_format_csv' => 'Wartości oddzielone przecinkami (plik CSV)', - 'export_format_mt940' => 'Format kompatybilny z MT940', - 'include_old_uploads_help' => 'Firefly III nie wyrzuca oryginalnych plików CSV zaimportowanych w przeszłości. Możesz uwzględnić je w eksporcie.', - 'do_export' => 'Eksportuj', - 'export_status_never_started' => 'Eksport nie został jeszcze rozpoczęty', - 'export_status_make_exporter' => 'Tworzenie rzeczy dla eksportera...', - 'export_status_collecting_journals' => 'Zbieranie Twoich transakcji...', - 'export_status_collected_journals' => 'Zebrano twoje transakcje!', - 'export_status_converting_to_export_format' => 'Konwertowanie Twoich transakcji...', - 'export_status_converted_to_export_format' => 'Przekonwertowano twoje transakcje!', - 'export_status_creating_journal_file' => 'Tworzenie pliku eksportu...', - 'export_status_created_journal_file' => 'Utworzono plik eksportu!', - 'export_status_collecting_attachments' => 'Zbieram wszystkie Twoje załączniki...', - 'export_status_collected_attachments' => 'Zebrano wszystkie twoje załączniki!', - 'export_status_collecting_old_uploads' => 'Zbieram Twoje wszystkie przesłane pliki...', - 'export_status_collected_old_uploads' => 'Zebrano wszystkie poprzednie pliki!', - 'export_status_creating_zip_file' => 'Tworzenie pliku zip...', - 'export_status_created_zip_file' => 'Utworzono plik zip!', - 'export_status_finished' => 'Eksport został pomyślnie zakończony! Yay!', - 'export_data_please_wait' => 'Proszę czekać...', - // rules 'rules' => 'Reguły', 'rule_name' => 'Nazwa reguły', @@ -502,30 +482,33 @@ return [ 'pref_custom_fiscal_year_help' => 'W krajach, w których rok podatkowy nie zaczyna się 1 stycznia i nie kończy 31 grudnia, możesz włączyć tą opcję oraz podać początek / koniec roku podatkowego', 'pref_fiscal_year_start_label' => 'Początek roku podatkowego', 'pref_two_factor_auth' => 'Weryfikacja dwuetapowa', - 'pref_two_factor_auth_help' => 'Po włączeniu weryfikacji dwuetapowej (znanej również jako uwierzytelnianie dwuskładnikowe) dodajesz dodatkową warstwę zabezpieczeń do swojego konta. Logujesz się czymś, co znasz (hasło) i czymś co masz (kod weryfikacyjny). Kody weryfikacyjne generowane są przez aplikację w telefonie, na przykład Authy lub Google Authenticator.', - 'pref_enable_two_factor_auth' => 'Włącz weryfikację dwuetapową', - 'pref_two_factor_auth_disabled' => 'Kod weryfikacji dwuetapowej został usunięty i wyłączony', - 'pref_two_factor_auth_remove_it' => 'Nie zapomnij usunąć konta z aplikacji uwierzytelniajcej!', - 'pref_two_factor_auth_code' => 'Zweryfikuj kod', - 'pref_two_factor_auth_code_help' => 'Zeskanuj kod QR za pomocą aplikacji w telefonie, takiej jak Authy lub Google Authenticator i wprowadź wygenerowany kod.', - 'pref_two_factor_auth_reset_code' => 'Zresetuj kod weryfikacyjny', - 'pref_two_factor_auth_disable_2fa' => 'Wyłącz weryfikację dwuetapową', - '2fa_use_secret_instead' => 'Jeżeli nie możesz zeskanować kodu QR użyj sekretu: :secret.', - 'pref_save_settings' => 'Zapisz ustawienia', - 'saved_preferences' => 'Preferencje zostały zapisane!', - 'preferences_general' => 'Ogólne', - 'preferences_frontpage' => 'Ekran główny', - 'preferences_security' => 'Bezpieczeństwo', - 'preferences_layout' => 'Układ', - 'pref_home_show_deposits' => 'Pokaż przychody na stronie domowej', - 'pref_home_show_deposits_info' => 'Ekran główny pokazuje już konta wydatków. Czy chcesz wyświetlać również konta przychodów?', - 'pref_home_do_show_deposits' => 'Tak, pokaż je', - 'successful_count' => 'z których :count zakończone pomyślnie', - 'list_page_size_title' => 'Rozmiar strony', - 'list_page_size_help' => 'Każda lista rzeczy (konta, transakcje itp.) pokazuje co najwyżej tyle na stronę.', - 'list_page_size_label' => 'Rozmiar strony', - 'between_dates' => '(:start i :end)', - 'pref_optional_fields_transaction' => 'Opcjonalne pola dla transakcji', + 'pref_two_factor_auth_help' => 'Po włączeniu weryfikacji dwuetapowej (znanej również jako uwierzytelnianie dwuskładnikowe) dodajesz dodatkową warstwę zabezpieczeń do swojego konta. Logujesz się czymś, co znasz (hasło) i czymś co masz (kod weryfikacyjny). Kody weryfikacyjne generowane są przez aplikację w telefonie, na przykład Authy lub Google Authenticator.', + 'pref_enable_two_factor_auth' => 'Włącz weryfikację dwuetapową', + 'pref_two_factor_auth_disabled' => 'Kod weryfikacji dwuetapowej został usunięty i wyłączony', + 'pref_two_factor_auth_remove_it' => 'Nie zapomnij usunąć konta z aplikacji uwierzytelniajcej!', + 'pref_two_factor_auth_code' => 'Zweryfikuj kod', + 'pref_two_factor_auth_code_help' => 'Zeskanuj kod QR za pomocą aplikacji w telefonie, takiej jak Authy lub Google Authenticator i wprowadź wygenerowany kod.', + 'pref_two_factor_auth_reset_code' => 'Zresetuj kod weryfikacyjny', + 'pref_two_factor_auth_disable_2fa' => 'Wyłącz weryfikację dwuetapową', + '2fa_use_secret_instead' => 'If you cannot scan the QR code, feel free to use the secret instead: :secret.', + '2fa_backup_codes' => 'Store these backup codes for access in case you lose your device.', + '2fa_already_enabled' => '2-step verification is already enabled.', + 'wrong_mfa_code' => 'This MFA code is not valid.', + 'pref_save_settings' => 'Zapisz ustawienia', + 'saved_preferences' => 'Preferencje zostały zapisane!', + 'preferences_general' => 'Ogólne', + 'preferences_frontpage' => 'Ekran główny', + 'preferences_security' => 'Bezpieczeństwo', + 'preferences_layout' => 'Układ', + 'pref_home_show_deposits' => 'Pokaż przychody na stronie domowej', + 'pref_home_show_deposits_info' => 'Ekran główny pokazuje już konta wydatków. Czy chcesz wyświetlać również konta przychodów?', + 'pref_home_do_show_deposits' => 'Tak, pokaż je', + 'successful_count' => 'z których :count zakończone pomyślnie', + 'list_page_size_title' => 'Rozmiar strony', + 'list_page_size_help' => 'Każda lista rzeczy (konta, transakcje itp.) pokazuje co najwyżej tyle na stronę.', + 'list_page_size_label' => 'Rozmiar strony', + 'between_dates' => '(:start i :end)', + 'pref_optional_fields_transaction' => 'Opcjonalne pola dla transakcji', 'pref_optional_fields_transaction_help' => 'Domyślnie nie wszystkie pola są aktywne podczas tworzenia nowej transakcji (aby uniknąć bałaganu). Poniżej możesz włączyć te pola, jeśli uważasz, że mogą one być przydatne dla Ciebie. Oczywiście każde pole, które jest wyłączone, ale już wypełnione, będzie widoczne niezależnie od ustawienia.', 'optional_tj_date_fields' => 'Pola dat', 'optional_tj_business_fields' => 'Pola biznesowe', @@ -579,24 +562,25 @@ return [ 'login_with_new_email' => 'Teraz możesz logować się nowym adresem e-mail.', 'login_with_old_email' => 'Teraz ponownie możesz logować się starym adresem e-mail.', 'login_provider_local_only' => 'Ta akcja nie jest dostępna gdy uwierzytelniasz się poprzez ":login_provider".', - 'delete_local_info_only' => 'Ponieważ uwierzytelniasz się poprzez ":login_provider", usunięte zostaną tylko lokalne dane Firefly III.', + 'delete_local_info_only' => 'Ponieważ uwierzytelniasz się poprzez ":login_provider", usunięte zostaną tylko lokalne dane Firefly III.', // attachments - 'nr_of_attachments' => 'Jeden załącznik |:count załączników', - 'attachments' => 'Załączniki', - 'edit_attachment' => 'Modyfikuj załącznik ":name"', - 'update_attachment' => 'Aktualizuj załącznik', - 'delete_attachment' => 'Usuń załącznik ":name"', - 'attachment_deleted' => 'Usunięto załącznik ":name"', - 'attachment_updated' => 'Zmodyfikowano załącznik ":name"', - 'upload_max_file_size' => 'Maksymalny rozmiar pliku to: :size', - 'list_all_attachments' => 'Lista wszystkich załączników', + 'nr_of_attachments' => 'Jeden załącznik |:count załączników', + 'attachments' => 'Załączniki', + 'edit_attachment' => 'Modyfikuj załącznik ":name"', + 'update_attachment' => 'Aktualizuj załącznik', + 'delete_attachment' => 'Usuń załącznik ":name"', + 'attachment_deleted' => 'Usunięto załącznik ":name"', + 'liabilities_deleted' => 'Deleted liability ":name"', + 'attachment_updated' => 'Zmodyfikowano załącznik ":name"', + 'upload_max_file_size' => 'Maksymalny rozmiar pliku to: :size', + 'list_all_attachments' => 'Lista wszystkich załączników', // transaction index - 'title_expenses' => 'Wydatki', - 'title_withdrawal' => 'Wydatki', - 'title_revenue' => 'Przychód / dochód', - 'title_deposit' => 'Przychód / dochód', + 'title_expenses' => 'Wydatki', + 'title_withdrawal' => 'Wydatki', + 'title_revenue' => 'Przychód / dochód', + 'title_deposit' => 'Przychód / dochód', 'title_transfer' => 'Transfery', 'title_transfers' => 'Transfery', @@ -634,6 +618,9 @@ return [ 'converted_to_Transfer' => 'Transakcja została przekonwertowana do transferu', 'invalid_convert_selection' => 'Wybrane konto jest już używane w tej transakcji lub nie istnieje.', 'source_or_dest_invalid' => 'Nie można znaleźć poprawnych szczegółów transakcji. Konwersja nie jest możliwa.', + 'convert_to_withdrawal' => 'Konwertuj na wypłatę', + 'convert_to_deposit' => 'Konwertuj na wpłatę', + 'convert_to_transfer' => 'Konwertuj na transfer', // create new stuff: 'create_new_withdrawal' => 'Utwórz nową wypłatę', @@ -695,7 +682,9 @@ return [ 'available_amount_indication' => 'Skorzystaj z tych kwot, aby uzyskać wskazówkę ile może wynosić Twój całkowity budżet.', 'suggested' => 'Sugerowane', 'average_between' => 'Średnia pomiędzy :start a :end', - 'over_budget_warn' => ' Zwykle budżetujesz około :amount dziennie. Obecna wartość to :over_amount dziennie.', + 'over_budget_warn' => ' Zwykle budżetujesz około :amount dziennie. Obecna wartość to :over_amount dziennie. Na pewno?', + 'transferred_in' => 'Przesłane (do)', + 'transferred_away' => 'Przesłane (od)', // bills: 'match_between_amounts' => 'Rachunek pasuje do transakcji między :low a :high.', @@ -779,6 +768,7 @@ return [ 'reconcile_options' => 'Opcje uzgadniania', 'reconcile_range' => 'Zakres rozrachunku', 'start_reconcile' => 'Rozpocznij uzgadnianie', + 'cash_account_type' => 'Cash', 'cash' => 'gotówka', 'account_type' => 'Typ konta', 'save_transactions_by_moving' => 'Zapisz te transakcje, przenosząc je do innego konta:', @@ -803,7 +793,9 @@ return [ 'reconcile_go_back' => 'Możesz zawsze zmodyfikować lub usunąć korektę później.', 'must_be_asset_account' => 'Możesz uzgodnić tylko konta aktywów', 'reconciliation_stored' => 'Uzgodnienie zapisane', - 'reconcilliation_transaction_title' => 'Rozrachunek (od :from do :to)', + 'reconciliation_error' => 'Z powodu błędu transakcje zostały oznaczone jako uzgodnione, ale korekta nie została zapisana: :error.', + 'reconciliation_transaction_title' => 'Rozrachunek (od :from do :to)', + 'sum_of_reconciliation' => 'Suma rozrachunku', 'reconcile_this_account' => 'Uzgodnij to konto', 'confirm_reconciliation' => 'Potwierdź rozrachunek', 'submitted_start_balance' => 'Przesłane saldo początkowe', @@ -815,7 +807,7 @@ return [ 'interest_calc_daily' => 'Co dzień', 'interest_calc_monthly' => 'Co miesiąc', 'interest_calc_yearly' => 'Co rok', - 'initial_balance_account' => 'Początkowe saldo konta ":name"', + 'initial_balance_account' => 'Początkowe saldo konta :account', // categories: 'new_category' => 'Nowa kategoria', @@ -847,6 +839,8 @@ return [ 'deleted_deposit' => 'Pomyślnie usunięto depozyt ":description"', 'deleted_transfer' => 'Pomyślnie usunięto transfer ":description"', 'stored_journal' => 'Pomyślnie utworzono nową transakcję ":description"', + 'stored_journal_no_descr' => 'Pomyślnie utworzono nową transakcję', + 'updated_journal_no_descr' => 'Pomyślnie zaktualizowano Twoją transakcję', 'select_transactions' => 'Wybierz transakcje', 'rule_group_select_transactions' => 'Zastosuj ":title" do transakcji', 'rule_select_transactions' => 'Zastosuj ":title" do transakcji', @@ -855,26 +849,33 @@ return [ 'mass_delete_journals' => 'Usuń wiele transakcji', 'mass_edit_journals' => 'Modyfikuj wiele transakcji', 'mass_bulk_journals' => 'Hurtowa edycja wielu transakcji', - 'mass_bulk_journals_explain' => 'Jeśli nie chcesz modyfikować transakcji jedna po drugiej za pomocą funkcji masowej edycji, możesz zaktualizować je za jednym razem. Po prostu wybierz preferowaną kategorię, tag(i) lub budżet w poniższych polach i wszystkie transakcje z tabeli zostaną zaktualizowane.', + 'mass_bulk_journals_explain' => 'This form allows you to change properties of the transactions listed below in one sweeping update. All the transactions in the table will be updated when you change the parameters you see here.', + 'part_of_split' => 'This transaction is part of a split transaction. If you have not selected all the splits, you may end up with changing only half the transaction.', 'bulk_set_new_values' => 'Użyj pól poniżej, aby ustawić nowe wartości. Jeżeli zostawisz je puste, zostaną wyczyszczone dla wszystkich transakcji. Pamiętaj także, że budżet zostanie ustawiony tylko dla wypłat.', 'no_bulk_category' => 'Nie aktualizuj kategorii', 'no_bulk_budget' => 'Nie aktualizuj budżetu', - 'no_bulk_tags' => 'Nie aktualizuj tagów', - 'bulk_edit' => 'Hurtowa edycja', - 'cannot_edit_other_fields' => 'Nie możesz masowo modyfikować innych pól niż te tutaj, ponieważ nie ma miejsca, aby je pokazać. Proszę użyć ikony edycji i edytować je jedno po drugim, jeśli chcesz edytować te pola.', - 'no_budget' => '(brak budżetu)', - 'no_budget_squared' => '(brak budżetu)', - 'perm-delete-many' => 'Usuwanie wielu elementów jednocześnie może być bardzo destrukcyjne. Proszę zachować ostrożność.', - 'mass_deleted_transactions_success' => 'Usunięto :amount transakcję(i).', - 'mass_edited_transactions_success' => 'Zaktualizowano :amount transakcję(i)', - 'opt_group_' => '(brak typu konta)', + 'no_bulk_tags' => 'Nie aktualizuj tagów', + 'mass_edit' => 'Edit selected individually', + 'bulk_edit' => 'Edit selected in bulk', + 'mass_delete' => 'Delete selected', + 'cannot_edit_other_fields' => 'Nie możesz masowo modyfikować innych pól niż te tutaj, ponieważ nie ma miejsca, aby je pokazać. Proszę użyć ikony edycji i edytować je jedno po drugim, jeśli chcesz edytować te pola.', + 'cannot_change_amount_reconciled' => 'You can\'t change the amount of reconciled transactions.', + 'no_budget' => '(brak budżetu)', + 'no_budget_squared' => '(brak budżetu)', + 'perm-delete-many' => 'Deleting many items in one go can be very disruptive. Please be cautious. You can delete part of a split transaction from this page, so take care.', + 'mass_deleted_transactions_success' => 'Usunięto :amount transakcję(i).', + 'mass_edited_transactions_success' => 'Zaktualizowano :amount transakcję(i)', + 'opt_group_' => '(brak typu konta)', 'opt_group_no_account_type' => '(brak typu konta)', 'opt_group_defaultAsset' => 'Domyślne konta aktywów', 'opt_group_savingAsset' => 'Konta oszczędnościowe', 'opt_group_sharedAsset' => 'Współdzielone konta aktywów', 'opt_group_ccAsset' => 'Karty kredytowe', 'opt_group_cashWalletAsset' => 'Portfele gotówkowe', + 'opt_group_expense_account' => 'Expense accounts', + 'opt_group_revenue_account' => 'Revenue accounts', 'opt_group_l_Loan' => 'Zobowiązanie: Pożyczka', + 'opt_group_cash_account' => 'Cash account', 'opt_group_l_Debt' => 'Zobowiązanie: Dług', 'opt_group_l_Mortgage' => 'Zobowiązanie: Hipoteka', 'opt_group_l_Credit card' => 'Zobowiązanie: Karta kredytowa', @@ -973,7 +974,7 @@ return [ 'errors' => 'Błędy', 'debt_start_date' => 'Początkowa data długu', 'debt_start_amount' => 'Początkowa kwota długu', - 'debt_start_amount_help' => 'Wprowadź proszę oryginalną kwotę tego zobowiązania jako dodatnią liczbę. Możesz także podać aktualną kwotę. Pamiętaj, aby zmodyfikować odpowiednio poniższą datę.', + 'debt_start_amount_help' => 'Jeśli jesteś winien kwotę, najlepiej wpisać ujemną kwotę, ponieważ ma to wpływ na twoją wartość netto. To samo dotyczy jeśli ktoś jest Ci winien. Sprawdź strony pomocy, aby uzyskać więcej informacji.', 'store_new_liabilities_account' => 'Zapisz nowe zobowiązanie', 'edit_liabilities_account' => 'Modyfikuj zobowiązanie ":name"', @@ -1145,6 +1146,7 @@ return [ 'deleted_piggy_bank' => 'Usunięto skarbonkę ":name"', 'added_amount_to_piggy' => 'Dodano :amount do ":name"', 'removed_amount_from_piggy' => 'Usunięto :amount z ":name"', + 'piggy_events' => 'Powiązane skarbonki', // tags 'delete_tag' => 'Usuń tag ":tag"', @@ -1154,47 +1156,57 @@ return [ 'updated_tag' => 'Zaktualizowano tag ":tag"', 'created_tag' => 'Tag ":tag" został utworzony!', - 'transaction_journal_information' => 'Informacje o transakcji', - 'transaction_journal_meta' => 'Meta informacje', - 'total_amount' => 'Łączna kwota', - 'number_of_decimals' => 'Ilość miejsc dziesiętnych', + 'transaction_journal_information' => 'Informacje o transakcji', + 'transaction_journal_meta' => 'Meta informacje', + 'transaction_journal_more' => 'Więcej informacji', + 'att_part_of_journal' => 'Zapisano jako ":journal"', + 'total_amount' => 'Łączna kwota', + 'number_of_decimals' => 'Ilość miejsc dziesiętnych', // administration - 'administration' => 'Administracja', - 'user_administration' => 'Administracja użytkownikami', - 'list_all_users' => 'Wszyscy użytkownicy', - 'all_users' => 'Wszyscy użytkownicy', - 'instance_configuration' => 'Konfiguracja', - 'firefly_instance_configuration' => 'Opcje konfiguracji dla Firefly III', - 'setting_single_user_mode' => 'Tryb pojedynczego użytkownika', - 'setting_single_user_mode_explain' => 'Domyślnie, Firefly III pozwala na jednego (1) użytkownika: Ciebie. Jest to środek bezpieczeństwa uniemożliwiający innym używanie Twojej instalacji, chyba że im pozwolisz. Kolejne rejestracje są zablokowane. Jeżeli odznaczysz to pole, inne osoby będą mogły używać Twojej instalacji Firefly III (zakładając, że jest ona dostępna w Internecie).', - 'store_configuration' => 'Zapisz konfigurację', - 'single_user_administration' => 'Administracja użytkownika dla :email', - 'edit_user' => 'Modyfikuj użytkownika :email', - 'hidden_fields_preferences' => 'Nie wszystkie pola są teraz widoczne. Należy je włączyć w ustawieniach.', - 'user_data_information' => 'Dane użytkownika', - 'user_information' => 'Informacja o użytkowniku', - 'total_size' => 'łączny rozmiar', - 'budget_or_budgets' => 'budżet(y)', - 'budgets_with_limits' => 'budżet(y) z określoną kwotą', - 'nr_of_rules_in_total_groups' => ':count_rules reguła(y) w :count_groups grupa(ch) reguł', - 'tag_or_tags' => 'tag(ów)', - 'configuration_updated' => 'Konfiguracja została zaktualizowana', - 'setting_is_demo_site' => 'Strona demonstracyjna', - 'setting_is_demo_site_explain' => 'Jeśli zaznaczysz to pole, ta instalacja będzie zachowywać się jak witryna demonstracyjna, co może mieć dziwne efekty uboczne.', - 'block_code_bounced' => 'Odrzucony e-mail potwierdzający', - 'block_code_expired' => 'Demonstracyjne konto wygasło', - 'no_block_code' => 'Brak powody blokady lub użytkownik niezablokowany', - 'block_code_email_changed' => 'Użytkownik nie potwierdził jeszcze nowego adresu e-mail', - 'admin_update_email' => 'W przeciwieństwie do strony "Profil", użytkownik NIE zostanie powiadomiony o zmianie adresu e-mail!', - 'update_user' => 'Modyfikuj użytkownika', - 'updated_user' => 'Dane użytkownika zostały zmienione.', - 'delete_user' => 'Usuń użytkownika :email', - 'user_deleted' => 'Użytkownik został usunięty', - 'send_test_email' => 'Wyślij testową wiadomość e-mail', - 'send_test_email_text' => 'Aby sprawdzić, czy Twoja instalacja umożliwia wysyłanie wiadomości e-mail, naciśnij ten przycisk. Nie zobaczysz tutaj błędu (jeśli jest), pliki dziennika będą odzwierciedlać wszelkie błędy. Możesz nacisnąć ten przycisk tyle razy, ile chcesz. Nie ma kontroli spamu. Wiadomość zostanie wysłana do :email i powinna wkrótce nadejść.', - 'send_message' => 'Wyślij wiadomość', - 'send_test_triggered' => 'Test został uruchomiony. Sprawdź swoją skrzynkę odbiorczą i pliki dziennika.', + 'administration' => 'Administracja', + 'user_administration' => 'Administracja użytkownikami', + 'list_all_users' => 'Wszyscy użytkownicy', + 'all_users' => 'Wszyscy użytkownicy', + 'instance_configuration' => 'Konfiguracja', + 'firefly_instance_configuration' => 'Opcje konfiguracji dla Firefly III', + 'setting_single_user_mode' => 'Tryb pojedynczego użytkownika', + 'setting_single_user_mode_explain' => 'Domyślnie, Firefly III pozwala na jednego (1) użytkownika: Ciebie. Jest to środek bezpieczeństwa uniemożliwiający innym używanie Twojej instalacji, chyba że im pozwolisz. Kolejne rejestracje są zablokowane. Jeżeli odznaczysz to pole, inne osoby będą mogły używać Twojej instalacji Firefly III (zakładając, że jest ona dostępna w Internecie).', + 'store_configuration' => 'Zapisz konfigurację', + 'single_user_administration' => 'Administracja użytkownika dla :email', + 'edit_user' => 'Modyfikuj użytkownika :email', + 'hidden_fields_preferences' => 'Możesz włączyć więcej opcji transakcji w swoich ustawieniach.', + 'user_data_information' => 'Dane użytkownika', + 'user_information' => 'Informacja o użytkowniku', + 'total_size' => 'łączny rozmiar', + 'budget_or_budgets' => 'budżet(y)', + 'budgets_with_limits' => 'budżet(y) z określoną kwotą', + 'nr_of_rules_in_total_groups' => ':count_rules reguła(y) w :count_groups grupa(ch) reguł', + 'tag_or_tags' => 'tag(ów)', + 'configuration_updated' => 'Konfiguracja została zaktualizowana', + 'setting_is_demo_site' => 'Strona demonstracyjna', + 'setting_is_demo_site_explain' => 'Jeśli zaznaczysz to pole, ta instalacja będzie zachowywać się jak witryna demonstracyjna, co może mieć dziwne efekty uboczne.', + 'block_code_bounced' => 'Odrzucony e-mail potwierdzający', + 'block_code_expired' => 'Demonstracyjne konto wygasło', + 'no_block_code' => 'Brak powody blokady lub użytkownik niezablokowany', + 'block_code_email_changed' => 'Użytkownik nie potwierdził jeszcze nowego adresu e-mail', + 'admin_update_email' => 'W przeciwieństwie do strony "Profil", użytkownik NIE zostanie powiadomiony o zmianie adresu e-mail!', + 'update_user' => 'Modyfikuj użytkownika', + 'updated_user' => 'Dane użytkownika zostały zmienione.', + 'delete_user' => 'Usuń użytkownika :email', + 'user_deleted' => 'Użytkownik został usunięty', + 'send_test_email' => 'Wyślij testową wiadomość e-mail', + 'send_test_email_text' => 'Aby sprawdzić, czy Twoja instalacja umożliwia wysyłanie wiadomości e-mail, naciśnij ten przycisk. Nie zobaczysz tutaj błędu (jeśli jest), pliki dziennika będą odzwierciedlać wszelkie błędy. Możesz nacisnąć ten przycisk tyle razy, ile chcesz. Nie ma kontroli spamu. Wiadomość zostanie wysłana do :email i powinna wkrótce nadejść.', + 'send_message' => 'Wyślij wiadomość', + 'send_test_triggered' => 'Test został uruchomiony. Sprawdź swoją skrzynkę odbiorczą i pliki dziennika.', + + 'split_transaction_title' => 'Opis podzielonej transakcji', + 'split_title_help' => 'Podzielone transakcje muszą posiadać globalny opis.', + 'transaction_information' => 'Informacje o transakcji', + 'you_create_transfer' => 'Tworzysz przelew.', + 'you_create_withdrawal' => 'Tworzysz wydatek.', + 'you_create_deposit' => 'Tworzysz wpłatę.', + // links 'journal_link_configuration' => 'Konfiguracja łączy między transakcjami', @@ -1214,12 +1226,13 @@ return [ 'do_not_save_connection' => '(nie zapisuj powiązań)', 'link_transaction' => 'Powiąż transakcje', 'link_to_other_transaction' => 'Powiąż aktualną transakcję z inną transakcją', - 'select_transaction_to_link' => 'Wybierz transakcję, którą chcesz powiązać z tą transakcją', + 'select_transaction_to_link' => 'Select a transaction to link this transaction to. The links are currently unused in Firefly III (apart from being shown), but I plan to change this in the future. Use the search box to select a transaction either by title or by ID. If you want to add custom link types, check out the administration section.', 'this_transaction' => 'Ta transakcja', 'transaction' => 'Transakcja', 'comments' => 'Komentarze', - 'to_link_not_found' => 'Jeśli nie widzisz transakcji, którą chcesz powiązać, po prostu wpisz jej identyfikator.', + 'link_notes' => 'Any notes you wish to store with the link.', 'invalid_link_selection' => 'Nie można powiązać tych transakcji', + 'selected_transaction' => 'Selected transaction', 'journals_linked' => 'Transakcje zostały powiązane.', 'journals_error_linked' => 'Wybrane transakcje są już powiązane.', 'journals_link_to_self' => 'Nie możesz powiązać transakcji z samej ze sobą', @@ -1258,12 +1271,11 @@ return [ 'split_this_withdrawal' => 'Podziel tą wypłatę', 'split_this_deposit' => 'Podziel tą wpłatę', 'split_this_transfer' => 'Podziel ten transfer', - 'cannot_edit_multiple_source' => 'Nie możesz edytować podzielonej transakcji #:id z opisem ":description", ponieważ zawiera ona wiele kont źródłowych.', - 'cannot_edit_multiple_dest' => 'Nie możesz edytować podzielonej transakcji #:id z opisem ":description", ponieważ zawiera ona wiele kont docelowych.', - 'cannot_edit_reconciled' => 'Nie możesz edytować transakcji #:id z opisem ":description", ponieważ została ona zaznaczona jako uzgodniona.', 'cannot_edit_opening_balance' => 'Nie możesz edytować salda otwarcia konta.', 'no_edit_multiple_left' => 'Nie wybrałeś żadnych poprawnych transakcji do edycji.', - 'cannot_convert_split_journal' => 'Nie można przekonwertować podzielonej transakcji', + 'breadcrumb_convert_group' => 'Convert transaction', + 'convert_invalid_source' => 'Source information is invalid for transaction #%d.', + 'convert_invalid_destination' => 'Destination information is invalid for transaction #%d.', // Import page (general strings only) 'import_index_title' => 'Importuj transakcje do Firefly III', @@ -1389,4 +1401,15 @@ return [ 'will_jump_monday' => 'Zostanie utworzona w poniedziałki zamiast w weekendy.', 'except_weekends' => 'Pomiń weekendy', 'recurrence_deleted' => 'Cykliczna transakcja ":title" została usunięta', + + // new lines for summary controller. + 'box_balance_in_currency' => 'Saldo (:currency)', + 'box_spent_in_currency' => 'Wydano (:currency)', + 'box_earned_in_currency' => 'Zarobiono (:currency)', + 'box_bill_paid_in_currency' => 'Zapłacone rachunki (:currency)', + 'box_bill_unpaid_in_currency' => 'Niezapłacone rachunki (:currency)', + 'box_left_to_spend_in_currency' => 'Możliwe do wydania (:currency)', + 'box_net_worth_in_currency' => 'Wartość netto (:currency)', + 'box_spend_per_day' => 'Możliwe do wydania codziennie: :amount', + ]; diff --git a/resources/lang/pl_PL/form.php b/resources/lang/pl_PL/form.php index 33b9dac11e..164181a6c6 100644 --- a/resources/lang/pl_PL/form.php +++ b/resources/lang/pl_PL/form.php @@ -57,20 +57,21 @@ return [ 'asset_source_account' => 'Konto źródłowe', 'journal_description' => 'Opis', 'note' => 'Notatki', + 'store_new_transaction' => 'Zapisz nową transakcję', 'split_journal' => 'Podziel tę transakcję', 'split_journal_explanation' => 'Podziel transakcję na wiele części', 'currency' => 'Waluta', 'account_id' => 'Konto aktywów', 'budget_id' => 'Budżet', - 'openingBalance' => 'Bilans otwarcia', + 'opening_balance' => 'Saldo początkowe', 'tagMode' => 'Tryb tagów', 'tag_position' => 'Lokalizacja taga', - 'virtualBalance' => 'Wirtualne saldo', + 'virtual_balance' => 'Wirtualne saldo', 'targetamount' => 'Kwota docelowa', - 'accountRole' => 'Rola konta', - 'openingBalanceDate' => 'Data salda otwarcia', - 'ccType' => 'Plan płatności kartą kredytową', - 'ccMonthlyPaymentDate' => 'Miesięczny termin spłaty karty kredytowej', + 'account_role' => 'Rola konta', + 'opening_balance_date' => 'Data salda otwarcia', + 'cc_type' => 'Plan płatności kartą kredytową', + 'cc_monthly_payment_date' => 'Miesięczny termin spłaty karty kredytowej', 'piggy_bank_id' => 'Skarbonka', 'returnHere' => 'Wróć tutaj', 'returnHereExplanation' => 'Po zapisaniu, wrócić tutaj.', @@ -118,7 +119,7 @@ return [ 'symbol' => 'Symbol', 'code' => 'Kod', 'iban' => 'IBAN', - 'accountNumber' => 'Numer konta', + 'account_number' => 'Numer konta', 'creditCardNumber' => 'Numer karty kredytowej', 'has_headers' => 'Nagłówki', 'date_format' => 'Format daty', @@ -139,12 +140,8 @@ return [ 'stop_processing' => 'Zatrzymaj przetwarzanie', 'start_date' => 'Początek zakresu', 'end_date' => 'Koniec zakresu', - 'export_start_range' => 'Początek okresu eksportu', - 'export_end_range' => 'Koniec okresu eksportu', - 'export_format' => 'Format pliku', 'include_attachments' => 'Uwzględnij dołączone załączniki', 'include_old_uploads' => 'Dołącz zaimportowane dane', - 'accounts' => 'Eksportuj transakcje z tych kont', 'delete_account' => 'Usuń konto ":name"', 'delete_bill' => 'Usuń rachunek ":name"', 'delete_budget' => 'Usuń budżet ":name"', @@ -256,4 +253,7 @@ return [ 'weekend' => 'Weekend', 'client_secret' => 'Sekret klienta', + 'withdrawal_destination_id' => 'Destination account', + 'deposit_source_id' => 'Source account', + ]; diff --git a/resources/lang/pl_PL/import.php b/resources/lang/pl_PL/import.php index 2b2d48894d..467ec69af1 100644 --- a/resources/lang/pl_PL/import.php +++ b/resources/lang/pl_PL/import.php @@ -193,7 +193,7 @@ return [ 'job_config_fints_url_help' => 'E.g. https://banking-dkb.s-fints-pt-dkb.de/fints30', 'job_config_fints_username_help' => 'Dla wielu banków jest to numer twojego konta.', - 'job_config_fints_port_help' => 'The default port is 443.', + 'job_config_fints_port_help' => 'Domyślny port to 443.', 'job_config_fints_account_help' => 'Wybierz konto bankowe, dla którego chcesz importować transakcje.', 'job_config_local_account_help' => 'Wybierz konto Firefly III odpowiadające wybranemu powyżej kontu bankowemu.', // specifics: @@ -207,6 +207,10 @@ return [ 'specific_rabo_descr' => 'Fixes potential problems with Rabobank files', 'specific_pres_name' => 'President\'s Choice Financial CA', 'specific_pres_descr' => 'Fixes potential problems with PC files', + 'specific_belfius_name' => 'Belfius BE', + 'specific_belfius_descr' => 'Fixes potential problems with Belfius files', + 'specific_ingbelgium_name' => 'ING BE', + 'specific_ingbelgium_descr' => 'Fixes potential problems with ING Belgium files', // job configuration for file provider (stage: roles) 'job_config_roles_title' => 'Import setup (3/4) - Define each column\'s role', 'job_config_roles_text' => 'Each column in your CSV file contains certain data. Please indicate what kind of data the importer should expect. The option to "map" data means that you will link each entry found in the column to a value in your database. An often mapped column is the column that contains the IBAN of the opposing account. That can be easily matched to IBAN\'s present in your database already.', @@ -291,14 +295,14 @@ return [ 'column_rabo-debit-credit' => 'Specyficzny wskaźnik obciążenia/kredytu Rabobank', 'column_ing-debit-credit' => 'Specyficzny wskaźnik obciążenia/kredytu ING', 'column_generic-debit-credit' => 'Ogólny wskaźnik obciążenia/kredytu bankowego', - 'column_sepa-ct-id' => 'SEPA end-to-end Identifier', - 'column_sepa-ct-op' => 'SEPA Opposing Account Identifier', - 'column_sepa-db' => 'SEPA Mandate Identifier', - 'column_sepa-cc' => 'SEPA Clearing Code', - 'column_sepa-ci' => 'SEPA Creditor Identifier', - 'column_sepa-ep' => 'SEPA External Purpose', - 'column_sepa-country' => 'SEPA Country Code', - 'column_sepa-batch-id' => 'ID paczki SEPA', + 'column_sepa_ct_id' => 'SEPA end-to-end Identifier', + 'column_sepa_ct_op' => 'SEPA Opposing Account Identifier', + 'column_sepa_db' => 'SEPA Mandate Identifier', + 'column_sepa_cc' => 'SEPA Clearing Code', + 'column_sepa_ci' => 'SEPA Creditor Identifier', + 'column_sepa_ep' => 'SEPA External Purpose', + 'column_sepa_country' => 'SEPA Country Code', + 'column_sepa_batch_id' => 'SEPA Batch ID', 'column_tags-comma' => 'Tagi (oddzielone przecinkami)', 'column_tags-space' => 'Tagi (oddzielone spacjami)', 'column_account-number' => 'Konto aktywów (numer konta)', @@ -306,4 +310,7 @@ return [ 'column_note' => 'Notatki', 'column_internal-reference' => 'Internal reference', + // error message + 'duplicate_row' => 'Row #:row (":description") could not be imported. It already exists.', + ]; diff --git a/resources/lang/pl_PL/intro.php b/resources/lang/pl_PL/intro.php index 2a2d5d471f..c525e96fa7 100644 --- a/resources/lang/pl_PL/intro.php +++ b/resources/lang/pl_PL/intro.php @@ -24,39 +24,61 @@ declare(strict_types=1); return [ // index - 'index_intro' => 'Witamy na stronie domowej Firefly III. Proszę poświęć trochę czasu, aby przejść przez to wprowadzenie, aby poznać sposób działania Firefly III.', - 'index_accounts-chart' => 'Ten wykres przedstawia bieżące saldo kont aktywów. Możesz wybrać konta widoczne tutaj w Twoich preferencjach.', - 'index_box_out_holder' => 'To małe pole i pola obok niego umożliwiają szybki przegląd Twojej sytuacji finansowej.', - 'index_help' => 'Jeśli potrzebujesz pomocy na stronie lub formularzu, naciśnij ten przycisk.', - 'index_outro' => 'Większość stron z Firefly III zacznie się od małego wprowadzenia jak to. Skontaktuj się ze mną, jeśli masz pytania lub komentarze. Miłego korzystania!', - 'index_sidebar-toggle' => 'Aby utworzyć nowe transakcje, konta lub inne rzeczy, użyj menu pod tą ikoną.', + 'index_intro' => 'Witamy na stronie domowej Firefly III. Proszę poświęć trochę czasu, aby przejść przez to wprowadzenie, aby poznać sposób działania Firefly III.', + 'index_accounts-chart' => 'Ten wykres przedstawia bieżące saldo kont aktywów. Możesz wybrać konta widoczne tutaj w Twoich preferencjach.', + 'index_box_out_holder' => 'To małe pole i pola obok niego umożliwiają szybki przegląd Twojej sytuacji finansowej.', + 'index_help' => 'Jeśli potrzebujesz pomocy na stronie lub formularzu, naciśnij ten przycisk.', + 'index_outro' => 'Większość stron z Firefly III zacznie się od małego wprowadzenia jak to. Skontaktuj się ze mną, jeśli masz pytania lub komentarze. Miłego korzystania!', + 'index_sidebar-toggle' => 'Aby utworzyć nowe transakcje, konta lub inne rzeczy, użyj menu pod tą ikoną.', + 'index_cash_account' => 'These are the accounts created so far. You can use the cash account to track cash expenses but it\'s not mandatory of course.', + + // transactions (withdrawal) + 'transactions_create_withdrawal_source' => 'Select your favorite asset account or liability from this dropdown.', + 'transactions_create_withdrawal_destination' => 'Select an expense account here. Leave it empty if you want to make a cash expense.', + 'transactions_create_withdrawal_foreign_currency' => 'Use this field to set a foreign currency and amount.', + 'transactions_create_withdrawal_more_meta' => 'Plenty of other meta data you set in these fields.', + 'transactions_create_withdrawal_split_add' => 'If you want to split a transaction, add more splits with this button', + + // transactions (deposit) + 'transactions_create_deposit_source' => 'Select or type the payee in this auto-completing dropdown/textbox. Leave it empty if you want to make a cash deposit.', + 'transactions_create_deposit_destination' => 'Select an asset or liability account here.', + 'transactions_create_deposit_foreign_currency' => 'Use this field to set a foreign currency and amount.', + 'transactions_create_deposit_more_meta' => 'Plenty of other meta data you set in these fields.', + 'transactions_create_deposit_split_add' => 'If you want to split a transaction, add more splits with this button', + + // transactions (transfer) + 'transactions_create_transfer_source' => 'Select the source asset account here.', + 'transactions_create_transfer_destination' => 'Select the destination asset account here.', + 'transactions_create_transfer_foreign_currency' => 'Use this field to set a foreign currency and amount.', + 'transactions_create_transfer_more_meta' => 'Plenty of other meta data you set in these fields.', + 'transactions_create_transfer_split_add' => 'If you want to split a transaction, add more splits with this button', // create account: - 'accounts_create_iban' => 'Nadaj kontom ważny numer IBAN. Może to ułatwić import danych w przyszłości.', - 'accounts_create_asset_opening_balance' => 'Konta aktywów mogą mieć "bilans otwarcia", wskazujący początek historii tego konta w Firefly III.', - 'accounts_create_asset_currency' => 'Firefly III obsługuje wiele walut. Konta aktywów mają jedną główną walutę, który należy ustawić tutaj.', - 'accounts_create_asset_virtual' => 'Czasami warto dodać do konta wirtualne saldo: dodatkowa kwota zawsze dodawana lub odejmowana od rzeczywistego salda.', + 'accounts_create_iban' => 'Nadaj kontom ważny numer IBAN. Może to ułatwić import danych w przyszłości.', + 'accounts_create_asset_opening_balance' => 'Konta aktywów mogą mieć "bilans otwarcia", wskazujący początek historii tego konta w Firefly III.', + 'accounts_create_asset_currency' => 'Firefly III obsługuje wiele walut. Konta aktywów mają jedną główną walutę, który należy ustawić tutaj.', + 'accounts_create_asset_virtual' => 'Czasami warto dodać do konta wirtualne saldo: dodatkowa kwota zawsze dodawana lub odejmowana od rzeczywistego salda.', // budgets index - 'budgets_index_intro' => 'Budżety są wykorzystywane do zarządzania finansami i stanowią jedną z podstawowych funkcji Firefly III.', - 'budgets_index_set_budget' => 'Ustaw całkowity budżet na każdy okres, aby Firefly III mógł Ci powiedzieć, czy wydałeś wszystkie dostępne pieniądze.', - 'budgets_index_see_expenses_bar' => 'Wydawanie pieniędzy powoli wypełnia ten pasek.', - 'budgets_index_navigate_periods' => 'Przejrzyj okresy, aby łatwiej ustawić przyszłe budżety.', - 'budgets_index_new_budget' => 'Utwórz nowe budżety zgodnie z Twoimi potrzebami.', - 'budgets_index_list_of_budgets' => 'Skorzystaj z tej tabeli, aby ustawić kwoty dla każdego budżetu i sprawdź jak ci idzie.', - 'budgets_index_outro' => 'Aby dowiedzieć się więcej o budżetowaniu, użyj ikonki pomocy w prawym górnym rogu.', + 'budgets_index_intro' => 'Budżety są wykorzystywane do zarządzania finansami i stanowią jedną z podstawowych funkcji Firefly III.', + 'budgets_index_set_budget' => 'Ustaw całkowity budżet na każdy okres, aby Firefly III mógł Ci powiedzieć, czy wydałeś wszystkie dostępne pieniądze.', + 'budgets_index_see_expenses_bar' => 'Wydawanie pieniędzy powoli wypełnia ten pasek.', + 'budgets_index_navigate_periods' => 'Przejrzyj okresy, aby łatwiej ustawić przyszłe budżety.', + 'budgets_index_new_budget' => 'Utwórz nowe budżety zgodnie z Twoimi potrzebami.', + 'budgets_index_list_of_budgets' => 'Skorzystaj z tej tabeli, aby ustawić kwoty dla każdego budżetu i sprawdź jak ci idzie.', + 'budgets_index_outro' => 'Aby dowiedzieć się więcej o budżetowaniu, użyj ikonki pomocy w prawym górnym rogu.', // reports (index) - 'reports_index_intro' => 'Skorzystaj z tych raportów, aby uzyskać szczegółowe informacje o swoich finansach.', - 'reports_index_inputReportType' => 'Wybierz typ raportu. Sprawdź stronę pomocy, aby zobaczyć, co pokazuje każdy raport.', - 'reports_index_inputAccountsSelect' => 'Możesz wykluczyć lub uwzględnić konta zasobów według własnego uznania.', - 'reports_index_inputDateRange' => 'Wybrany zakres dat zależy wyłącznie od ciebie: od jednego dnia do 10 lat.', - 'reports_index_extra-options-box' => 'W zależności od wybranego raportu możesz wybrać dodatkowe filtry i opcje tutaj. Obserwuj to pole, gdy zmieniasz typy raportów.', + 'reports_index_intro' => 'Skorzystaj z tych raportów, aby uzyskać szczegółowe informacje o swoich finansach.', + 'reports_index_inputReportType' => 'Wybierz typ raportu. Sprawdź stronę pomocy, aby zobaczyć, co pokazuje każdy raport.', + 'reports_index_inputAccountsSelect' => 'Możesz wykluczyć lub uwzględnić konta zasobów według własnego uznania.', + 'reports_index_inputDateRange' => 'Wybrany zakres dat zależy wyłącznie od ciebie: od jednego dnia do 10 lat.', + 'reports_index_extra-options-box' => 'W zależności od wybranego raportu możesz wybrać dodatkowe filtry i opcje tutaj. Obserwuj to pole, gdy zmieniasz typy raportów.', // reports (reports) - 'reports_report_default_intro' => 'Raport ten zapewni szybki i wszechstronny przegląd twoich finansów. Jeśli chcesz zobaczyć cokolwiek innego, nie wahaj się ze mną skontaktować!', - 'reports_report_audit_intro' => 'Ten raport zawiera szczegółowe informacje na temat kont aktywów.', - 'reports_report_audit_optionsBox' => 'Użyj tych pól wyboru aby pokazać lub ukryć kolumny, które Cię interesują.', + 'reports_report_default_intro' => 'Raport ten zapewni szybki i wszechstronny przegląd twoich finansów. Jeśli chcesz zobaczyć cokolwiek innego, nie wahaj się ze mną skontaktować!', + 'reports_report_audit_intro' => 'Ten raport zawiera szczegółowe informacje na temat kont aktywów.', + 'reports_report_audit_optionsBox' => 'Użyj tych pól wyboru aby pokazać lub ukryć kolumny, które Cię interesują.', 'reports_report_category_intro' => 'Ten raport daje wgląd w jedną lub wiele kategorii.', 'reports_report_category_pieCharts' => 'Te wykresy dają wgląd w wydatki i dochody według kategorii lub konta.', diff --git a/resources/lang/pl_PL/list.php b/resources/lang/pl_PL/list.php index 47fbcaf3af..3084c9a208 100644 --- a/resources/lang/pl_PL/list.php +++ b/resources/lang/pl_PL/list.php @@ -23,71 +23,71 @@ declare(strict_types=1); return [ - 'buttons' => 'Przyciski', - 'icon' => 'Ikona', - 'id' => 'ID', - 'create_date' => 'Utworzono', - 'update_date' => 'Zaktualizowano', - 'updated_at' => 'Zaktualizowano', - 'balance_before' => 'Saldo przed', - 'balance_after' => 'Saldo po', - 'name' => 'Nazwa', - 'role' => 'Rola', - 'currentBalance' => 'Bieżące saldo', - 'linked_to_rules' => 'Powiązane reguły', - 'active' => 'Jest aktywny?', - 'lastActivity' => 'Ostatnia aktywność', - 'balanceDiff' => 'Różnica sald', - 'matchesOn' => 'Dopasowanie', - 'account_type' => 'Typ konta', - 'created_at' => 'Utworzono', - 'account' => 'Konto', - 'matchingAmount' => 'Kwota', - 'split_number' => '# podziału', - 'destination' => 'Cel', - 'source' => 'Źródło', - 'next_expected_match' => 'Następne oczekiwane dopasowanie', - 'automatch' => 'Auto dopasowanie?', - 'repeat_freq' => 'Powtarza się', - 'description' => 'Opis', - 'amount' => 'Kwota', - 'internal_reference' => 'Wewnętrzny numer', - 'date' => 'Data', - 'interest_date' => 'Stopa procentowa', - 'book_date' => 'Data księgowania', - 'process_date' => 'Przetworzono', - 'due_date' => 'Termin realizacji', - 'payment_date' => 'Data płatności', - 'invoice_date' => 'Data faktury', - 'interal_reference' => 'Wewnętrzny numer', - 'notes' => 'Notatki', - 'from' => 'Z', - 'piggy_bank' => 'Skarbonka', - 'to' => 'Do', - 'budget' => 'Budżet', - 'category' => 'Kategoria', - 'bill' => 'Rachunek', - 'withdrawal' => 'Wypłata', - 'deposit' => 'Wpłata', - 'transfer' => 'Transfer', - 'type' => 'Typ', - 'completed' => 'Zakończone', - 'iban' => 'IBAN', - 'paid_current_period' => 'Zapłacono w tym okresie', - 'email' => 'Adres E-Mail', - 'registered_at' => 'Zarejestrowano', - 'is_blocked' => 'Jest zablokowany', - 'is_admin' => 'Jest administratorem', - 'has_two_factor' => 'Ma dwustopniową autoryzację', - 'blocked_code' => 'Kod blokady', - 'source_account' => 'Konto źródłowe', + 'buttons' => 'Przyciski', + 'icon' => 'Ikona', + 'id' => 'ID', + 'create_date' => 'Utworzono', + 'update_date' => 'Zaktualizowano', + 'updated_at' => 'Zaktualizowano', + 'balance_before' => 'Saldo przed', + 'balance_after' => 'Saldo po', + 'name' => 'Nazwa', + 'role' => 'Rola', + 'currentBalance' => 'Bieżące saldo', + 'linked_to_rules' => 'Powiązane reguły', + 'active' => 'Jest aktywny?', + 'transaction_type' => 'Type', + 'lastActivity' => 'Ostatnia aktywność', + 'balanceDiff' => 'Różnica sald', + 'matchesOn' => 'Dopasowanie', + 'account_type' => 'Typ konta', + 'created_at' => 'Utworzono', + 'account' => 'Konto', + 'matchingAmount' => 'Kwota', + 'split_number' => '# podziału', + 'destination' => 'Cel', + 'source' => 'Źródło', + 'next_expected_match' => 'Następne oczekiwane dopasowanie', + 'automatch' => 'Auto dopasowanie?', + 'repeat_freq' => 'Powtarza się', + 'description' => 'Opis', + 'amount' => 'Kwota', + 'internal_reference' => 'Wewnętrzny numer', + 'date' => 'Data', + 'interest_date' => 'Stopa procentowa', + 'book_date' => 'Data księgowania', + 'process_date' => 'Przetworzono', + 'due_date' => 'Termin realizacji', + 'payment_date' => 'Data płatności', + 'invoice_date' => 'Data faktury', + 'interal_reference' => 'Wewnętrzny numer', + 'notes' => 'Notatki', + 'from' => 'Z', + 'piggy_bank' => 'Skarbonka', + 'to' => 'Do', + 'budget' => 'Budżet', + 'category' => 'Kategoria', + 'bill' => 'Rachunek', + 'withdrawal' => 'Wypłata', + 'deposit' => 'Wpłata', + 'transfer' => 'Transfer', + 'type' => 'Typ', + 'completed' => 'Zakończone', + 'iban' => 'IBAN', + 'paid_current_period' => 'Zapłacono w tym okresie', + 'email' => 'Adres E-Mail', + 'registered_at' => 'Zarejestrowano', + 'is_blocked' => 'Jest zablokowany', + 'is_admin' => 'Jest administratorem', + 'has_two_factor' => 'Ma dwustopniową autoryzację', + 'blocked_code' => 'Kod blokady', + 'source_account' => 'Konto źródłowe', 'destination_account' => 'Konto docelowe', 'accounts_count' => 'Liczba kont', 'journals_count' => 'Liczba transakcji', 'attachments_count' => 'Liczba załączników', 'bills_count' => 'Liczba rachunków', 'categories_count' => 'Liczba kategorii', - 'export_jobs_count' => 'Liczba zadań eksportu', 'import_jobs_count' => 'Liczba zadań importu', 'budget_count' => 'Liczba budżetów', 'rule_and_groups_count' => 'Liczba reguł i grup reguł', @@ -106,14 +106,14 @@ return [ 'account_on_spectre' => 'Konto (Spectre)', 'account_on_ynab' => 'Konto (YNAB)', 'do_import' => 'Importuj z tego konta', - 'sepa-ct-id' => 'Identyfikator end-to-end SEPA', - 'sepa-ct-op' => 'Identyfikator przeciwnego konta SEPA', - 'sepa-db' => 'Identyfikator mandatu SEPA', - 'sepa-country' => 'Kraj SEPA', - 'sepa-cc' => 'Kod rozliczeniowy SEPA', - 'sepa-ep' => 'SEPA External Purpose', - 'sepa-ci' => 'SEPA Creditor Identifier', - 'sepa-batch-id' => 'ID paczki SEPA', + 'sepa_ct_id' => 'SEPA End to End Identifier', + 'sepa_ct_op' => 'SEPA Opposing Account Identifier', + 'sepa_db' => 'SEPA Mandate Identifier', + 'sepa_country' => 'SEPA Country', + 'sepa_cc' => 'SEPA Clearing Code', + 'sepa_ep' => 'SEPA External Purpose', + 'sepa_ci' => 'SEPA Creditor Identifier', + 'sepa_batch_id' => 'SEPA Batch ID', 'external_id' => 'Zewnętrzne ID', 'account_at_bunq' => 'Konto bunq', 'file_name' => 'Nazwa pliku', diff --git a/resources/lang/pl_PL/validation.php b/resources/lang/pl_PL/validation.php index bbdee1ffe1..08497096c9 100644 --- a/resources/lang/pl_PL/validation.php +++ b/resources/lang/pl_PL/validation.php @@ -36,12 +36,16 @@ return [ 'file_attached' => 'Pomyślnie wgrano plik ":name".', 'must_exist' => 'Identyfikator w polu :attribute nie istnieje w bazie danych.', 'all_accounts_equal' => 'Wszystkie konta w tym polu muszą być takie same.', + 'group_title_mandatory' => 'Tytuł grupy jest obowiązkowy, gdy istnieje więcej niż jedna transakcja.', + 'transaction_types_equal' => 'Wszystkie podziały muszą być tego samego typu.', + 'invalid_transaction_type' => 'Nieprawidłowy typ transakcji.', 'invalid_selection' => 'Twój wybór jest nieprawidłowy.', 'belongs_user' => 'Ta wartość jest nieprawidłowa dla tego pola.', 'at_least_one_transaction' => 'Wymaga co najmniej jednej transakcji.', 'at_least_one_repetition' => 'Wymaga co najmniej jednego powtórzenia.', 'require_repeat_until' => 'Wymagana jest liczba powtórzeń lub data zakończenia (repeat_until), ale nie obie jednocześnie.', 'require_currency_info' => 'Treść tego pola jest nieprawidłowa bez informacji o walucie.', + 'require_currency_amount' => 'Treść tego pola jest nieprawidłowa bez informacji o obcej kwocie.', 'equal_description' => 'Opis transakcji nie powinien być równy globalnemu opisowi.', 'file_invalid_mime' => 'Plik ":name" jest typu ":mime", który nie jest akceptowany jako nowy plik do przekazania.', 'file_too_large' => 'Plik ":name" jest zbyt duży.', @@ -135,8 +139,8 @@ return [ 'name' => 'nazwa', 'piggy_bank_id' => 'identyfikator skarbonki', 'targetamount' => 'kwota docelowa', - 'openingBalanceDate' => 'data salda otwarcia', - 'openingBalance' => 'bilans otwarcia', + 'opening_balance_date' => 'data salda otwarcia', + 'opening_balance' => 'saldo otwarcia', 'match' => 'dopasowanie', 'amount_min' => 'minimalna kwota', 'amount_max' => 'maksymalna kwota', @@ -164,4 +168,28 @@ return [ 'rule-trigger.4' => 'wyzwalacz reguły #4', 'rule-trigger.5' => 'wyzwalacz reguły #5', ], + + // validation of accounts: + 'withdrawal_source_need_data' => 'Aby kontynuować, musisz uzyskać prawidłowy identyfikator konta źródłowego i/lub prawidłową nazwę konta źródłowego.', + 'withdrawal_source_bad_data' => 'Nie można znaleźć poprawnego konta źródłowego podczas wyszukiwania identyfikatora ":id" lub nazwy ":name".', + 'withdrawal_dest_need_data' => 'Aby kontynuować, musisz uzyskać prawidłowy identyfikator konta wydatków i/lub prawidłową nazwę konta wydatków.', + 'withdrawal_dest_bad_data' => 'Nie można znaleźć poprawnego konta wydatków podczas wyszukiwania identyfikatora ":id" lub nazwy ":name".', + + 'deposit_source_need_data' => 'Aby kontynuować, musisz uzyskać prawidłowy identyfikator konta źródłowego i/lub prawidłową nazwę konta źródłowego.', + 'deposit_source_bad_data' => 'Nie można znaleźć poprawnego konta źródłowego podczas wyszukiwania identyfikatora ":id" lub nazwy ":name".', + 'deposit_dest_need_data' => 'Aby kontynuować, musisz uzyskać prawidłowy identyfikator konta wydatków i/lub prawidłową nazwę konta wydatków.', + 'deposit_dest_bad_data' => 'Nie można znaleźć poprawnego konta wydatków podczas wyszukiwania identyfikatora ":id" lub nazwy ":name".', + + 'transfer_source_need_data' => 'Aby kontynuować, musisz uzyskać prawidłowy identyfikator konta źródłowego i/lub prawidłową nazwę konta źródłowego.', + 'transfer_source_bad_data' => 'Nie można znaleźć poprawnego konta źródłowego podczas wyszukiwania identyfikatora ":id" lub nazwy ":name".', + 'transfer_dest_need_data' => 'Aby kontynuować, musisz uzyskać prawidłowy identyfikator konta wydatków i/lub prawidłową nazwę konta wydatków.', + 'transfer_dest_bad_data' => 'Nie można znaleźć poprawnego konta wydatków podczas wyszukiwania identyfikatora ":id" lub nazwy ":name".', + 'need_id_in_edit' => 'Każdy podział musi posiadać transaction_journal_id (poprawny identyfikator lub 0).', + + 'ob_source_need_data' => 'Aby kontynuować, musisz uzyskać prawidłowy identyfikator konta źródłowego i/lub prawidłową nazwę konta źródłowego.', + 'ob_dest_need_data' => 'Aby kontynuować, musisz uzyskać prawidłowy identyfikator konta wydatków i/lub prawidłową nazwę konta wydatków.', + 'ob_dest_bad_data' => 'Nie można znaleźć poprawnego konta wydatków podczas wyszukiwania identyfikatora ":id" lub nazwy ":name".', + + 'generic_invalid_source' => 'You can\'t use this account as the source account.', + 'generic_invalid_destination' => 'You can\'t use this account as the destination account.', ]; diff --git a/resources/lang/pt_BR/breadcrumbs.php b/resources/lang/pt_BR/breadcrumbs.php index 793571ed1c..508db43def 100644 --- a/resources/lang/pt_BR/breadcrumbs.php +++ b/resources/lang/pt_BR/breadcrumbs.php @@ -23,35 +23,37 @@ declare(strict_types=1); return [ - 'home' => 'Início', - 'edit_currency' => 'Editar moeda ":name"', - 'delete_currency' => 'Excluir moeda ":name"', - 'newPiggyBank' => 'Criar um novo cofrinho', - 'edit_piggyBank' => 'Editar cofrinho ":name"', - 'preferences' => 'Preferências', - 'profile' => 'Perfil', - 'changePassword' => 'Alterar sua senha', - 'change_email' => 'Altere seu endereço de email', - 'bills' => 'Faturas', - 'newBill' => 'Nova fatura', - 'edit_bill' => 'Editar fatura ":name"', - 'delete_bill' => 'Apagar fatura ":name"', - 'reports' => 'Relatórios', - 'search_result' => 'Resultados da busca ":query"', - 'withdrawal_list' => 'Despesas', - 'deposit_list' => 'Receitas, renda e depósitos', - 'transfer_list' => 'Transferências', - 'transfers_list' => 'Transferências', - 'reconciliation_list' => 'Reconciliações', - 'create_withdrawal' => 'Criar uma nova retirada', - 'create_deposit' => 'Criar um novo depósito', - 'create_transfer' => 'Criar nova transferência', - 'edit_journal' => 'Editar transação ":description"', - 'edit_reconciliation' => 'Editar ":description"', - 'delete_journal' => 'Apagar transação ":description"', - 'tags' => 'Etiquetas', - 'createTag' => 'Criar nova etiqueta', - 'edit_tag' => 'Editar etiqueta ":tag"', - 'delete_tag' => 'Apagar etiqueta ":tag"', - 'delete_journal_link' => 'Eliminar ligação entre transações', + 'home' => 'Início', + 'edit_currency' => 'Editar moeda ":name"', + 'delete_currency' => 'Excluir moeda ":name"', + 'newPiggyBank' => 'Criar um novo cofrinho', + 'edit_piggyBank' => 'Editar cofrinho ":name"', + 'preferences' => 'Preferências', + 'profile' => 'Perfil', + 'changePassword' => 'Alterar sua senha', + 'change_email' => 'Altere seu endereço de email', + 'bills' => 'Faturas', + 'newBill' => 'Nova fatura', + 'edit_bill' => 'Editar fatura ":name"', + 'delete_bill' => 'Apagar fatura ":name"', + 'reports' => 'Relatórios', + 'search_result' => 'Resultados da busca ":query"', + 'withdrawal_list' => 'Despesas', + 'Withdrawal_list' => 'Despesas', + 'deposit_list' => 'Receitas, renda e depósitos', + 'transfer_list' => 'Transferências', + 'transfers_list' => 'Transferências', + 'reconciliation_list' => 'Reconciliações', + 'create_withdrawal' => 'Criar uma nova retirada', + 'create_deposit' => 'Criar um novo depósito', + 'create_transfer' => 'Criar nova transferência', + 'create_new_transaction' => 'Criar nova transação', + 'edit_journal' => 'Editar transação ":description"', + 'edit_reconciliation' => 'Editar ":description"', + 'delete_journal' => 'Apagar transação ":description"', + 'tags' => 'Etiquetas', + 'createTag' => 'Criar nova etiqueta', + 'edit_tag' => 'Editar etiqueta ":tag"', + 'delete_tag' => 'Apagar etiqueta ":tag"', + 'delete_journal_link' => 'Eliminar ligação entre transações', ]; diff --git a/resources/lang/pt_BR/firefly.php b/resources/lang/pt_BR/firefly.php index b4a52d0106..d64fb7103c 100644 --- a/resources/lang/pt_BR/firefly.php +++ b/resources/lang/pt_BR/firefly.php @@ -33,7 +33,7 @@ return [ 'last_seven_days' => 'Últimos sete dias', 'last_thirty_days' => 'Últimos 30 dias', 'welcomeBack' => 'O que está passando?', - 'welcome_back' => 'O que está passando?', + 'welcome_back' => 'O que está passando?', 'everything' => 'Tudo', 'today' => 'hoje', 'customRange' => 'Intervalo Personalizado', @@ -54,6 +54,7 @@ return [ 'create_new_stuff' => 'Criar novas coisas', 'new_withdrawal' => 'Nova retirada', 'create_new_transaction' => 'Criar nova transação', + 'new_transaction' => 'Nova transação', 'go_to_asset_accounts' => 'Veja suas contas ativas', 'go_to_budgets' => 'Vá para seus orçamentos', 'go_to_categories' => 'Vá para suas categorias', @@ -82,28 +83,32 @@ return [ 'help_for_this_page' => 'Ajuda para esta página', 'no_help_could_be_found' => 'O texto de ajuda não pode ser encontrado.', 'no_help_title' => 'Desculpe, ocorreu um erro.', - 'two_factor_welcome' => 'Olá, :user!', - 'two_factor_enter_code' => 'Para continuar, por favor, digite seu código de autenticação em duas etapas. Seu aplicativo pode gerá-lo para você.', - 'two_factor_code_here' => 'Insira o código aqui', - 'two_factor_title' => 'Autenticação em duas etapas', - 'authenticate' => 'Autenticar', - 'two_factor_forgot_title' => 'Perdeu autenticação em duas etapas', - 'two_factor_forgot' => 'Esqueci minha autenticação em duas etapas.', - 'two_factor_lost_header' => 'Perdeu sua autenticação em duas etapas?', - 'two_factor_lost_intro' => 'Infelizmente, isso não é algo que você pode redefinir a partir da interface web. Você tem duas opções.', - 'two_factor_lost_fix_self' => 'Se você executar sua própria instância do Firefly III, verifique os logs no storage/logs para obter instruções.', - 'two_factor_lost_fix_owner' => 'Caso contrário, o proprietário do site :site_owner e peça para redefinir a sua autenticação de duas etapas.', - 'warning_much_data' => ':days dias de dados podem demorar um pouco para carregar.', - 'registered' => 'Você se registrou com sucesso!', - 'Default asset account' => 'Conta padrão', - 'no_budget_pointer' => 'Parece que você ainda não tem orçamentos. Você deve criar alguns na página de orçamentos. Orçamentos podem ajudá-lo a manter o controle das despesas.', - 'Savings account' => 'Conta poupança', - 'Credit card' => 'Cartão de crédito', - 'source_accounts' => 'Conta(s) de origem', - 'destination_accounts' => 'Conta(s) de destino', - 'user_id_is' => 'Seu id de usuário é :user', - 'field_supports_markdown' => 'Este campo suporta Markdown.', - 'need_more_help' => 'Se você precisa de mais ajuda usando o Firefly III, por favor abra um ticket no Github.', + 'two_factor_welcome' => 'Olá!', + 'two_factor_enter_code' => 'Para continuar, por favor, digite seu código de autenticação em duas etapas. Seu aplicativo pode gerá-lo para você.', + 'two_factor_code_here' => 'Insira o código aqui', + 'two_factor_title' => 'Autenticação em duas etapas', + 'authenticate' => 'Autenticar', + 'two_factor_forgot_title' => 'Perdeu autenticação em duas etapas', + 'two_factor_forgot' => 'Esqueci minha autenticação em duas etapas.', + 'two_factor_lost_header' => 'Perdeu sua autenticação em duas etapas?', + 'two_factor_lost_intro' => 'Se você perdeu seus códigos de backup também, você tem azar. Isso não é algo que você pode corrigir a partir da interface da web. Você tem duas escolhas.', + 'two_factor_lost_fix_self' => 'Se você executar sua própria instância de Firefly III, verifique os logs em storage/logs para instruções, ou execute docker logs <container_id> para ver as instruções (atualize esta página).', + 'two_factor_lost_fix_owner' => 'Caso contrário, o proprietário do site :site_owner e peça para redefinir a sua autenticação de duas etapas.', + 'mfa_backup_code' => 'Você usou um código de backup para acessar o Firefly III. Não pode ser usado novamente, então cruze-o na sua lista.', + 'pref_two_factor_new_backup_codes' => 'Obter novos códigos de backup', + 'pref_two_factor_backup_code_count' => 'Você tem :count código(s) de backup válido(s).', + '2fa_i_have_them' => 'Eu os armazenei!', + 'warning_much_data' => ':days dias de dados podem demorar um pouco para carregar.', + 'registered' => 'Você se registrou com sucesso!', + 'Default asset account' => 'Conta padrão', + 'no_budget_pointer' => 'Parece que você ainda não tem orçamentos. Você deve criar alguns na página de orçamentos. Orçamentos podem ajudá-lo a manter o controle das despesas.', + 'Savings account' => 'Conta poupança', + 'Credit card' => 'Cartão de crédito', + 'source_accounts' => 'Conta(s) de origem', + 'destination_accounts' => 'Conta(s) de destino', + 'user_id_is' => 'Seu id de usuário é :user', + 'field_supports_markdown' => 'Este campo suporta Markdown.', + 'need_more_help' => 'Se você precisa de mais ajuda usando o Firefly III, por favor abra um ticket no Github.', 'reenable_intro_text' => 'Você pode também reativar o guia de introdução.', 'intro_boxes_after_refresh' => 'As caixas de introdução reaparecerão quando você atualizar a página.', 'show_all_no_filter' => 'Mostre todas as transações sem agrupá-las por data.', @@ -220,21 +225,23 @@ return [ 'search_query' => 'Pedido', 'search_found_transactions' => 'Firefly III encontrou :count transação(ões) em :time segundo(s).', 'search_for_query' => 'Firefly III está procurando transações com todas estas palavras neles: :query', - 'search_modifier_amount_is' => 'Valor é exatamente :value', - 'search_modifier_amount' => 'Valor é exatamente :value', - 'search_modifier_amount_max' => 'Valor é no máximo :value', - 'search_modifier_amount_min' => 'Valor é pelo menos :value', - 'search_modifier_amount_less' => 'Valor é menor que :value', - 'search_modifier_amount_more' => 'Valor é maior que :value', - 'search_modifier_source' => 'Conta de origem é :value', - 'search_modifier_destination' => 'Conta de destino é :value', - 'search_modifier_category' => 'Categoria é :value', - 'search_modifier_budget' => 'Orçamento é :value', - 'search_modifier_bill' => 'Fatura é :value', - 'search_modifier_type' => 'Tipo de transação é :value', - 'search_modifier_date' => 'Data da transação é :value', - 'search_modifier_date_before' => 'Data da transação é antes de :value', - 'search_modifier_date_after' => 'Data da transação é após :value', + 'search_modifier_amount_is' => 'Valor é exatamente :value', + 'search_modifier_amount' => 'Valor é exatamente :value', + 'search_modifier_amount_max' => 'Valor é no máximo :value', + 'search_modifier_amount_min' => 'Valor é pelo menos :value', + 'search_modifier_amount_less' => 'Valor é menor que :value', + 'search_modifier_amount_more' => 'Valor é maior que :value', + 'search_modifier_source' => 'Conta de origem é :value', + 'search_modifier_from' => 'Conta de origem é :value', + 'search_modifier_destination' => 'Conta de destino é :value', + 'search_modifier_to' => 'Conta de destino é :value', + 'search_modifier_category' => 'Categoria é :value', + 'search_modifier_budget' => 'Orçamento é :value', + 'search_modifier_bill' => 'Fatura é :value', + 'search_modifier_type' => 'Tipo de transação é :value', + 'search_modifier_date' => 'Data da transação é :value', + 'search_modifier_date_before' => 'Data da transação é antes de :value', + 'search_modifier_date_after' => 'Data da transação é após :value', 'search_modifier_on' => 'Data da transação é :value', 'search_modifier_before' => 'Data da transação é antes de :value', 'search_modifier_after' => 'Data da transação é após :value', @@ -257,33 +264,6 @@ return [ 'half-year' => 'metade de cada ano', 'yearly' => 'anual', - // export data: - 'import_and_export' => 'Importar / Exportar', - 'export_data' => 'Exportar dados', - 'export_and_backup_data' => 'Exportar dados', - 'export_data_intro' => 'Use os dados exportados para mover para uma nova aplicação financeira. Observe que esses arquivos não são feitos como um backup. Eles não contêm informações suficientes sobre os dados para restaurar completamente uma nova instalação do Firefly III. Se você quiser fazer um backup de seus dados, faça backup do banco de dados diretamente.', - 'export_format' => 'Formato de exportação', - 'export_format_csv' => 'Valores separados por vírgula (arquivo CSV)', - 'export_format_mt940' => 'Compatível com formato MT940', - 'include_old_uploads_help' => 'O Firefly III não joga fora os arquivos CSV originais que você importou no passado. Você pode incluí-los em sua exportação.', - 'do_export' => 'Exportar', - 'export_status_never_started' => 'A exportação ainda não foi iniciada', - 'export_status_make_exporter' => 'Criar o exportador...', - 'export_status_collecting_journals' => 'Coletando suas transações...', - 'export_status_collected_journals' => 'Suas transações foram coletadas!', - 'export_status_converting_to_export_format' => 'Convertendo suas transações...', - 'export_status_converted_to_export_format' => 'Suas transações foram convertidas!', - 'export_status_creating_journal_file' => 'Criando o arquivo de exportação...', - 'export_status_created_journal_file' => 'Criado o arquivo de exportação!', - 'export_status_collecting_attachments' => 'Recolher todos os seus anexos...', - 'export_status_collected_attachments' => 'Recolhidos todos os seus anexos!', - 'export_status_collecting_old_uploads' => 'Recolher todos os seus envios anteriores...', - 'export_status_collected_old_uploads' => 'Recolhidos todos os seus envios anteriores!', - 'export_status_creating_zip_file' => 'Criando um arquivo zip...', - 'export_status_created_zip_file' => 'Criado um arquivo zip!', - 'export_status_finished' => 'Exportação terminou com sucesso! Yay!', - 'export_data_please_wait' => 'Por favor aguarde...', - // rules 'rules' => 'Regras', 'rule_name' => 'Nome da regra', @@ -502,30 +482,33 @@ return [ 'pref_custom_fiscal_year_help' => 'Nos países que usam um exercício diferente de 1 de Janeiro a 31 de Dezembro, você pode ativar isto e especificar início / fim do ano fiscal', 'pref_fiscal_year_start_label' => 'Data de início de ano fiscal', 'pref_two_factor_auth' => 'Verificação em duas etapas', - 'pref_two_factor_auth_help' => 'Quando você habilitar verificação em 2-passos (também conhecido como Two Factor Authentication), você adicionar uma camada extra de segurança para sua conta. Você entra com alguma coisa que você sabe (sua senha) e algo que você tem (um código de verificação). Os códigos de verificação são gerados por um aplicativo em seu telefone, como Authy ou Google Authenticator.', - 'pref_enable_two_factor_auth' => 'Habilitar a verificação de 2 etapas', - 'pref_two_factor_auth_disabled' => 'código de verificação em 2 etapas removido e desativado', - 'pref_two_factor_auth_remove_it' => 'Não se esqueça de remover a conta de seu aplicativo de autenticação!', - 'pref_two_factor_auth_code' => 'Verificar código', - 'pref_two_factor_auth_code_help' => 'Scaneie o código QR com um aplicativo em seu telefone como Authy ou Google Authenticator e insira o código gerado.', - 'pref_two_factor_auth_reset_code' => 'Redefinir o código de verificação', - 'pref_two_factor_auth_disable_2fa' => 'Desativar verificação em duas etapas', - '2fa_use_secret_instead' => 'Se você não pode escanear o código QR, sinta-se à vontade para usar o código secreto: :secret.', - 'pref_save_settings' => 'Salvar definições', - 'saved_preferences' => 'Preferências salvas!', - 'preferences_general' => 'Geral', - 'preferences_frontpage' => 'Tela inicial', - 'preferences_security' => 'Segurança', - 'preferences_layout' => 'Interface', - 'pref_home_show_deposits' => 'Depósitos de mostrar na tela inicial', - 'pref_home_show_deposits_info' => 'A tela inicial já mostra suas contas de despesas. Deveria também mostrar suas receitas?', - 'pref_home_do_show_deposits' => 'Sim, mostrar-lhes', - 'successful_count' => 'dos quais :count bem sucedida', - 'list_page_size_title' => 'Tamanho da página', - 'list_page_size_help' => 'Qualquer lista de coisas (contas, transações, etc.) mostra, no máximo, este tanto por página.', - 'list_page_size_label' => 'Tamanho da página', - 'between_dates' => '(:start e :end)', - 'pref_optional_fields_transaction' => 'Campos opcionais para transações', + 'pref_two_factor_auth_help' => 'Quando você habilitar verificação em 2-passos (também conhecido como Two Factor Authentication), você adicionar uma camada extra de segurança para sua conta. Você entra com alguma coisa que você sabe (sua senha) e algo que você tem (um código de verificação). Os códigos de verificação são gerados por um aplicativo em seu telefone, como Authy ou Google Authenticator.', + 'pref_enable_two_factor_auth' => 'Habilitar a verificação de 2 etapas', + 'pref_two_factor_auth_disabled' => 'código de verificação em 2 etapas removido e desativado', + 'pref_two_factor_auth_remove_it' => 'Não se esqueça de remover a conta de seu aplicativo de autenticação!', + 'pref_two_factor_auth_code' => 'Verificar código', + 'pref_two_factor_auth_code_help' => 'Scaneie o código QR com um aplicativo em seu telefone como Authy ou Google Authenticator e insira o código gerado.', + 'pref_two_factor_auth_reset_code' => 'Redefinir o código de verificação', + 'pref_two_factor_auth_disable_2fa' => 'Desativar verificação em duas etapas', + '2fa_use_secret_instead' => 'Se você não pode escanear o QR code, sinta-se à vontade para usar o código secreto :secret.', + '2fa_backup_codes' => 'Armazene esses códigos de reserva para acesso, caso você perca seu dispositivo.', + '2fa_already_enabled' => 'A verificação em duas etapas já está habilitada.', + 'wrong_mfa_code' => 'Esse código MFA não é válido.', + 'pref_save_settings' => 'Salvar definições', + 'saved_preferences' => 'Preferências salvas!', + 'preferences_general' => 'Geral', + 'preferences_frontpage' => 'Tela inicial', + 'preferences_security' => 'Segurança', + 'preferences_layout' => 'Interface', + 'pref_home_show_deposits' => 'Depósitos de mostrar na tela inicial', + 'pref_home_show_deposits_info' => 'A tela inicial já mostra suas contas de despesas. Deveria também mostrar suas receitas?', + 'pref_home_do_show_deposits' => 'Sim, mostrar-lhes', + 'successful_count' => 'dos quais :count bem sucedida', + 'list_page_size_title' => 'Tamanho da página', + 'list_page_size_help' => 'Qualquer lista de coisas (contas, transações, etc.) mostra, no máximo, este tanto por página.', + 'list_page_size_label' => 'Tamanho da página', + 'between_dates' => '(:start e :end)', + 'pref_optional_fields_transaction' => 'Campos opcionais para transações', 'pref_optional_fields_transaction_help' => 'Por padrão, nem todos os campos estão ativados ao criar uma nova transação (por causa da desordem). Abaixo, você pode habilitar esses campos se você acha que eles podem ser úteis para você. Claro, qualquer campo desabilitado, mas já preenchido, será visível, independentemente da configuração.', 'optional_tj_date_fields' => 'Campos de data', 'optional_tj_business_fields' => 'Campos de negócios', @@ -579,24 +562,25 @@ return [ 'login_with_new_email' => 'Agora você pode fazer login com seu novo endereço de e-mail.', 'login_with_old_email' => 'Agora você pode fazer login novamente com o seu endereço de e-mail antigo.', 'login_provider_local_only' => 'Esta ação não está disponível durante a autenticação por meio de ":login_provider".', - 'delete_local_info_only' => 'Como você se autentica por meio de ":login_provider", isso irá apagar apenas informações locais do Firefly III.', + 'delete_local_info_only' => 'Como você se autentica por meio de ":login_provider", isso irá apagar apenas informações locais do Firefly III.', // attachments - 'nr_of_attachments' => 'Um anexo|:count anexos', - 'attachments' => 'Anexos', - 'edit_attachment' => 'Editar anexo ":name"', - 'update_attachment' => 'Atualizar anexo', - 'delete_attachment' => 'Apagar anexo ":name"', - 'attachment_deleted' => 'Anexo apagado ":name"', - 'attachment_updated' => 'Anexo atualizado ":name"', - 'upload_max_file_size' => 'Tamanho máximo do arquivo: :size', - 'list_all_attachments' => 'Lista de todos os anexos', + 'nr_of_attachments' => 'Um anexo|:count anexos', + 'attachments' => 'Anexos', + 'edit_attachment' => 'Editar anexo ":name"', + 'update_attachment' => 'Atualizar anexo', + 'delete_attachment' => 'Apagar anexo ":name"', + 'attachment_deleted' => 'Anexo apagado ":name"', + 'liabilities_deleted' => 'Passivo excluído ":name"', + 'attachment_updated' => 'Anexo atualizado ":name"', + 'upload_max_file_size' => 'Tamanho máximo do arquivo: :size', + 'list_all_attachments' => 'Lista de todos os anexos', // transaction index - 'title_expenses' => 'Despesas', - 'title_withdrawal' => 'Despesas', - 'title_revenue' => 'Receitas / Renda', - 'title_deposit' => 'Receita / Renda', + 'title_expenses' => 'Despesas', + 'title_withdrawal' => 'Despesas', + 'title_revenue' => 'Receitas / Renda', + 'title_deposit' => 'Receita / Renda', 'title_transfer' => 'Transferências', 'title_transfers' => 'Transferências', @@ -634,6 +618,9 @@ return [ 'converted_to_Transfer' => 'A transação foi convertida em uma transferência', 'invalid_convert_selection' => 'A conta que você selecionou já é usada nesta transação ou não existe.', 'source_or_dest_invalid' => 'Os detalhes corretos da transação não foram encontrados. A conversão não é possível.', + 'convert_to_withdrawal' => 'Converter para retirada', + 'convert_to_deposit' => 'Converter para depósito', + 'convert_to_transfer' => 'Converter para transferência', // create new stuff: 'create_new_withdrawal' => 'Criar nova retirada', @@ -695,7 +682,9 @@ return [ 'available_amount_indication' => 'Use esses montantes para obter uma indicação do que seu orçamento total poderia ser.', 'suggested' => 'Sugerido', 'average_between' => 'Média entre :start e :end', - 'over_budget_warn' => ' Normalmente seu orçamento é de :amount por dia. Este é de is :over_amount por dia.', + 'over_budget_warn' => ' Normalmente seu orçamento é de :amount por dia. Desta vez o valor é :over_amount por dia. Você tem certeza?', + 'transferred_in' => 'Transferido (para)', + 'transferred_away' => 'Transferido (fora)', // bills: 'match_between_amounts' => 'Fatura corresponde a transações entre :low e :high.', @@ -779,6 +768,7 @@ return [ 'reconcile_options' => 'Opções de reconciliação', 'reconcile_range' => 'Intervalo de reconciliação', 'start_reconcile' => 'Comece a reconciliar', + 'cash_account_type' => 'Dinheiro', 'cash' => 'dinheiro', 'account_type' => 'Tipo de conta', 'save_transactions_by_moving' => 'Salve essas transações, movendo-os para outra conta:', @@ -803,7 +793,9 @@ return [ 'reconcile_go_back' => 'Você sempre pode editar ou excluir uma correção mais tarde.', 'must_be_asset_account' => 'Você só pode conciliar contas de ativos', 'reconciliation_stored' => 'Reconciliação armazenada', - 'reconcilliation_transaction_title' => 'Reconciliação (:from a :to)', + 'reconciliation_error' => 'Devido a um erro, as transações foram marcadas como conciliadas, mas a correcção não foi armazenada: :error.', + 'reconciliation_transaction_title' => 'Reconciliação (:from a :to)', + 'sum_of_reconciliation' => 'Total reconciliado', 'reconcile_this_account' => 'Concilie esta conta', 'confirm_reconciliation' => 'Confirmar reconciliação', 'submitted_start_balance' => 'Saldo inicial enviado', @@ -815,7 +807,7 @@ return [ 'interest_calc_daily' => 'Por dia', 'interest_calc_monthly' => 'Por mês', 'interest_calc_yearly' => 'Por ano', - 'initial_balance_account' => 'Saldo inicial da conta :name', + 'initial_balance_account' => 'Saldo inicial da conta :account', // categories: 'new_category' => 'Nova categoria', @@ -847,6 +839,8 @@ return [ 'deleted_deposit' => 'Depósito ":description" excluído com sucesso', 'deleted_transfer' => 'Transferência ":description" excluída com sucesso', 'stored_journal' => 'Transação ":description" incluída com sucesso', + 'stored_journal_no_descr' => 'Transação criada com sucesso', + 'updated_journal_no_descr' => 'Transação atualizada com sucesso', 'select_transactions' => 'Selecione as transações', 'rule_group_select_transactions' => 'Aplicar ":title" às transações', 'rule_select_transactions' => 'Aplicar ":title" às transações', @@ -855,26 +849,33 @@ return [ 'mass_delete_journals' => 'Excluir um número de transacções', 'mass_edit_journals' => 'Editar um número de transacções', 'mass_bulk_journals' => 'Editar um grande número de transações', - 'mass_bulk_journals_explain' => 'Se você não quer mudar suas transações uma a uma, usando a função de edição em massa, você pode atualizá-los de uma só vez. Basta selecionar a categoria, tag(s) ou orçamento preferidos nos campos abaixo e todas as transações na tabela serão atualizadas.', + 'mass_bulk_journals_explain' => 'Este formulário permite alterar as propriedades das transações listadas abaixo em uma atualização abrangente. Todas as transações na tabela serão atualizadas quando você alterar os parâmetros que você vê aqui.', + 'part_of_split' => 'Esta transação faz parte de uma transação dividida. Se você não selecionou todas as divisões, poderá acabar mudando apenas metade da transação.', 'bulk_set_new_values' => 'Use as entradas abaixo para definir novos valores. Se você deixá-los vazios, eles serão feitos vazios para todos. Além disso, note que apenas as retiradas receberão um orçamento.', 'no_bulk_category' => 'Não atualize a categoria', 'no_bulk_budget' => 'Não atualize o orçamento', - 'no_bulk_tags' => 'Não atualize a(s) tag(s)', - 'bulk_edit' => 'Editar vários', - 'cannot_edit_other_fields' => 'Você não pode editar em massa outros campos que não esses aqui, porque não há espaço para mostrá-los. Por favor siga o link e editá-los por um por um, se você precisar editar esses campos.', - 'no_budget' => '(sem orçamento)', - 'no_budget_squared' => '(sem orçamento)', - 'perm-delete-many' => 'Exclusão de muitos itens de uma só vez pode ser muito perturbador. Por favor, seja cauteloso.', - 'mass_deleted_transactions_success' => 'Excluído :amount de transação(ões).', - 'mass_edited_transactions_success' => 'Atualizado :amount de transação(ões)', - 'opt_group_' => '(nenhum tipo de conta)', + 'no_bulk_tags' => 'Não atualize a(s) tag(s)', + 'mass_edit' => 'Editar selecionados individualmente', + 'bulk_edit' => 'Editar selecionados em massa', + 'mass_delete' => 'Excluir selecionados', + 'cannot_edit_other_fields' => 'Você não pode editar em massa outros campos que não esses aqui, porque não há espaço para mostrá-los. Por favor siga o link e editá-los por um por um, se você precisar editar esses campos.', + 'cannot_change_amount_reconciled' => 'Você não pode alterar o valor das transações reconciliadas.', + 'no_budget' => '(sem orçamento)', + 'no_budget_squared' => '(sem orçamento)', + 'perm-delete-many' => 'Excluir muitos itens de uma só vez pode ser muito perturbador. Por favor, seja cauteloso. Você pode excluir parte de uma transação dividida desta página, então tome cuidado.', + 'mass_deleted_transactions_success' => 'Excluído :amount de transação(ões).', + 'mass_edited_transactions_success' => 'Atualizado :amount de transação(ões)', + 'opt_group_' => '(nenhum tipo de conta)', 'opt_group_no_account_type' => '(sem o tipo de conta)', 'opt_group_defaultAsset' => 'Contas padrão', 'opt_group_savingAsset' => 'Contas de poupança', 'opt_group_sharedAsset' => 'Contas de ativos compartilhadas', 'opt_group_ccAsset' => 'Cartões de crédito', 'opt_group_cashWalletAsset' => 'Carteiras de dinheiro', + 'opt_group_expense_account' => 'Contas de despesas', + 'opt_group_revenue_account' => 'Contas de receitas', 'opt_group_l_Loan' => 'Passivo: empréstimo', + 'opt_group_cash_account' => 'Conta em dinheiro', 'opt_group_l_Debt' => 'Passivo: dívida', 'opt_group_l_Mortgage' => 'Passivo: hipoteca', 'opt_group_l_Credit card' => 'Passivo: cartão de crédito', @@ -973,7 +974,7 @@ return [ 'errors' => 'Erros', 'debt_start_date' => 'Data de início da dívida', 'debt_start_amount' => 'Montante inicial da dívida', - 'debt_start_amount_help' => 'Digite o montante inicial deste passivo como um valor positivo. Você também pode digitar o valor atual. Certifique-se de que a data abaixo esteja editada de maneira correspondente.', + 'debt_start_amount_help' => 'Se você deve um valor, é melhor inserir um valor negativo, porque ele influencia seu valor total. Se devem a você um valor, o mesmo se aplica. Confira as páginas de ajuda para obter mais informações.', 'store_new_liabilities_account' => 'Guardar novo passivo', 'edit_liabilities_account' => 'Editar passivo ":name"', @@ -1145,6 +1146,7 @@ return [ 'deleted_piggy_bank' => 'Apagar cofrinho ":name"', 'added_amount_to_piggy' => 'Adicionado :amount de ":name"', 'removed_amount_from_piggy' => 'Removido :amount de ":name"', + 'piggy_events' => 'Relacionado ao cofrinho', // tags 'delete_tag' => 'Apagar tag ":tag"', @@ -1154,47 +1156,57 @@ return [ 'updated_tag' => 'Tag atualizada ":tag"', 'created_tag' => 'Tag ":tag" foi criada!', - 'transaction_journal_information' => 'Informação da transação', - 'transaction_journal_meta' => 'Meta-informação', - 'total_amount' => 'Valor total', - 'number_of_decimals' => 'Número de casas decimais', + 'transaction_journal_information' => 'Informação da transação', + 'transaction_journal_meta' => 'Meta-informação', + 'transaction_journal_more' => 'Mais informações', + 'att_part_of_journal' => 'Armazendo sob ":journal"', + 'total_amount' => 'Valor total', + 'number_of_decimals' => 'Número de casas decimais', // administration - 'administration' => 'Administração', - 'user_administration' => 'Administração de usuários', - 'list_all_users' => 'Todos os usuários', - 'all_users' => 'Todos os usuários', - 'instance_configuration' => 'Configuração', - 'firefly_instance_configuration' => 'Opções de configuração para Firefly III', - 'setting_single_user_mode' => 'Modo de usuário único', - 'setting_single_user_mode_explain' => 'Por padrão, o Firefly III aceita apenas um (1) usuário registrado: você. Esta é uma medida de segurança para impedir que outros usem sua instalação a menos que você os permita. Os registrors futuros estão bloqueados. Quando você desmarca esta opção, outros podem usar sua instalação se puderem alcançá-la (quando ela está conectada à Internet).', - 'store_configuration' => 'Configuração da Loja', - 'single_user_administration' => 'Administração de usuários para :email', - 'edit_user' => 'Editar usuário :email', - 'hidden_fields_preferences' => 'Nem todos os campos estão visíveis agora. Você deve habilitá-los em suas configurações.', - 'user_data_information' => 'Dados de usuário', - 'user_information' => 'Informações do usuário', - 'total_size' => 'tamanho total', - 'budget_or_budgets' => 'orçamento(s)', - 'budgets_with_limits' => 'budget(s) com quantidade configurada', - 'nr_of_rules_in_total_groups' => ':count_rules regra (s) em :count_groups grupo(s) de regras', - 'tag_or_tags' => 'tag(s)', - 'configuration_updated' => 'A configuração foi atualizada', - 'setting_is_demo_site' => 'Site demo', - 'setting_is_demo_site_explain' => 'Se você marcar esta caixa, esta instalação se comportará como se fosse o site de demonstração, o que pode ter efeitos colaterais estranhos.', - 'block_code_bounced' => 'Mensagem(s) de email ressaltada', - 'block_code_expired' => 'Conta de demonstração expirada', - 'no_block_code' => 'Nenhuma razão para o bloqueio ou o usuário não está bloqueado', - 'block_code_email_changed' => 'O usuário ainda não confirmou o novo endereço de e-mail', - 'admin_update_email' => 'Ao contrário da página de perfil, o usuário NÃO será notificado de que seu endereço de e-mail mudou!', - 'update_user' => 'Atualizar usuário', - 'updated_user' => 'Os dados do usuário foram alterados.', - 'delete_user' => 'Excluir usuário :email', - 'user_deleted' => 'O usuário foi apagado', - 'send_test_email' => 'Enviar e-mail de teste', - 'send_test_email_text' => 'Para ver se a sua instalação é capaz de enviar e-mail, pressione este botão. Você não verá um erro aqui (se houver), os arquivos de log refletirão quaisquer erros. Você pode pressionar este botão quantas vezes quiser. Não há controle de spam. A mensagem será enviada para :email e deverá chegar em breve.', - 'send_message' => 'Enviar mensagem', - 'send_test_triggered' => 'O teste foi desencadeado. Verifique a sua caixa de entrada e os arquivos de log.', + 'administration' => 'Administração', + 'user_administration' => 'Administração de usuários', + 'list_all_users' => 'Todos os usuários', + 'all_users' => 'Todos os usuários', + 'instance_configuration' => 'Configuração', + 'firefly_instance_configuration' => 'Opções de configuração para Firefly III', + 'setting_single_user_mode' => 'Modo de usuário único', + 'setting_single_user_mode_explain' => 'Por padrão, o Firefly III aceita apenas um (1) usuário registrado: você. Esta é uma medida de segurança para impedir que outros usem sua instalação a menos que você os permita. Os registrors futuros estão bloqueados. Quando você desmarca esta opção, outros podem usar sua instalação se puderem alcançá-la (quando ela está conectada à Internet).', + 'store_configuration' => 'Configuração da Loja', + 'single_user_administration' => 'Administração de usuários para :email', + 'edit_user' => 'Editar usuário :email', + 'hidden_fields_preferences' => 'Você pode habilitar mais opções de transações nas suas configurações.', + 'user_data_information' => 'Dados de usuário', + 'user_information' => 'Informações do usuário', + 'total_size' => 'tamanho total', + 'budget_or_budgets' => 'orçamento(s)', + 'budgets_with_limits' => 'budget(s) com quantidade configurada', + 'nr_of_rules_in_total_groups' => ':count_rules regra (s) em :count_groups grupo(s) de regras', + 'tag_or_tags' => 'tag(s)', + 'configuration_updated' => 'A configuração foi atualizada', + 'setting_is_demo_site' => 'Site demo', + 'setting_is_demo_site_explain' => 'Se você marcar esta caixa, esta instalação se comportará como se fosse o site de demonstração, o que pode ter efeitos colaterais estranhos.', + 'block_code_bounced' => 'Mensagem(s) de email ressaltada', + 'block_code_expired' => 'Conta de demonstração expirada', + 'no_block_code' => 'Nenhuma razão para o bloqueio ou o usuário não está bloqueado', + 'block_code_email_changed' => 'O usuário ainda não confirmou o novo endereço de e-mail', + 'admin_update_email' => 'Ao contrário da página de perfil, o usuário NÃO será notificado de que seu endereço de e-mail mudou!', + 'update_user' => 'Atualizar usuário', + 'updated_user' => 'Os dados do usuário foram alterados.', + 'delete_user' => 'Excluir usuário :email', + 'user_deleted' => 'O usuário foi apagado', + 'send_test_email' => 'Enviar e-mail de teste', + 'send_test_email_text' => 'Para ver se a sua instalação é capaz de enviar e-mail, pressione este botão. Você não verá um erro aqui (se houver), os arquivos de log refletirão quaisquer erros. Você pode pressionar este botão quantas vezes quiser. Não há controle de spam. A mensagem será enviada para :email e deverá chegar em breve.', + 'send_message' => 'Enviar mensagem', + 'send_test_triggered' => 'O teste foi desencadeado. Verifique a sua caixa de entrada e os arquivos de log.', + + 'split_transaction_title' => 'Descrição da transação dividida', + 'split_title_help' => 'Se você criar uma transação dividida, é necessário haver uma descrição global para todas as partes da transação.', + 'transaction_information' => 'Informação de transação', + 'you_create_transfer' => 'Você está criando uma transferência.', + 'you_create_withdrawal' => 'Você está criando uma retirada.', + 'you_create_deposit' => 'Você está criando um depósito.', + // links 'journal_link_configuration' => 'Configuração de ligação de transações', @@ -1214,12 +1226,13 @@ return [ 'do_not_save_connection' => '(não salve a conexão)', 'link_transaction' => 'Ligar transação', 'link_to_other_transaction' => 'Ligue esta transação a outra transação', - 'select_transaction_to_link' => 'Selecione uma transação para ligar a esta transação', + 'select_transaction_to_link' => 'Selecione uma transação para vincular esta transação. Atualmente, os links não são usados no Firefly III (além de serem exibidos), mas planejo mudar isso no futuro. Use a caixa de pesquisa para selecionar uma transação por título ou por ID. Se você quiser adicionar tipos de link personalizados, confira a seção de administração.', 'this_transaction' => 'Esta transação', 'transaction' => 'Transação', 'comments' => 'Comentários', - 'to_link_not_found' => 'Se a transação para a qual deseja ligar não estiver listada, basta inserir sua ID.', + 'link_notes' => 'Quaisquer notas que você deseja armazenar com o link.', 'invalid_link_selection' => 'Não é possível ligar essas transações', + 'selected_transaction' => 'Transação selecionada', 'journals_linked' => 'As transações estão ligadas.', 'journals_error_linked' => 'Essas transações já estão ligadas.', 'journals_link_to_self' => 'Você não pode vincular uma transação a ela mesma', @@ -1258,12 +1271,11 @@ return [ 'split_this_withdrawal' => 'Dividir esta retirada', 'split_this_deposit' => 'Dividir este depósito', 'split_this_transfer' => 'Dividir essa transferência', - 'cannot_edit_multiple_source' => 'Você não pode editar transações parceladas #:id com a descrição ":description" porque ele contém várias contas de origem.', - 'cannot_edit_multiple_dest' => 'Você não pode editar transações parceladas #:id com a descrição ":description" porque ele contém várias contas de destino.', - 'cannot_edit_reconciled' => 'Você não pode editar o #:id da transação com a descrição ":description" porque foi marcado como reconciliado.', 'cannot_edit_opening_balance' => 'Não é possível editar o saldo de abertura de uma conta.', 'no_edit_multiple_left' => 'Você não selecionou nenhuma transação válida para editar.', - 'cannot_convert_split_journal' => 'Não é possível converter uma transação dividida', + 'breadcrumb_convert_group' => 'Converter transação', + 'convert_invalid_source' => 'As informações de origem são inválidas para transações #%d.', + 'convert_invalid_destination' => 'As informações de destino são inválidas para transações #%d.', // Import page (general strings only) 'import_index_title' => 'Importar transações para o Firefly III', @@ -1389,4 +1401,15 @@ return [ 'will_jump_monday' => 'Será criada na segunda-feira em vez de nos finais de semana.', 'except_weekends' => 'Exceto nos fins de semana', 'recurrence_deleted' => 'Transação recorrente ":title" apagada', + + // new lines for summary controller. + 'box_balance_in_currency' => 'Saldo (:currency)', + 'box_spent_in_currency' => 'Gasto (:currency)', + 'box_earned_in_currency' => 'Ganho (:currency)', + 'box_bill_paid_in_currency' => 'Faturas pagas (:currency)', + 'box_bill_unpaid_in_currency' => 'Faturas não pagas (:currency)', + 'box_left_to_spend_in_currency' => 'Valor para gastar (:currency)', + 'box_net_worth_in_currency' => 'Valor líquido (:currency)', + 'box_spend_per_day' => 'Restante para gastar por dia: :amount', + ]; diff --git a/resources/lang/pt_BR/form.php b/resources/lang/pt_BR/form.php index 84948ee347..1e7a95f9aa 100644 --- a/resources/lang/pt_BR/form.php +++ b/resources/lang/pt_BR/form.php @@ -57,20 +57,21 @@ return [ 'asset_source_account' => 'Conta de origem', 'journal_description' => 'Descrição', 'note' => 'Notas', + 'store_new_transaction' => 'Armazenar uma nova transação', 'split_journal' => 'Dividir essa transação', 'split_journal_explanation' => 'Dividir essa transação em várias partes', 'currency' => 'Moeda', 'account_id' => 'Conta de ativo', 'budget_id' => 'Orçamento', - 'openingBalance' => 'Saldo inicial', + 'opening_balance' => 'Saldo inicial', 'tagMode' => 'Modo de tag', 'tag_position' => 'Localização do indexador', - 'virtualBalance' => 'Saldo virtual', + 'virtual_balance' => 'Saldo virtual', 'targetamount' => 'Valor alvo', - 'accountRole' => 'Tipo de conta', - 'openingBalanceDate' => 'Data do Saldo inicial', - 'ccType' => 'Plano de pagamento do Cartão de Crédito', - 'ccMonthlyPaymentDate' => 'Data do pagamento mensal do Cartão de Crédito', + 'account_role' => 'Função de conta', + 'opening_balance_date' => 'Data do saldo inicial', + 'cc_type' => 'Plano de pagamento do Cartão de Crédito', + 'cc_monthly_payment_date' => 'Data do pagamento mensal do Cartão de Crédito', 'piggy_bank_id' => 'Cofrinho', 'returnHere' => 'Retornar aqui', 'returnHereExplanation' => 'Depois de armazenar, retorne aqui para criar outro.', @@ -118,7 +119,7 @@ return [ 'symbol' => 'Símbolo', 'code' => 'Código', 'iban' => 'IBAN', - 'accountNumber' => 'Número de conta', + 'account_number' => 'Número de conta', 'creditCardNumber' => 'Número do cartão de crédito', 'has_headers' => 'Cabeçalhos', 'date_format' => 'Formato da Data', @@ -139,12 +140,8 @@ return [ 'stop_processing' => 'Parar processamento', 'start_date' => 'Início do intervalo', 'end_date' => 'Final do intervalo', - 'export_start_range' => 'Início do intervalo de exportação', - 'export_end_range' => 'Fim do intervalo de exportação', - 'export_format' => 'Formato do arquivo', 'include_attachments' => 'Incluir anexos enviados', 'include_old_uploads' => 'Incluir dados importados', - 'accounts' => 'Exportar transações destas contas', 'delete_account' => 'Apagar conta ":name"', 'delete_bill' => 'Apagar fatura ":name"', 'delete_budget' => 'Excluir o orçamento ":name"', @@ -256,4 +253,7 @@ return [ 'weekend' => 'Fim de Semana', 'client_secret' => 'Chave secreta', + 'withdrawal_destination_id' => 'Conta de destino', + 'deposit_source_id' => 'Conta de origem', + ]; diff --git a/resources/lang/pt_BR/import.php b/resources/lang/pt_BR/import.php index 27ef3682b9..a6aeaa3ed0 100644 --- a/resources/lang/pt_BR/import.php +++ b/resources/lang/pt_BR/import.php @@ -50,116 +50,116 @@ return [ // prerequisites box (index) 'need_prereq_title' => 'Pré-requisitos de importação', - 'need_prereq_intro' => 'Alguns metodos de importacao necessitam da tua atencao antes que possam ser usados. Por exemplo, eles podem necessitar de uma chave especial da API. Podes configurar tudo aqui. O icon indica se esses pre-requesitos foram cumpridos.', - 'do_prereq_fake' => 'Pre-requesitos para o provedor ficticio', - 'do_prereq_file' => 'Pre-requesitos para a importacao de ficheiros', - 'do_prereq_bunq' => 'Pre-requesitos para a importacao do bunq', - 'do_prereq_spectre' => 'Pre-requesitos para a importacao do Spectre', - 'do_prereq_plaid' => 'Pre-requesitos para a importacao do Plaid', - 'do_prereq_yodlee' => 'Pre-requesitos para a importacao do Yodlee', - 'do_prereq_quovo' => 'Pre-requesitos para a importacao do Quovo', - 'do_prereq_ynab' => 'Pre-requesitos para a importacao do YNAB', + 'need_prereq_intro' => 'Alguns métodos de importação precisam da sua atenção antes de serem utilizados. Por exemplo, esses métodos podem necessitar uma chave de API especial ou chaves de aplicação. Você pode configurá-los aqui. O ícone indica se os pré-requisitos foram atendidos.', + 'do_prereq_fake' => 'Pre-requisitos para o fornecedor falso', + 'do_prereq_file' => 'Pré-requisitos para a importação por arquivo', + 'do_prereq_bunq' => 'Pré-requisitos para a importação usando bunq', + 'do_prereq_spectre' => 'Pré-requisitos para a importação usando Spectre', + 'do_prereq_plaid' => 'Pré-requisitos para a importação usando Plaid', + 'do_prereq_yodlee' => 'Pré-requisitos para a importação usando Yodlee', + 'do_prereq_quovo' => 'Pré-requisitos para a importação usando Quovo', + 'do_prereq_ynab' => 'Pré-requisitos para a importação usando YNAB', // prerequisites: - 'prereq_fake_title' => 'Configuracao para a importacao pelo provedor de importacao ficticio', - 'prereq_fake_text' => 'Este provedor ficticio necessita de uma chave API ficticia. Ela tem de conter 32 caracteres de comprimento. Podes usar esta: 123456789012345678901234567890AA', + 'prereq_fake_title' => 'Pré-requisitos para a importação usando um fornecedor falso', + 'prereq_fake_text' => 'Este fornecedor falso necessita de uma falsa API key de 32 caracteres. Você pode usar este: 123456789012345678901234567890AA', 'prereq_spectre_title' => 'Pre-requesitos para uma importacao pela API do Spectre', - 'prereq_spectre_text' => 'Para importar dados usando a API do Spectre (v4), deves fornecer ao Firefly III 2 codigos secretos. Eles podem ser encontrados na seguinte pagina.', - 'prereq_spectre_pub' => 'Da mesma forma, a API do Spectre necessita de saber a chave publica que ves em baixo. Sem ela, nao te vai reconhecer. Por favor introduz esta chave publica na seguinte pagina.', - 'prereq_bunq_title' => 'Pre-requesitos para uma importaca pelo bunq', - 'prereq_bunq_text' => 'Para importar do bunq, precisas de obter uma chave da API. Podes fazer isso atraves da app. De notar que a importacao do bunq este em fase BETA. Ela so tem vindo a ser testada na API sandbox.', - 'prereq_bunq_ip' => 'o bunq necessita do teu endereco IP externo. O Firefly III tentou preencher esse valor usando o servico ipify. Verifica que este endereco de IP e correcto, ou a importacao vai falhar.', - 'prereq_ynab_title' => 'Pre-requesitos para uma importacao de YNAB', - 'prereq_ynab_text' => 'Para poderes descarregar as transaccoes do YNAB, por favor cria uma nova aplicacao na tua Pagina de Configuracoes de Desenvolvedor e introduz o ID e a senha de cliente nesta pagina.', - 'prereq_ynab_redirect' => 'Para completar a configuracao, introduz o seguinte URL na Pagina de Configuracoes de Desenvolvedor, sobre a area de "Redirect URI(s)".', - 'callback_not_tls' => 'Firefly III has detected the following callback URI. It seems your server is not set up to accept TLS-connections (https). YNAB will not accept this URI. You may continue with the import (because Firefly III could be wrong) but please keep this in mind.', + 'prereq_spectre_text' => 'Para importar dados usando a API do Spectre (v4), você deve fornecer ao Firefly III dois códigos secretos. Eles podem ser encontrados na pagina de segredos.', + 'prereq_spectre_pub' => 'Da mesma forma, a API do Spectre precisa saber a chave pública abaixo. Sem ela, a API não vai reconhecê-lo. Por favor introduza esta chave pública na seguinte pagina.', + 'prereq_bunq_title' => 'Pré-requisitos para uma importação do bunq', + 'prereq_bunq_text' => 'Para importar do bunq, você precisa obter uma chave de API. Você pode fazer isso através do aplicativo. Por favor, note que a função de importação para bunq está em BETA. Foi testado apenas na API do sandbox.', + 'prereq_bunq_ip' => 'O bunq requer seu endereço IP externo. O Firefly III tentou preencher isso usando o serviço ipify. Certifique-se de que esse endereço IP esteja correto ou a importação falhará.', + 'prereq_ynab_title' => 'Pré-requisitos para uma importação de YNAB', + 'prereq_ynab_text' => 'Para poder fazer o download das transações do YNAB, crie um novo aplicativo em sua página de configurações do desenvolvedor e insira o ID do cliente e senha nesta página.', + 'prereq_ynab_redirect' => 'Para concluir a configuração, digite o seguinte URL na página de configurações do desenvolvedor sob "URI(s) de redirecionamento".', + 'callback_not_tls' => 'O Firefly III detectou o seguinte URI de retorno de chamada. Parece que seu servidor não está configurado para aceitar conexões TLS (https). O YNAB não aceitará este URI. Você pode continuar com a importação (porque o Firefly III pode estar errado), mas lembre-se disso.', // prerequisites success messages: - 'prerequisites_saved_for_fake' => 'Fake API key stored successfully!', - 'prerequisites_saved_for_spectre' => 'App ID and secret stored!', - 'prerequisites_saved_for_bunq' => 'API key and IP stored!', - 'prerequisites_saved_for_ynab' => 'YNAB client ID and secret stored!', + 'prerequisites_saved_for_fake' => 'API Key falsa armazenada com sucesso!', + 'prerequisites_saved_for_spectre' => 'App ID e segredo armazenados!', + 'prerequisites_saved_for_bunq' => 'Chave de API e IP armazenados!', + 'prerequisites_saved_for_ynab' => 'ID do cliente YNAB e segredo armazenados!', // job configuration: - 'job_config_apply_rules_title' => 'Job configuration - apply your rules?', - 'job_config_apply_rules_text' => 'Once the fake provider has run, your rules can be applied to the transactions. This adds time to the import.', - 'job_config_input' => 'O teu input', + 'job_config_apply_rules_title' => 'Configuração de tarefa - aplicar suas regras?', + 'job_config_apply_rules_text' => 'Uma vez que o provedor falso executar, suas regras podem ser aplicadas às transações. Isso aumenta o tempo da importação.', + 'job_config_input' => 'Sua entrada', // job configuration for the fake provider: - 'job_config_fake_artist_title' => 'Introduz o nome do album', - 'job_config_fake_artist_text' => 'Many import routines have a few configuration steps you must go through. In the case of the fake import provider, you must answer some weird questions. In this case, enter "David Bowie" to continue.', - 'job_config_fake_song_title' => 'Introduz o nome da musica', - 'job_config_fake_song_text' => 'Mention the song "Golden years" to continue with the fake import.', - 'job_config_fake_album_title' => 'Introduz o nome do album', - 'job_config_fake_album_text' => 'Some import routines require extra data halfway through the import. In the case of the fake import provider, you must answer some weird questions. Enter "Station to station" to continue.', + 'job_config_fake_artist_title' => 'Digite o nome do álbum', + 'job_config_fake_artist_text' => 'Muitas rotinas de importação têm algumas etapas de configuração pelas quais você deve passar. No caso do provedor de importação falso, você deve responder a algumas perguntas estranhas. Neste caso, digite "David Bowie" para continuar.', + 'job_config_fake_song_title' => 'Digite o nome da música', + 'job_config_fake_song_text' => 'Mencione a música "Golden years" para continuar com a importação falsa.', + 'job_config_fake_album_title' => 'Digite o nome do álbum', + 'job_config_fake_album_text' => 'Algumas rotinas de importação exigem dados extras na metade da importação. No caso do provedor de importação falso, você deve responder a algumas perguntas estranhas. Digite "Station to station" para continuar.', // job configuration form the file provider - 'job_config_file_upload_title' => 'Import setup (1/4) - Upload your file', - 'job_config_file_upload_text' => 'This routine will help you import files from your bank into Firefly III. ', - 'job_config_file_upload_help' => 'Select your file. Please make sure the file is UTF-8 encoded.', - 'job_config_file_upload_config_help' => 'If you have previously imported data into Firefly III, you may have a configuration file, which will pre-set configuration values for you. For some banks, other users have kindly provided their configuration file', - 'job_config_file_upload_type_help' => 'Select the type of file you will upload', - 'job_config_file_upload_submit' => 'Carregar ficheiros', + 'job_config_file_upload_title' => 'Importar configuração (1/4) - Carregar seu arquivo', + 'job_config_file_upload_text' => 'Essa rotina ajudará você a importar arquivos do seu banco para o Firefly III. ', + 'job_config_file_upload_help' => 'Selecione seu arquivo. Por favor, verifique se o arquivo é codificado em UTF-8.', + 'job_config_file_upload_config_help' => 'Se você já importou dados para o Firefly III, você pode ter um arquivo de configuração, que irá pré-configurar valores para você. Para alguns bancos, outros usuários gentilmente forneceram seu arquivo de configuração', + 'job_config_file_upload_type_help' => 'Selecione o tipo de arquivo que você fará o upload', + 'job_config_file_upload_submit' => 'Anexar arquivos', 'import_file_type_csv' => 'CSV (valores separados por vírgula)', 'import_file_type_ofx' => 'OFX', - 'file_not_utf8' => 'The file you have uploaded is not encoded as UTF-8 or ASCII. Firefly III cannot handle such files. Please use Notepad++ or Sublime to convert your file to UTF-8.', - 'job_config_uc_title' => 'Import setup (2/4) - Basic file setup', - 'job_config_uc_text' => 'To be able to import your file correctly, please validate the options below.', - 'job_config_uc_header_help' => 'Check this box if the first row of your CSV file are the column titles.', - 'job_config_uc_date_help' => 'Date time format in your file. Follow the format as this page indicates. The default value will parse dates that look like this: :dateExample.', - 'job_config_uc_delimiter_help' => 'Choose the field delimiter that is used in your input file. If not sure, comma is the safest option.', - 'job_config_uc_account_help' => 'If your file does NOT contain information about your asset account(s), use this dropdown to select to which account the transactions in the file belong to.', + 'file_not_utf8' => 'O arquivo que você enviou não está codificado como UTF-8 ou ASCII. O Firefly III não pode lidar com esses arquivos. Por favor, use o Notepad++ ou Sublime para converter seu arquivo em UTF-8.', + 'job_config_uc_title' => 'Importar configuração (2/4) - configuração básica do arquivo', + 'job_config_uc_text' => 'Para ser capaz de importar o arquivo corretamente, por favor valide as opções abaixo.', + 'job_config_uc_header_help' => 'Marque esta caixa se a primeira linha do seu arquivo CSV for os títulos das colunas.', + 'job_config_uc_date_help' => 'Formato de data e hora no seu arquivo. Siga o formato como esta página indica. O valor padrão analisará datas semelhantes a esta: :dateExample.', + 'job_config_uc_delimiter_help' => 'Escolha o delimitador de campo que é usado em seu arquivo de entrada. Se não tiver certeza, a vírgula é a opção mais segura.', + 'job_config_uc_account_help' => 'Se o seu arquivo NÃO contiver informações sobre sua(s) conta(s) de ativos, use esta lista suspensa para selecionar a qual conta as transações no arquivo pertencem.', 'job_config_uc_apply_rules_title' => 'Aplicar regras', - 'job_config_uc_apply_rules_text' => 'Applies your rules to every imported transaction. Note that this slows the import significantly.', - 'job_config_uc_specifics_title' => 'Bank-specific options', - 'job_config_uc_specifics_txt' => 'Some banks deliver badly formatted files. Firefly III can fix those automatically. If your bank delivers such files but it\'s not listed here, please open an issue on GitHub.', + 'job_config_uc_apply_rules_text' => 'Aplica suas regras a todas as transações importadas. Note que isto diminui significativamente a importação.', + 'job_config_uc_specifics_title' => 'Opções específicas do banco', + 'job_config_uc_specifics_txt' => 'Alguns bancos entregam arquivos mal formatados. O Firefly III pode consertar esses arquivos automaticamente. Se seu banco entregar esses arquivos, mas não está listado aqui, por favor, abra uma issue no GitHub.', 'job_config_uc_submit' => 'Continuar', - 'invalid_import_account' => 'You have selected an invalid account to import into.', - 'import_liability_select' => 'Responsabilidade', + 'invalid_import_account' => 'Você selecionou uma conta inválida para importar.', + 'import_liability_select' => 'Passivo', // job configuration for Spectre: - 'job_config_spectre_login_title' => 'Escolhe o teu login', - 'job_config_spectre_login_text' => 'Firefly III has found :count existing login(s) in your Spectre account. Which one would you like to use to import from?', - 'spectre_login_status_active' => 'Activo', - 'spectre_login_status_inactive' => 'Inactivo', + 'job_config_spectre_login_title' => 'Escolha seu login', + 'job_config_spectre_login_text' => 'Firefly III encontrou :count login(s) existente(s) na sua conta Spectre. De qual você gostaria de usar para importar?', + 'spectre_login_status_active' => 'Ativo', + 'spectre_login_status_inactive' => 'Inativo', 'spectre_login_status_disabled' => 'Desabilitado', - 'spectre_login_new_login' => 'Login with another bank, or one of these banks with different credentials.', - 'job_config_spectre_accounts_title' => 'Seleccionar as contas de onde vai importar', - 'job_config_spectre_accounts_text' => 'You have selected ":name" (:country). You have :count account(s) available from this provider. Please select the Firefly III asset account(s) where the transactions from these accounts should be stored. Remember, in order to import data both the Firefly III account and the ":name"-account must have the same currency.', - 'spectre_do_not_import' => '(nao importar)', - 'spectre_no_mapping' => 'It seems you have not selected any accounts to import from.', + 'spectre_login_new_login' => 'Faça o login com outro banco, ou com um desses bancos com credenciais diferentes.', + 'job_config_spectre_accounts_title' => 'Selecione as contas a serem importadas', + 'job_config_spectre_accounts_text' => 'Você selecionou ":name" (:country). Você tem :count conta(s) disponível deste provedor. Por favor, selecione a conta de ativo(s) Firefly III onde as transações destas contas devem ser armazenadas. Lembre-se, para importar dados tanto da conta Firefly III como da ":name" devem ter a mesma moeda.', + 'spectre_do_not_import' => '(não importar)', + 'spectre_no_mapping' => 'Parece que você não selecionou nenhuma conta para importar.', 'imported_from_account' => 'Importado de ":account"', 'spectre_account_with_number' => 'Conta :number', 'job_config_spectre_apply_rules' => 'Aplicar regras', - 'job_config_spectre_apply_rules_text' => 'By default, your rules will be applied to the transactions created during this import routine. If you do not want this to happen, deselect this checkbox.', + 'job_config_spectre_apply_rules_text' => 'Por padrão, suas regras serão aplicadas às transações criadas durante esta rotina de importação. Se você não quer que isso aconteça, desmarque esta caixa de seleção.', // job configuration for bunq: 'job_config_bunq_accounts_title' => 'contas bunq', - 'job_config_bunq_accounts_text' => 'These are the accounts associated with your bunq account. Please select the accounts from which you want to import, and in which account the transactions must be imported.', - 'bunq_no_mapping' => 'It seems you have not selected any accounts.', - 'should_download_config' => 'You should download the configuration file for this job. This will make future imports way easier.', - 'share_config_file' => 'If you have imported data from a public bank, you should share your configuration file so it will be easy for other users to import their data. Sharing your configuration file will not expose your financial details.', + 'job_config_bunq_accounts_text' => 'Estas são as contas associadas à sua conta bunq. Por favor, selecione as contas das quais você deseja importar e em qual conta as transações devem ser importadas.', + 'bunq_no_mapping' => 'Parece que você não selecionou nenhuma conta.', + 'should_download_config' => 'Você deve baixar o arquivo de configuração para esta tarefa. Isso facilitará as futuras importações.', + 'share_config_file' => 'Se você importou dados de um banco público, você deve compartilhar seu arquivo de configuração para que seja fácil outros usuários importem seus dados. Compartilhar seu arquivo de configuração não exporá seus detalhes financeiros.', 'job_config_bunq_apply_rules' => 'Aplicar regras', - 'job_config_bunq_apply_rules_text' => 'By default, your rules will be applied to the transactions created during this import routine. If you do not want this to happen, deselect this checkbox.', - 'bunq_savings_goal' => 'Savings goal: :amount (:percentage%)', - 'bunq_account_status_CANCELLED' => 'Closed bunq account', + 'job_config_bunq_apply_rules_text' => 'Por padrão, suas regras serão aplicadas às transações criadas durante esta rotina de importação. Se você não quer que isso aconteça, desmarque esta caixa de seleção.', + 'bunq_savings_goal' => 'Meta de poupança: :amount (:percentage%)', + 'bunq_account_status_CANCELLED' => 'Contas bunq fechadas', 'ynab_account_closed' => 'Conta fechada!', 'ynab_account_deleted' => 'Conta excluída!', 'ynab_account_type_savings' => 'conta poupança', - 'ynab_account_type_checking' => 'checking account', + 'ynab_account_type_checking' => 'conta corrente', 'ynab_account_type_cash' => 'conta de dinheiro', - 'ynab_account_type_creditCard' => 'cartao de credito', - 'ynab_account_type_lineOfCredit' => 'linha de credito', - 'ynab_account_type_otherAsset' => 'outra conta de activos', - 'ynab_account_type_otherLiability' => 'outras responsabilidades', + 'ynab_account_type_creditCard' => 'cartão de crédito', + 'ynab_account_type_lineOfCredit' => 'linha de crédito', + 'ynab_account_type_otherAsset' => 'outra conta de ativos', + 'ynab_account_type_otherLiability' => 'outros passivos', 'ynab_account_type_payPal' => 'Paypal', 'ynab_account_type_merchantAccount' => 'conta de comerciante', - 'ynab_account_type_investmentAccount' => 'conta de investimentos', + 'ynab_account_type_investmentAccount' => 'conta de investimento', 'ynab_account_type_mortgage' => 'hipoteca', - 'ynab_do_not_import' => '(nao importar)', + 'ynab_do_not_import' => '(não importar)', 'job_config_ynab_apply_rules' => 'Aplicar regras', - 'job_config_ynab_apply_rules_text' => 'By default, your rules will be applied to the transactions created during this import routine. If you do not want this to happen, deselect this checkbox.', + 'job_config_ynab_apply_rules_text' => 'Por padrão, suas regras serão aplicadas às transações criadas durante esta rotina de importação. Se você não quer que isso aconteça, desmarque esta caixa de seleção.', // job configuration for YNAB: - 'job_config_ynab_select_budgets' => 'Seleccionar o teu orcamento', - 'job_config_ynab_select_budgets_text' => 'You have :count budgets stored at YNAB. Please select the one from which Firefly III will import the transactions.', + 'job_config_ynab_select_budgets' => 'Selecione seu orçamento', + 'job_config_ynab_select_budgets_text' => 'Você tem :count orçamentos armazenados no YNAB. Por favor, selecione de qual o Firefly III irá importar as transações.', 'job_config_ynab_no_budgets' => 'There are no budgets available to be imported from.', 'ynab_no_mapping' => 'It seems you have not selected any accounts to import from.', 'job_config_ynab_bad_currency' => 'You cannot import from the following budget(s), because you do not have accounts with the same currency as these budgets.', @@ -170,8 +170,8 @@ return [ // keys from "extra" array: 'spectre_extra_key_iban' => 'IBAN', 'spectre_extra_key_swift' => 'SWIFT', - 'spectre_extra_key_status' => 'Estado', - 'spectre_extra_key_card_type' => 'Tipo de cartao', + 'spectre_extra_key_status' => 'Status', + 'spectre_extra_key_card_type' => 'Tipo de cartão', 'spectre_extra_key_account_name' => 'Nome da conta', 'spectre_extra_key_client_name' => 'Nome do cliente', 'spectre_extra_key_account_number' => 'Número da conta', @@ -181,12 +181,12 @@ return [ 'spectre_extra_key_interest_rate' => 'Taxa de juros', 'spectre_extra_key_expiry_date' => 'Data de vencimento', 'spectre_extra_key_open_date' => 'Data de abertura', - 'spectre_extra_key_current_time' => 'Hora actual', + 'spectre_extra_key_current_time' => 'Hora atual', 'spectre_extra_key_current_date' => 'Data actual', - 'spectre_extra_key_cards' => 'Cartoes', + 'spectre_extra_key_cards' => 'Cartões', 'spectre_extra_key_units' => 'Unidades', - 'spectre_extra_key_unit_price' => 'Preco unitario', - 'spectre_extra_key_transactions_count' => 'Contagem de transaccoes', + 'spectre_extra_key_unit_price' => 'Preço unitário', + 'spectre_extra_key_transactions_count' => 'Contagem de transações', //job configuration for finTS 'fints_connection_failed' => 'An error occurred while trying to connecting to your bank. Please make sure that all the data you entered is correct. Original error message: :originalError', @@ -207,6 +207,10 @@ return [ 'specific_rabo_descr' => 'Fixes potential problems with Rabobank files', 'specific_pres_name' => 'President\'s Choice Financial CA', 'specific_pres_descr' => 'Fixes potential problems with PC files', + 'specific_belfius_name' => 'Belfius BE', + 'specific_belfius_descr' => 'Fixes potential problems with Belfius files', + 'specific_ingbelgium_name' => 'ING BE', + 'specific_ingbelgium_descr' => 'Fixes potential problems with ING Belgium files', // job configuration for file provider (stage: roles) 'job_config_roles_title' => 'Import setup (3/4) - Define each column\'s role', 'job_config_roles_text' => 'Each column in your CSV file contains certain data. Please indicate what kind of data the importer should expect. The option to "map" data means that you will link each entry found in the column to a value in your database. An often mapped column is the column that contains the IBAN of the opposing account. That can be easily matched to IBAN\'s present in your database already.', @@ -243,21 +247,21 @@ return [ 'status_finished_title' => 'Importacao terminada', 'status_finished_text' => 'A importacao foi terminada.', 'finished_with_errors' => 'There were some errors during the import. Please review them carefully.', - 'unknown_import_result' => 'Resultados da importacao desconhecidos', - 'result_no_transactions' => 'No transactions have been imported. Perhaps they were all duplicates is simply no transactions where present to be imported. Perhaps the log files can tell you what happened. If you import data regularly, this is normal.', - 'result_one_transaction' => 'Exactly one transaction has been imported. It is stored under tag :tag where you can inspect it further.', - 'result_many_transactions' => 'Firefly III has imported :count transactions. They are stored under tag :tag where you can inspect them further.', + 'unknown_import_result' => 'Resultados da importação desconhecidos', + 'result_no_transactions' => 'Nenhuma transação foi importada. Talvez fossem todos duplicados, simplesmente não há transações a serem importadas. Talvez os arquivos de log possam dizer o que aconteceu. Se você importar dados regularmente, isso é normal.', + 'result_one_transaction' => 'Exatamente uma transação foi importada. Ela é armazenada sob a tag :tag onde você pode inspecioná-la ainda mais.', + 'result_many_transactions' => 'Firefly III importou :count transações. Elas são armazenados na tag :tag, onde você pode inspecioná-los ainda mais.', // general errors and warnings: - 'bad_job_status' => 'To access this page, your import job cannot have status ":status".', + 'bad_job_status' => 'Para acessar esta página, seu job de importação não pode ter status ":status".', // column roles for CSV import: 'column__ignore' => '(ignorar esta coluna)', 'column_account-iban' => 'Conta de Ativo (IBAN)', 'column_account-id' => 'ID da Conta de Ativo (correspondente FF3)', 'column_account-name' => 'Conta de Ativo (nome)', - 'column_account-bic' => 'Asset account (BIC)', + 'column_account-bic' => 'Conta de Ativo (IBAN)', 'column_amount' => 'Montante', 'column_amount_foreign' => 'Montante (em moeda estrangeira)', 'column_amount_debit' => 'Montante (coluna de débito)', @@ -290,15 +294,15 @@ return [ 'column_opposing-name' => 'Conta contrária (nome)', 'column_rabo-debit-credit' => 'Indicador de débito/crédito específico do Rabobank', 'column_ing-debit-credit' => 'Indicador de débito/crédito específico do ING', - 'column_generic-debit-credit' => 'Generic bank debit/credit indicator', - 'column_sepa-ct-id' => 'SEPA identificador end-to-end', - 'column_sepa-ct-op' => 'SEPA Identificador de conta de contrária', - 'column_sepa-db' => 'SEPA Identificador de Mandato', - 'column_sepa-cc' => 'SEPA Código de Compensação', - 'column_sepa-ci' => 'SEPA Identificador Credor', - 'column_sepa-ep' => 'SEPA Finalidade Externa', - 'column_sepa-country' => 'SEPA Código do País', - 'column_sepa-batch-id' => 'SEPA Batch ID', + 'column_generic-debit-credit' => 'Indicador de débito/crédito bancário genérico', + 'column_sepa_ct_id' => 'Identificador final do SEPA', + 'column_sepa_ct_op' => 'Identificador de conta de oposição SEPA', + 'column_sepa_db' => 'Identificador obrigatório SEPA', + 'column_sepa_cc' => 'Codigo de Compensação SEPA', + 'column_sepa_ci' => 'Identificador de Crédito SEPA', + 'column_sepa_ep' => 'Finalidade externa SEPA', + 'column_sepa_country' => 'Código do País SEPA', + 'column_sepa_batch_id' => 'ID do Lote SEPA', 'column_tags-comma' => 'Tags (separadas por vírgula)', 'column_tags-space' => 'Tags (separadas por espaço)', 'column_account-number' => 'Conta de ativo (número da conta)', @@ -306,4 +310,7 @@ return [ 'column_note' => 'Nota(s)', 'column_internal-reference' => 'Referência interna', + // error message + 'duplicate_row' => 'Linha #:row (":description") não pôde ser importada. Ela já existe.', + ]; diff --git a/resources/lang/pt_BR/intro.php b/resources/lang/pt_BR/intro.php index db8d46c81d..8c6c9747d4 100644 --- a/resources/lang/pt_BR/intro.php +++ b/resources/lang/pt_BR/intro.php @@ -24,39 +24,61 @@ declare(strict_types=1); return [ // index - 'index_intro' => 'Bem-vindo à página de inicial do Firefly III. Por favor, aproveite esta introdução para verificar como funciona o Firefly III.', - 'index_accounts-chart' => 'Este gráfico mostra o saldo atual de suas contas de ativos. Você pode selecionar as contas visíveis aqui nas suas preferências.', - 'index_box_out_holder' => 'Esta pequena caixa e as caixas próximas a esta lhe darão uma rápida visão geral de sua situação financeira.', - 'index_help' => 'Se você precisar de ajuda com uma página ou um formulário, pressione este botão.', - 'index_outro' => 'A maioria das páginas do Firefly III começará com uma pequena turnê como esta. Entre em contato comigo quando tiver dúvidas ou comentários. Vamos lá!', - 'index_sidebar-toggle' => 'Para criar novas transações, contas ou outras coisas, use o menu abaixo deste ícone.', + 'index_intro' => 'Bem-vindo à página de inicial do Firefly III. Por favor, aproveite esta introdução para verificar como funciona o Firefly III.', + 'index_accounts-chart' => 'Este gráfico mostra o saldo atual de suas contas de ativos. Você pode selecionar as contas visíveis aqui nas suas preferências.', + 'index_box_out_holder' => 'Esta pequena caixa e as caixas próximas a esta lhe darão uma rápida visão geral de sua situação financeira.', + 'index_help' => 'Se você precisar de ajuda com uma página ou um formulário, pressione este botão.', + 'index_outro' => 'A maioria das páginas do Firefly III começará com uma pequena turnê como esta. Entre em contato comigo quando tiver dúvidas ou comentários. Vamos lá!', + 'index_sidebar-toggle' => 'Para criar novas transações, contas ou outras coisas, use o menu abaixo deste ícone.', + 'index_cash_account' => 'Estas são as contas criadas até agora. Você pode usar a conta de caixa para rastrear as despesas de caixa, mas não é obrigatório, claro.', + + // transactions (withdrawal) + 'transactions_create_withdrawal_source' => 'Selecione sua conta favorita ou passivo deste dropdown.', + 'transactions_create_withdrawal_destination' => 'Selecione uma conta de despesas aqui. Deixe em branco se você quiser fazer uma despesa em dinheiro.', + 'transactions_create_withdrawal_foreign_currency' => 'Use este campo para definir uma moeda estrangeira e quantia.', + 'transactions_create_withdrawal_more_meta' => 'Muitos outros metadados que você definiu nesses campos.', + 'transactions_create_withdrawal_split_add' => 'Se você quiser dividir uma transação, adicione mais divisões com este botão', + + // transactions (deposit) + 'transactions_create_deposit_source' => 'Selecione ou digite o beneficiário neste/a dropdown/caixa de texto de preenchimento automático. Deixe em branco se você quiser fazer um depósito em dinheiro.', + 'transactions_create_deposit_destination' => 'Selecione uma conta de ativo ou passivo aqui.', + 'transactions_create_deposit_foreign_currency' => 'Use este campo para definir uma moeda estrangeira e quantia.', + 'transactions_create_deposit_more_meta' => 'Muitos outros metadados que você definiu nesses campos.', + 'transactions_create_deposit_split_add' => 'Se você quiser dividir uma transação, adicione mais divisões com este botão', + + // transactions (transfer) + 'transactions_create_transfer_source' => 'Selecione a conta do ativo de origem aqui.', + 'transactions_create_transfer_destination' => 'Selecione a conta do ativo de destino aqui.', + 'transactions_create_transfer_foreign_currency' => 'Use este campo para definir uma moeda estrangeira e quantia.', + 'transactions_create_transfer_more_meta' => 'Muitos outros metadados que você definiu nesses campos.', + 'transactions_create_transfer_split_add' => 'Se você quiser dividir uma transação, adicione mais divisões com este botão', // create account: - 'accounts_create_iban' => 'Dê a suas contas um IBAN válido. Isso poderá tornar a importação de dados muito fácil no futuro.', - 'accounts_create_asset_opening_balance' => 'As contas de bens podem ter um "saldo de abertura", indicando o início do histórico desta conta no Firefly III.', - 'accounts_create_asset_currency' => 'Firefly III suporta múltiplas moedas. As contas de ativos têm uma moeda principal, que você deve definir aqui.', - 'accounts_create_asset_virtual' => 'Às vezes, ajuda a dar à sua conta um saldo virtual: um valor extra sempre adicionado ou removido do saldo real.', + 'accounts_create_iban' => 'Dê a suas contas um IBAN válido. Isso poderá tornar a importação de dados muito fácil no futuro.', + 'accounts_create_asset_opening_balance' => 'As contas de bens podem ter um "saldo de abertura", indicando o início do histórico desta conta no Firefly III.', + 'accounts_create_asset_currency' => 'Firefly III suporta múltiplas moedas. As contas de ativos têm uma moeda principal, que você deve definir aqui.', + 'accounts_create_asset_virtual' => 'Às vezes, ajuda a dar à sua conta um saldo virtual: um valor extra sempre adicionado ou removido do saldo real.', // budgets index - 'budgets_index_intro' => 'Os orçamentos são usados ​​para gerenciar suas finanças e formar uma das principais funções do Firefly III.', - 'budgets_index_set_budget' => 'Defina seu orçamento total para todos os períodos, de modo que o Firefly III possa lhe dizer se você orçou todo o dinheiro disponível.', - 'budgets_index_see_expenses_bar' => 'Gastar dinheiro vai preencher lentamente esta barra.', - 'budgets_index_navigate_periods' => 'Navegue por períodos para definir os orçamentos facilmente antes do tempo.', - 'budgets_index_new_budget' => 'Crie novos orçamentos conforme for entendendo o programa.', - 'budgets_index_list_of_budgets' => 'Use esta tabela para definir os montantes para cada orçamento e veja como você está fazendo.', - 'budgets_index_outro' => 'Para saber mais sobre orçamentação, clique no ícone de ajuda no canto superior direito.', + 'budgets_index_intro' => 'Os orçamentos são usados ​​para gerenciar suas finanças e formar uma das principais funções do Firefly III.', + 'budgets_index_set_budget' => 'Defina seu orçamento total para todos os períodos, de modo que o Firefly III possa lhe dizer se você orçou todo o dinheiro disponível.', + 'budgets_index_see_expenses_bar' => 'Gastar dinheiro vai preencher lentamente esta barra.', + 'budgets_index_navigate_periods' => 'Navegue por períodos para definir os orçamentos facilmente antes do tempo.', + 'budgets_index_new_budget' => 'Crie novos orçamentos conforme for entendendo o programa.', + 'budgets_index_list_of_budgets' => 'Use esta tabela para definir os montantes para cada orçamento e veja como você está fazendo.', + 'budgets_index_outro' => 'Para saber mais sobre orçamentação, clique no ícone de ajuda no canto superior direito.', // reports (index) - 'reports_index_intro' => 'Use esses relatórios para obter informações detalhadas sobre suas finanças.', - 'reports_index_inputReportType' => 'Escolha um tipo de relatório. Confira as páginas de ajuda para ver o que cada relatório mostra.', - 'reports_index_inputAccountsSelect' => 'Você pode excluir ou incluir contas de ativos de acordo com a sua demanda.', - 'reports_index_inputDateRange' => 'O intervalo de datas selecionado depende inteiramente de você: de um dia a 10 anos.', - 'reports_index_extra-options-box' => 'Dependendo do relatório que você selecionou, você pode usar filtros e opções adicionais aqui. Observe esta caixa quando você altera os tipos de relatórios.', + 'reports_index_intro' => 'Use esses relatórios para obter informações detalhadas sobre suas finanças.', + 'reports_index_inputReportType' => 'Escolha um tipo de relatório. Confira as páginas de ajuda para ver o que cada relatório mostra.', + 'reports_index_inputAccountsSelect' => 'Você pode excluir ou incluir contas de ativos de acordo com a sua demanda.', + 'reports_index_inputDateRange' => 'O intervalo de datas selecionado depende inteiramente de você: de um dia a 10 anos.', + 'reports_index_extra-options-box' => 'Dependendo do relatório que você selecionou, você pode usar filtros e opções adicionais aqui. Observe esta caixa quando você altera os tipos de relatórios.', // reports (reports) - 'reports_report_default_intro' => 'Este relatório lhe dará uma visão geral rápida e abrangente de suas finanças. Se você deseja ver mais alguma coisa, não hesite em contactar-me!', - 'reports_report_audit_intro' => 'Este relatório fornecerá informações detalhadas sobre suas contas de ativos.', - 'reports_report_audit_optionsBox' => 'Use essas caixas de seleção para mostrar ou ocultar as colunas em que você está interessado.', + 'reports_report_default_intro' => 'Este relatório lhe dará uma visão geral rápida e abrangente de suas finanças. Se você deseja ver mais alguma coisa, não hesite em contactar-me!', + 'reports_report_audit_intro' => 'Este relatório fornecerá informações detalhadas sobre suas contas de ativos.', + 'reports_report_audit_optionsBox' => 'Use essas caixas de seleção para mostrar ou ocultar as colunas em que você está interessado.', 'reports_report_category_intro' => 'Este relatório lhe dará uma visão em uma ou várias categorias.', 'reports_report_category_pieCharts' => 'Esses gráficos fornecerão informações sobre despesas e receitas por categoria ou por conta.', @@ -74,7 +96,7 @@ return [ 'transactions_create_switch_box' => 'Use esses botões para mudar rapidamente o tipo de transação que deseja salvar.', 'transactions_create_ffInput_category' => 'Você pode digitar livremente neste campo. As categorias criadas anteriormente serão sugeridas.', 'transactions_create_withdrawal_ffInput_budget' => 'Vincule sua retirada a um orçamento para um melhor controle financeiro.', - 'transactions_create_withdrawal_currency_dropdown_amount' => 'Use este menu suspenso quando seu depósito estiver em outra moeda.', + 'transactions_create_withdrawal_currency_dropdown_amount' => 'Use este menu quando seu depósito estiver em outra moeda.', 'transactions_create_deposit_currency_dropdown_amount' => 'Use este menu suspenso quando seu depósito estiver em outra moeda.', 'transactions_create_transfer_ffInput_piggy_bank_id' => 'Selecione um banco e vincule essa transferência às suas economias.', diff --git a/resources/lang/pt_BR/list.php b/resources/lang/pt_BR/list.php index 2709d7eff6..7ee2080dae 100644 --- a/resources/lang/pt_BR/list.php +++ b/resources/lang/pt_BR/list.php @@ -23,71 +23,71 @@ declare(strict_types=1); return [ - 'buttons' => 'Botões', - 'icon' => 'Ícone', - 'id' => 'ID', - 'create_date' => 'Criado em', - 'update_date' => 'Atualizado em', - 'updated_at' => 'Atualizado em', - 'balance_before' => 'Saldo Antes', - 'balance_after' => 'Saldo depois', - 'name' => 'Nome', - 'role' => 'Papel', - 'currentBalance' => 'Saldo atual', - 'linked_to_rules' => 'Regras relevantes', - 'active' => 'Está ativo?', - 'lastActivity' => 'Última atividade', - 'balanceDiff' => 'Diferença de saldo', - 'matchesOn' => 'Correspondido em', - 'account_type' => 'Tipo de conta', - 'created_at' => 'Criado em', - 'account' => 'Conta', - 'matchingAmount' => 'Total', - 'split_number' => 'Dividir #', - 'destination' => 'Destino', - 'source' => 'Fonte', - 'next_expected_match' => 'Próximo correspondente esperado', - 'automatch' => 'Correspondência automática?', - 'repeat_freq' => 'Repetições', - 'description' => 'Descrição', - 'amount' => 'Total', - 'internal_reference' => 'Referência interna', - 'date' => 'Data', - 'interest_date' => 'Data de interesse', - 'book_date' => 'Data reserva', - 'process_date' => 'Data de processamento', - 'due_date' => 'Prazo', - 'payment_date' => 'Data de pagamento', - 'invoice_date' => 'Data da Fatura', - 'interal_reference' => 'Referência interna', - 'notes' => 'Notas', - 'from' => 'De', - 'piggy_bank' => 'Cofrinho', - 'to' => 'Para', - 'budget' => 'Orçamento', - 'category' => 'Categoria', - 'bill' => 'Fatura', - 'withdrawal' => 'Retirada', - 'deposit' => 'Depósito', - 'transfer' => 'Transferência', - 'type' => 'Tipo', - 'completed' => 'Completo', - 'iban' => 'IBAN', - 'paid_current_period' => 'Pago este período', - 'email' => 'Email', - 'registered_at' => 'Registrado em', - 'is_blocked' => 'Está bloqueado', - 'is_admin' => 'É admin', - 'has_two_factor' => 'Tem 2FA', - 'blocked_code' => 'Bloco de código', - 'source_account' => 'Conta de origem', + 'buttons' => 'Botões', + 'icon' => 'Ícone', + 'id' => 'ID', + 'create_date' => 'Criado em', + 'update_date' => 'Atualizado em', + 'updated_at' => 'Atualizado em', + 'balance_before' => 'Saldo Antes', + 'balance_after' => 'Saldo depois', + 'name' => 'Nome', + 'role' => 'Papel', + 'currentBalance' => 'Saldo atual', + 'linked_to_rules' => 'Regras relevantes', + 'active' => 'Está ativo?', + 'transaction_type' => 'Tipo', + 'lastActivity' => 'Última atividade', + 'balanceDiff' => 'Diferença de saldo', + 'matchesOn' => 'Correspondido em', + 'account_type' => 'Tipo de conta', + 'created_at' => 'Criado em', + 'account' => 'Conta', + 'matchingAmount' => 'Total', + 'split_number' => 'Dividir #', + 'destination' => 'Destino', + 'source' => 'Fonte', + 'next_expected_match' => 'Próximo correspondente esperado', + 'automatch' => 'Correspondência automática?', + 'repeat_freq' => 'Repetições', + 'description' => 'Descrição', + 'amount' => 'Total', + 'internal_reference' => 'Referência interna', + 'date' => 'Data', + 'interest_date' => 'Data de interesse', + 'book_date' => 'Data reserva', + 'process_date' => 'Data de processamento', + 'due_date' => 'Prazo', + 'payment_date' => 'Data de pagamento', + 'invoice_date' => 'Data da Fatura', + 'interal_reference' => 'Referência interna', + 'notes' => 'Notas', + 'from' => 'De', + 'piggy_bank' => 'Cofrinho', + 'to' => 'Para', + 'budget' => 'Orçamento', + 'category' => 'Categoria', + 'bill' => 'Fatura', + 'withdrawal' => 'Retirada', + 'deposit' => 'Depósito', + 'transfer' => 'Transferência', + 'type' => 'Tipo', + 'completed' => 'Completo', + 'iban' => 'IBAN', + 'paid_current_period' => 'Pago este período', + 'email' => 'Email', + 'registered_at' => 'Registrado em', + 'is_blocked' => 'Está bloqueado', + 'is_admin' => 'É admin', + 'has_two_factor' => 'Tem 2FA', + 'blocked_code' => 'Bloco de código', + 'source_account' => 'Conta de origem', 'destination_account' => 'Conta de destino', 'accounts_count' => 'Número de Contas', 'journals_count' => 'Número de transações', 'attachments_count' => 'Número de anexos', 'bills_count' => 'Número de contas', 'categories_count' => 'Número de categorias', - 'export_jobs_count' => 'Número de jobs de exportação', 'import_jobs_count' => 'Número de jobs de importação', 'budget_count' => 'Número de orçamentos', 'rule_and_groups_count' => 'Número de regras e grupos de regras', @@ -106,14 +106,14 @@ return [ 'account_on_spectre' => 'Conta (Spectre)', 'account_on_ynab' => 'Conta (YNAB)', 'do_import' => 'Importar desta conta', - 'sepa-ct-id' => 'SEPA Identificador end-to-end', - 'sepa-ct-op' => 'SEPA Identificador de conta de contrária', - 'sepa-db' => 'SEPA Identificador de Mandato', - 'sepa-country' => 'SEPA País', - 'sepa-cc' => 'SEPA Código de Compensação', - 'sepa-ep' => 'SEPA Finalidade Externa', - 'sepa-ci' => 'SEPA Identificador do Credor', - 'sepa-batch-id' => 'ID de lote SEPA', + 'sepa_ct_id' => 'SEPA Identificador end-to-end', + 'sepa_ct_op' => 'SEPA Identificador de Conta Destino', + 'sepa_db' => 'SEPA Identificador Mandatário', + 'sepa_country' => 'SEPA País', + 'sepa_cc' => 'SEPA Código de Compensação', + 'sepa_ep' => 'SEPA Finalidade Externa', + 'sepa_ci' => 'SEPA Identificador do Credor', + 'sepa_batch_id' => 'ID de lote SEPA', 'external_id' => 'ID externo', 'account_at_bunq' => 'Loja com bunq', 'file_name' => 'Nome do arquivo', diff --git a/resources/lang/pt_BR/validation.php b/resources/lang/pt_BR/validation.php index 8908af7c02..5389221d6c 100644 --- a/resources/lang/pt_BR/validation.php +++ b/resources/lang/pt_BR/validation.php @@ -36,12 +36,16 @@ return [ 'file_attached' => 'Arquivo carregado com sucesso ":name".', 'must_exist' => 'O ID no campo :attribute não existe no banco de dados.', 'all_accounts_equal' => 'Todas as contas neste campo devem ser iguais.', + 'group_title_mandatory' => 'Um título de grupo é obrigatório quando existe mais de uma transação.', + 'transaction_types_equal' => 'Todas as divisões devem ser do mesmo tipo.', + 'invalid_transaction_type' => 'Tipo de transação inválido.', 'invalid_selection' => 'Sua seleção é inválida.', 'belongs_user' => 'Esse valor é inválido para este campo.', 'at_least_one_transaction' => 'Precisa de ao menos uma transação.', 'at_least_one_repetition' => 'Precisa de ao menos uma repetição.', 'require_repeat_until' => 'É necessário ou um número de repetições ou uma data de término (repetir até). Não ambos.', 'require_currency_info' => 'O conteúdo deste campo é inválido sem informações de moeda.', + 'require_currency_amount' => 'O conteúdo deste campo é inválido sem a informação de moeda estrangeira.', 'equal_description' => 'A descrição da transação não pode ser igual à descrição global.', 'file_invalid_mime' => 'Arquivo ":name" é do tipo ":mime" que não é aceito como um novo upload.', 'file_too_large' => 'Arquivo ":name" é muito grande.', @@ -131,20 +135,20 @@ return [ 'attributes' => [ 'email' => 'endereço de e-mail', 'description' => 'descrição', - 'amount' => 'quantidade', + 'amount' => 'valor', 'name' => 'nome', 'piggy_bank_id' => 'ID do cofrinho', 'targetamount' => 'quantidade alvo', - 'openingBalanceDate' => 'data do Saldo inicial', - 'openingBalance' => 'saldo inicial', + 'opening_balance_date' => 'data do saldo inicial', + 'opening_balance' => 'saldo inicial', 'match' => 'coincidente', 'amount_min' => 'valor mínimo', 'amount_max' => 'valor máximo', 'title' => 'título', - 'tag' => 'identificador', + 'tag' => 'tag', 'transaction_description' => 'Descrição da transação', 'rule-action-value.1' => 'valor de ação de regra #1', - 'rule-action-value.2' => 'valor de ação de regra #2', + 'rule-action-value.2' => 'valor de ação da regra #2', 'rule-action-value.3' => 'valor de ação de regra #3', 'rule-action-value.4' => 'valor de ação de regra #4', 'rule-action-value.5' => 'valor de ação de regra #5', @@ -164,4 +168,28 @@ return [ 'rule-trigger.4' => 'disparador da regra #4', 'rule-trigger.5' => 'disparador da regra #5', ], + + // validation of accounts: + 'withdrawal_source_need_data' => 'É necessário obter um ID de uma conta de origem válida e/ou um nome de conta de origem válido para continuar.', + 'withdrawal_source_bad_data' => 'Não foi possível encontrar uma conta de destino válida ao pesquisar por ID ":id" ou nome ":name".', + 'withdrawal_dest_need_data' => 'É necessário obter um ID de uma conta de origem válida e/ou um nome de conta de origem válido para continuar.', + 'withdrawal_dest_bad_data' => 'Não foi possível encontrar uma conta de destino válida ao pesquisar por ID ":id" ou nome ":name".', + + 'deposit_source_need_data' => 'É necessário obter um ID de uma conta de origem válida e/ou um nome de conta de origem válido para continuar.', + 'deposit_source_bad_data' => 'Não foi possível encontrar uma conta de destino válida ao pesquisar por ID ":id" ou nome ":name".', + 'deposit_dest_need_data' => 'É necessário obter obter um ID de conta de destino válido e/ou nome de conta de destino válido para continuar.', + 'deposit_dest_bad_data' => 'Não foi possível encontrar uma conta de destino válida ao pesquisar por ID ":id" ou nome ":name".', + + 'transfer_source_need_data' => 'É necessário obter um ID de uma conta de origem válida e/ou um nome de conta de origem válido para continuar.', + 'transfer_source_bad_data' => 'Não foi possível encontrar uma conta de destino válida ao pesquisar por ID ":id" ou nome ":name".', + 'transfer_dest_need_data' => 'É necessário obter obter um ID de conta de destino válido e/ou nome de conta de destino válido para continuar.', + 'transfer_dest_bad_data' => 'Não foi possível encontrar uma conta de destino válida ao pesquisar por ID ":id" ou nome ":name".', + 'need_id_in_edit' => 'Cada divisão deve ter transaction_journal_id (ID válido ou 0).', + + 'ob_source_need_data' => 'É necessário obter um ID de uma conta de origem válida e/ou um nome de conta de origem válido para continuar.', + 'ob_dest_need_data' => 'É necessário obter um ID de uma conta de origem válida e/ou um nome de conta de origem válido para continuar.', + 'ob_dest_bad_data' => 'Não foi possível encontrar uma conta de destino válida ao pesquisar por ID ":id" ou nome ":name".', + + 'generic_invalid_source' => 'Você não pode usar esta conta como conta de origem.', + 'generic_invalid_destination' => 'Você não pode usar esta conta como conta de destino.', ]; diff --git a/resources/lang/ro_RO/auth.php b/resources/lang/ro_RO/auth.php new file mode 100644 index 0000000000..c586307449 --- /dev/null +++ b/resources/lang/ro_RO/auth.php @@ -0,0 +1,28 @@ +. + */ + +declare(strict_types=1); + +return [ + 'failed' => 'Aceste informații de autentificare nu corespund înregistrărilor noastre.', + 'throttle' => 'Prea multe tentative de autentificare. Vă rugăm să încercaţi din nou în :seconds secunde.', +]; diff --git a/resources/lang/ro_RO/bank.php b/resources/lang/ro_RO/bank.php new file mode 100644 index 0000000000..5d00b1e685 --- /dev/null +++ b/resources/lang/ro_RO/bank.php @@ -0,0 +1,26 @@ +. + */ + +declare(strict_types=1); + +return [ +]; diff --git a/resources/lang/ro_RO/breadcrumbs.php b/resources/lang/ro_RO/breadcrumbs.php new file mode 100644 index 0000000000..d07c873d3f --- /dev/null +++ b/resources/lang/ro_RO/breadcrumbs.php @@ -0,0 +1,59 @@ +. + */ + +declare(strict_types=1); + +return [ + 'home' => 'Acasă', + 'edit_currency' => 'Editează moneda ":name"', + 'delete_currency' => 'Șterge moneda ":name"', + 'newPiggyBank' => 'Crează o nouă pușculiță', + 'edit_piggyBank' => 'Editează pușculița ":name"', + 'preferences' => 'Preferințe', + 'profile' => 'Profil', + 'changePassword' => 'Modificare parolă', + 'change_email' => 'Modificare adresă de email', + 'bills' => 'Facturi', + 'newBill' => 'Factură nouă', + 'edit_bill' => 'Editează factura ":name"', + 'delete_bill' => 'Șterge factura ":name"', + 'reports' => 'Rapoarte', + 'search_result' => 'Rezultatele căutării ":query"', + 'withdrawal_list' => 'Cheltuieli', + 'Withdrawal_list' => 'Expenses', + 'deposit_list' => 'Venituri și depozite', + 'transfer_list' => 'Transferuri', + 'transfers_list' => 'Transferuri', + 'reconciliation_list' => 'Reconcilieri', + 'create_withdrawal' => 'Crează o nouă retragere', + 'create_deposit' => 'Crează depozit nou', + 'create_transfer' => 'Crează transfer nou', + 'create_new_transaction' => 'Creați o tranzacție nouă', + 'edit_journal' => 'Editează tranzacția ":description"', + 'edit_reconciliation' => 'Editează ":description"', + 'delete_journal' => 'Șterge tranzacția ":description"', + 'tags' => 'Etichete', + 'createTag' => 'Crează o etichetă nouă', + 'edit_tag' => 'Editează eticheta ":tag"', + 'delete_tag' => 'Șterge eticheta ":tag"', + 'delete_journal_link' => 'Şterge legătura dintre tranzacţii', +]; diff --git a/resources/lang/ro_RO/components.php b/resources/lang/ro_RO/components.php new file mode 100644 index 0000000000..c306c6448a --- /dev/null +++ b/resources/lang/ro_RO/components.php @@ -0,0 +1,32 @@ +. + */ + +declare(strict_types=1); + +return [ + // profile + 'personal_access_tokens' => 'Token-uri personale', + + // bills: + 'not_expected_period' => 'Nu este de așteptat în această perioadă', + 'not_or_not_yet' => 'Nu (încă)', +]; diff --git a/resources/lang/ro_RO/config.php b/resources/lang/ro_RO/config.php new file mode 100644 index 0000000000..b7553b164e --- /dev/null +++ b/resources/lang/ro_RO/config.php @@ -0,0 +1,51 @@ +. + */ + +declare(strict_types=1); + +return [ + 'html_language' => 'ro', + 'locale' => 'ro, Română, ro_RO.utf8, ro_RO.UTF-8', + 'month' => '%B %Y', + 'month_and_day' => '%B %e, %Y', + 'month_and_date_day' => '%A %B %e %Y', + 'month_and_day_no_year' => '%B %e', + 'date_time' => '%B %e, %Y, @ %T', + 'specific_day' => '%e %B %Y', + 'week_in_year' => 'Săptămâna %W, %Y', + 'year' => '%Y', + 'half_year' => '%B %Y', + 'month_js' => 'MMMM YYYY', + 'month_and_day_js' => 'MMMM Do, YYYY', + 'date_time_js' => 'MMMM Do, YYYY, @ HH:mm:ss', + 'specific_day_js' => 'D MMMM YYYY', + 'week_in_year_js' => '[Week] w, YYYY', + 'year_js' => 'YYYY', + 'half_year_js' => 'Q YYYY', + 'dow_1' => 'Luni', + 'dow_2' => 'Marţi', + 'dow_3' => 'Miercuri', + 'dow_4' => 'Joi', + 'dow_5' => 'Vineri', + 'dow_6' => 'Sâmbătă', + 'dow_7' => 'Duminică', +]; diff --git a/resources/lang/ro_RO/csv.php b/resources/lang/ro_RO/csv.php new file mode 100644 index 0000000000..aae109a40a --- /dev/null +++ b/resources/lang/ro_RO/csv.php @@ -0,0 +1,26 @@ +. + */ + +declare(strict_types=1); + +return [ +]; diff --git a/resources/lang/ro_RO/demo.php b/resources/lang/ro_RO/demo.php new file mode 100644 index 0000000000..60b913ecac --- /dev/null +++ b/resources/lang/ro_RO/demo.php @@ -0,0 +1,38 @@ +. + */ + +declare(strict_types=1); + +return [ + 'no_demo_text' => 'Ne pare rău, nu există niciun text demo-explicativ suplimentar pentru această pagină .', + 'see_help_icon' => 'Totuși, pictograma din colțul din dreapta sus vă poate spune mai multe.', + 'index' => 'Bine ați venit la Firefly III ! Pe această pagină veți obține o prezentare generală rapidă a finanțelor. Pentru mai multe informații, consultați Conturile → Conturile de active și, desigur, paginile Bugete și Rapoarte . Sau doar aruncați o privire în jur și să vedeți unde ajungeți.', + 'accounts-index' => 'Conturile de active sunt conturile bancare personale. Conturile de cheltuieli sunt conturile la care cheltuiți bani, cum ar fi magazine și prieteni. Conturile de venituri sunt conturile în care primiți bani, cum ar fi locul de muncă, guvernul sau alte surse de venit. Datoriile sunt datoriile și împrumuturile dvs., cum ar fi datorii vechi de cărți de credit sau împrumuturi pentru studenți. Pe această pagină le puteți edita sau elimina.', + 'budgets-index' => 'Această pagină vă arată o prezentare generală a bugetelor dvs. Bara de sus indică suma disponibilă pentru a fi înscrisă în buget. Acest lucru poate fi personalizat pentru orice perioadă, făcând clic pe suma din dreapta. Suma pe care ați cheltuit-o este afișată în bara de mai jos. Mai jos sunt cheltuielile pe buget și ceea ce ați bugetat pentru ele.', + 'reports-index-start' => 'Firefly III acceptă o serie de tipuri de rapoarte. Citiți despre ele făcând clic pe iconița din colțul din dreapta sus.', + 'reports-index-examples' => 'Asigurați-vă că ați verificat aceste exemple o prezentare financiară lunară, o prezentare financiară anuală and o prezentare generală a bugetului.', + 'currencies-index' => 'Firefly III acceptă mai multe monede. Deși EURO este moneda implicită, acesta poate fi schimbată în Dolar, RON și multe alte valute. După cum vedeți, o mică selecție de valute a fost inclusă, dar puteți adăuga propria dvs. monedă dacă doriți. Schimbarea monedei prestabilite nu va schimba însă moneda tranzacțiilor existente: Firefly III acceptă simultan utilizarea mai multor monede.', + 'transactions-index' => 'Aceste cheltuieli, depozite și transferuri nu sunt deosebit de imaginative. Au fost generate automat.', + 'piggy-banks-index' => 'După cum puteți vedea, există trei pușculițe. Utilizați butoanele plus și minus pentru a influența cantitatea de bani din fiecare pușculiță. Faceți clic pe numele pușculiței pentru a vedea gestiunea pentru fiecare pușculiță.', + 'import-index' => 'Orice fișier CSV poate fi importat în Firefly III. De asemenea, acceptă importul datelor de la bunq și Spectre. Alte bănci și agregatoare financiare vor fi implementate în viitor. Totuși, în calitate de utilizator demo, puteți vedea doar providerii "falși". Se vor genera unele tranzacții aleatorii pentru a vă arăta cum funcționează procesul.', + 'profile-index' => 'Rețineți că site-ul demo se resetează la fiecare patru ore. Accesul dvs. poate fi revocat oricând. Acest lucru se întâmplă automat și nu este un bug.', +]; diff --git a/resources/lang/ro_RO/firefly.php b/resources/lang/ro_RO/firefly.php new file mode 100644 index 0000000000..c114ab1c80 --- /dev/null +++ b/resources/lang/ro_RO/firefly.php @@ -0,0 +1,1415 @@ +. + */ + +declare(strict_types=1); + +return [ + // general stuff: + 'close' => 'Închide', + 'actions' => 'Acțiuni', + 'edit' => 'Editează', + 'delete' => 'Șterge', + 'split' => 'Împarte', + 'clone' => 'Clonă', + 'last_seven_days' => 'Ultimele 7 zile', + 'last_thirty_days' => 'Ultimele 30 de zile', + 'welcomeBack' => 'Bine ați venit!', + 'welcome_back' => 'Ce se redă?', + 'everything' => 'Tot', + 'today' => 'Azi', + 'customRange' => 'Intervalul personalizat', + 'apply' => 'Aplică', + 'select_date' => 'Selectează data..', + 'cancel' => 'Anulare', + 'from' => 'De la', + 'to' => 'Către', + 'structure' => 'Structură', + 'help_translating' => 'Acest text de ajutor nu este încă disponibil în limba dvs.', + 'showEverything' => 'Afișați totul', + 'never' => 'Niciodată', + 'no_results_for_empty_search' => 'Căutarea dvs. a fost goală, deci nu a fost găsit nimic.', + 'removed_amount' => ':amount eliminată', + 'added_amount' => ':amount adaugată', + 'asset_account_role_help' => 'Opțiunile suplimentare rezultate din alegerea dvs. pot fi setate mai târziu.', + 'Opening balance' => 'Soldul de deschidere', + 'create_new_stuff' => 'Creați lucruri noi', + 'new_withdrawal' => 'Tranzacție nouă', + 'create_new_transaction' => 'Creați o nouă tranzacție', + 'new_transaction' => 'Tranzacţie nouă', + 'go_to_asset_accounts' => 'Vizualizați conturile de active', + 'go_to_budgets' => 'Mergi la bugete', + 'go_to_categories' => 'Mergi la categorii', + 'go_to_bills' => 'Mergi la facturi', + 'go_to_expense_accounts' => 'Vezi cheltuielile contabile', + 'go_to_revenue_accounts' => 'Vezi contul de venituris', + 'go_to_piggies' => 'Mergi la pușculiță', + 'new_deposit' => 'Depunere nouă', + 'new_transfer' => 'Transfer nou', + 'new_transfers' => 'Transferuri noi', + 'new_asset_account' => 'Cont nou de activ', + 'new_expense_account' => 'Cont nou de cheltuieli', + 'new_revenue_account' => 'Cont nou pentru venituri', + 'new_liabilities_account' => 'Provizion nou', + 'new_budget' => 'Buget nou', + 'new_bill' => 'Factură nouă', + 'block_account_logout' => 'Ai fost deconectat. Conturile blocate nu pot utiliza acest site. Ați înregistrat o adresă de e-mail validă?', + 'flash_success' => 'Succes!', + 'flash_info' => 'Mesaj', + 'flash_warning' => 'Avertizare!', + 'flash_error' => 'Eroare!', + 'flash_info_multiple' => 'Există un mesaj | Există :count mesaje', + 'flash_error_multiple' => 'Există o singură eroare | Există :count erori', + 'net_worth' => 'Valoarea netă', + 'route_has_no_help' => 'Nu există nici un ajutor pentru această rută.', + 'help_for_this_page' => 'Ajutor pentru această pagină', + 'no_help_could_be_found' => 'Nu a putut fi găsit niciun text de ajutor.', + 'no_help_title' => 'Ne cerem scuze, a apărut o eroare.', + 'two_factor_welcome' => 'Hello!', + 'two_factor_enter_code' => 'Pentru a continua, introduceți codul dvs. de autentificare cu doi factori. Aplicația dvs. o poate genera pentru dvs.', + 'two_factor_code_here' => 'Introdu codul aici', + 'two_factor_title' => 'Autentificare cu doi factori', + 'authenticate' => 'Autentificare', + 'two_factor_forgot_title' => 'S-a pierdut autentificarea cu doi factori', + 'two_factor_forgot' => 'Am uitat autentificarea cu doi factori.', + 'two_factor_lost_header' => 'Ai uitat autentificarea cu doi factori?', + 'two_factor_lost_intro' => 'If you lost your backup codes as well, you have bad luck. This is not something you can fix from the web interface. You have two choices.', + 'two_factor_lost_fix_self' => 'If you run your own instance of Firefly III, check the logs in storage/logs for instructions, or run docker logs <container_id> to see the instructions (refresh this page).', + 'two_factor_lost_fix_owner' => 'În caz contrar, trimiteți prin e-mail proprietarului site-ului : proprietarul site-ului și solicitați-i să reseteze autentificarea cu doi factori.', + 'mfa_backup_code' => 'You have used a backup code to login to Firefly III. It can\'t be used again, so cross it from your list.', + 'pref_two_factor_new_backup_codes' => 'Get new backup codes', + 'pref_two_factor_backup_code_count' => 'You have :count valid backup code(s).', + '2fa_i_have_them' => 'I stored them!', + 'warning_much_data' => ':days zilele de date pot dura o perioadă până încărcare.', + 'registered' => 'Te-ai inregistrat cu succes!', + 'Default asset account' => 'Ccont de active implicit', + 'no_budget_pointer' => 'Se pare că nu ai încă bugete. Ar trebui să creați unele pe pagina bugete . Bugetele vă pot ajuta să țineți evidența cheltuielilor.', + 'Savings account' => 'Cont de economii', + 'Credit card' => 'Card de credit', + 'source_accounts' => 'Cont (uri) sursă', + 'destination_accounts' => 'Cont (uri) de destinație', + 'user_id_is' => 'ID-ul dvs. de utilizator este :user', + 'field_supports_markdown' => 'Acest câmp acceptă Markdown HTML.', + 'need_more_help' => 'Dacă aveți nevoie de ajutor suplimentar, deschideți un tichet pe Github.', + 'reenable_intro_text' => 'De asemenea, puteți să activați din nou ghidul de introducere .', + 'intro_boxes_after_refresh' => 'Cutiile de introducere vor apărea din nou atunci când actualizați pagina.', + 'show_all_no_filter' => 'Afișați toate tranzacțiile fără a le grupa după dată.', + 'expenses_by_category' => 'Cheltuieli pe categorii', + 'expenses_by_budget' => 'Cheltuieli pe buget', + 'income_by_category' => 'Venituri pe categorii', + 'expenses_by_asset_account' => 'Cheltuieli pe cont de activ', + 'expenses_by_expense_account' => 'Cheltuieli pe cont de cheltuieli', + 'cannot_redirect_to_account' => 'Nu vă putem redirecționa către pagina corectă. Scuze.', + 'sum_of_expenses' => 'Suma cheltuielilor', + 'sum_of_income' => 'Suma veniturilor', + 'liabilities' => 'Provizioane', + 'spent_in_specific_budget' => 'Cheltuit în bugetul ":budget"', + 'sum_of_expenses_in_budget' => 'Cheltuielile totale în bugetul ":budget"', + 'left_in_budget_limit' => 'Rest de chetuit ăn funcție de buget', + 'current_period' => 'Perioada curentă', + 'show_the_current_period_and_overview' => 'Afișați perioada curentă și prezentarea generală', + 'pref_languages_locale' => 'Pentru ca o altă limbă decât limba engleză să funcționeze corect, sistemul dvs. de operare trebuie să fie dotat cu informațiile de localizare corecte. Dacă acestea nu sunt prezente, datele valutare, datele și sumele pot fi formatate greșit.', + 'budget_in_period' => 'Toate tranzacțiile pentru bugetul ":name" între :start și :end', + 'chart_budget_in_period' => 'Graficul cu toate tranzacțiile pentru bugetul ":name" între :start și :end', + 'chart_account_in_period' => 'Graficul cu toate tranzacțiile pentru contul ":name" între :start și :end', + 'chart_category_in_period' => 'Graficul cu toate tranzacțiile pentru categoria ":name" între :start și :end', + 'chart_category_all' => 'Graficul cu toate tranzacțiile pentru categoria ":name"', + 'clone_withdrawal' => 'Clonați această retragere', + 'clone_deposit' => 'Clonați această depozit', + 'clone_transfer' => 'Clonați această transfer', + 'multi_select_no_selection' => 'Nimic selectat', + 'multi_select_select_all' => 'Selectează tot', + 'multi_select_n_selected' => 'selectat', + 'multi_select_all_selected' => 'Toate selectate', + 'multi_select_filter_placeholder' => 'Gasește..', + 'intro_next_label' => 'Următor', + 'intro_prev_label' => 'Anterior', + 'intro_skip_label' => 'Treci peste', + 'intro_done_label' => 'Finalizat', + 'between_dates_breadcrumb' => 'între :start și :end', + 'all_journals_without_budget' => 'Toate tranzacțiile fără un buget', + 'journals_without_budget' => 'Tranzacții fără un buget', + 'all_journals_without_category' => 'Toate tranzacțiile fără o categorie', + 'journals_without_category' => 'Tranzacții fără o categorie', + 'all_journals_for_account' => 'Toate tranzacțiile pentru contul :name', + 'chart_all_journals_for_account' => 'Graficul tuturor tranzacțiilor pentru contul :name', + 'journals_in_period_for_account' => 'Toate tranzacțiile pentru contul :name între :start și :end', + 'transferred' => 'Transferat', + 'all_withdrawal' => 'Toate cheltuielile', + 'all_transactions' => 'Toate tranzacțiile', + 'title_withdrawal_between' => 'Toate cheltuielile între :start și :end', + 'all_deposit' => 'Toate veniturile', + 'title_deposit_between' => 'Toate veniturile între :start și :end', + 'all_transfers' => 'Toate transferurile', + 'title_transfers_between' => 'Toate transferurile între :start și :end', + 'all_transfer' => 'Toate transferurile', + 'all_journals_for_tag' => 'Toate tranzacțiile pentru eticheta ":tag"', + 'title_transfer_between' => 'Toate transferurile între :start și :end', + 'all_journals_for_category' => 'Toate tranzacțiile pentru categoria :name', + 'all_journals_for_budget' => 'Toate tranzacțiile pentru bugetul :name', + 'chart_all_journals_for_budget' => 'Graficul cu toate tranzacțiile pentru bugetul :name', + 'journals_in_period_for_category' => 'Toate tranzacțiile pentru categoria :name între :start și :end', + 'journals_in_period_for_tag' => 'Toate tranzacțiile pentru eticheta :tag între :start și :end', + 'not_available_demo_user' => 'Funcția pe care încercați să o accesați nu este disponibilă pentru demo.', + 'exchange_rate_instructions' => 'Contul de active "@name" acceptă numai tranzacțiile în @native_currency. Dacă doriți să utilizați în schimb @foreign_currency asigurați-vă că suma din @native_currency este cunoscută, de asemenea:', + 'transfer_exchange_rate_instructions' => 'Sursa contului de active "@source_name" acceptă numai tranzacțiile în @source_currency. Destinația contului de active "@dest_name" acceptă numai tranzacțiile în @dest_currency. Trebuie să furnizați corect suma transferată în ambele valute.', + 'transaction_data' => 'Datele tranzacției', + 'invalid_server_configuration' => 'Configurația serverului este nevalidă', + 'invalid_locale_settings' => 'Nu se pot formata sumele deoarece serverului dvs îi lipesc pachete esențiale. Există instrucțiuni despre cum să faceți acest lucru .', + 'quickswitch' => 'Schimbare rapida', + 'sign_in_to_start' => 'Logați-vă pentru a începe sesiunea', + 'sign_in' => 'Logare', + 'register_new_account' => 'Înregistrați un nou cont', + 'forgot_my_password' => 'Am uitat parola', + 'problems_with_input' => 'Sunt probleme cu input-ul dvs.', + 'reset_password' => 'Reseteaza parola', + 'button_reset_password' => 'Reseteaza parola', + 'reset_button' => 'Reseteaza', + 'want_to_login' => 'Vreau sa mă loghez', + 'login_page_title' => 'Logare', + 'register_page_title' => 'Înregistrare', + 'forgot_pw_page_title' => 'Am uitat parola', + 'reset_pw_page_title' => 'Reseteaza parola', + 'cannot_reset_demo_user' => 'Nu puteți reseta parola utilizatorului demo.', + 'button_register' => 'Înregistrare', + 'authorization' => 'Autorizare', + 'active_bills_only' => 'numai facturi active', + 'average_per_bill' => 'media pe factură', + 'expected_total' => 'total așteptat', + // API access + 'authorization_request' => 'v: Solicitare de autorizare', + 'authorization_request_intro' => ' :client solicită permisiunea de a accesa administrația financiară. Doriți să autorizați :client pentru a accesa aceste înregistrări?', + 'scopes_will_be_able' => 'Această aplicație va fi capabilă să to:', + 'button_authorize' => 'Autorizează', + 'none_in_select_list' => '(nici unul)', + 'name_in_currency' => ':name în :currency', + 'paid_in_currency' => 'Plătit în :currency', + 'unpaid_in_currency' => 'Neplătit în :currency', + + // check for updates: + 'update_check_title' => 'Verifică actualizări', + 'admin_update_check_title' => 'Verificați automat actualizarea', + 'admin_update_check_explain' => 'Firefly III poate verifica automat actualizările. Când activați această setare, va contacta Github pentru a vedea dacă este disponibilă o nouă versiune de Firefly III. Când este, veți primi o notificare. Puteți testa această notificare utilizând butonul din dreapta. Indicați mai jos dacă doriți ca Firefly III să verifice actualizările.', + 'check_for_updates_permission' => 'Firefly III poate verifica actualizările, dar are nevoie de permisiunea dvs. pentru a face acest lucru. Accesați link-ul pentru a indica dacă doriți ca această funcție să fie activată.', + 'updates_ask_me_later' => 'Intreabă-mă mai târziu', + 'updates_do_not_check' => 'Nu verificați actualizările', + 'updates_enable_check' => 'Activați verificarea pentru actualizări', + 'admin_update_check_now_title' => 'Verificați actualizările acum', + 'admin_update_check_now_explain' => 'Dacă apăsați butonul, Firefly III va vedea dacă versiunea curentă este cea mai recentă.', + 'check_for_updates_button' => 'Verifică acum!', + 'update_new_version_alert' => 'O nouă versiune de Firefly III este disponibilă. Dvs. aveți v:your_version, ultima versiune este v:new_version lansată în data de :date.', + 'update_current_version_alert' => 'Aveți versiunea v:version, care este ultima disponibilă.', + 'update_newer_version_alert' => 'Aveți versiunea v:your_version, care este mai nouă decât cea mai recentă versiune, v:new_version.', + 'update_check_error' => 'A apărut o eroare la verificarea actualizărilor. Consultați log-urile.', + + // search + 'search' => 'Caută', + 'search_query' => 'Interogare', + 'search_found_transactions' => 'Numărul de tranzacții găsite:', + 'search_for_query' => 'Firefly III este în căutarea pentru tranzacţii cu toate aceste cuvinte în ele: :query', + 'search_modifier_amount_is' => 'Suma este exact :value', + 'search_modifier_amount' => 'Suma este exact :value', + 'search_modifier_amount_max' => 'Suma este la cele mai multe :value', + 'search_modifier_amount_min' => 'Suma este de cel puţin :value', + 'search_modifier_amount_less' => 'Suma este mai mică decât :value', + 'search_modifier_amount_more' => 'Suma este mai mare decât :value', + 'search_modifier_source' => 'Contul sursă este :value', + 'search_modifier_from' => 'Source account is :value', + 'search_modifier_destination' => 'Contul destinației este :value', + 'search_modifier_to' => 'Destination account is :value', + 'search_modifier_category' => 'Categoria este :value', + 'search_modifier_budget' => 'Bugetul este :value', + 'search_modifier_bill' => 'Factura este :value', + 'search_modifier_type' => 'Tipul de tranzacţie este :value', + 'search_modifier_date' => 'Data tranzacţiei este :value', + 'search_modifier_date_before' => 'Data tranzacţiei este înainte de :value', + 'search_modifier_date_after' => 'Data tranzacţiei este după :value', + 'search_modifier_on' => 'Data tranzacţiei este :value', + 'search_modifier_before' => 'Data tranzacţiei este înainte de :value', + 'search_modifier_after' => 'Data tranzacţiei este după :value', + 'modifiers_applies_are' => 'Următorii modificatorii sunt aplicați la căutare:', + 'general_search_error' => 'A apărut o eroare în timpul căutării. Verificați log-urile pentru mai multe informații.', + 'search_box' => 'Caută', + 'search_box_intro' => 'Bun venit la funcția de căutare. Introduceți interogarea dvs. în căsuță. Asigurați-vă că verificați fișierul de ajutor deoarece căutarea este destul de avansată.', + 'search_error' => 'Eroare la căutare', + 'search_searching' => 'Căutare ...', + 'search_results' => 'Rezultatele căutarii', + + // repeat frequencies: + 'repeat_freq_yearly' => 'anual', + 'repeat_freq_half-year' => 'fiecare jumătate de an', + 'repeat_freq_quarterly' => 'trimestrial', + 'repeat_freq_monthly' => 'lunar', + 'repeat_freq_weekly' => 'săptămânal', + 'weekly' => 'săptămânal', + 'quarterly' => 'trimestrial', + 'half-year' => 'fiecare jumătate de an', + 'yearly' => 'anual', + + // rules + 'rules' => 'Reguli', + 'rule_name' => 'Denumirea regulii', + 'rule_triggers' => 'Regulă se va declanșa când', + 'rule_actions' => 'Regulă va', + 'new_rule' => 'O nouă regulă', + 'new_rule_group' => 'Un nou grup de reguli', + 'rule_priority_up' => 'Acordați prioritate regulii', + 'rule_priority_down' => 'Acordați mai putină prioritate regulii', + 'make_new_rule_group' => 'Faceți un nou grup de reguli', + 'store_new_rule_group' => 'Păstrați un nou grup de reguli', + 'created_new_rule_group' => 'Grup nou de reguli ":title" stocat!', + 'updated_rule_group' => 'Grup de reguli actualizat cu succes ":title".', + 'edit_rule_group' => 'Editați grupul de reguli ":title"', + 'delete_rule_group' => 'Ștergeți grupul de reguli ":title"', + 'deleted_rule_group' => 'Grupul de reguli ":title" a fost șters', + 'update_rule_group' => 'Actualizați grupul de reguli', + 'no_rules_in_group' => 'Nu există reguli în acest grup', + 'move_rule_group_up' => 'Mutare grupul de reguli mai sus', + 'move_rule_group_down' => 'Mutare grupul de reguli mai jos', + 'save_rules_by_moving' => 'Salvați aceste reguli prin mutarea lor într-un alt grup de reguli', + 'make_new_rule' => 'Creați o nouă regulă în grupul de reguli ":title"', + 'make_new_rule_no_group' => 'Creați o nouă regulă', + 'instructions_rule_from_bill' => 'Pentru a potrivi tranzacția cu noua factură ":name", Firefly III poate crea o regulă care va fi verificată automat împotriva tranzacțiilor pe care le stocați. Verificați detaliile de mai jos și păstrați regula pentru ca Firefly III să potrivească automat tranzacțiile cu factura nouă.', + 'rule_is_strict' => 'regulă strictă', + 'rule_is_not_strict' => 'regulă nestrictă', + 'rule_help_stop_processing' => 'Când bifați această casetă, regulile ulterioare din acest grup nu vor fi executate.', + 'rule_help_strict' => 'În regulile stricte, toți declanșatorii trebuie să declanșeze acțiunea (acțiunile) care trebuie executate. În reguli nestricte, orice declanșator este suficient pentru acțiunea (acțiunile) care trebuie executată.', + 'rule_help_active' => 'Regulile noi nu vor fi niciodată declanșate.', + 'stored_new_rule' => 'S-a salvat o nouă regulă cu titlu ":title"', + 'deleted_rule' => 'S-a șters o regulă cu titlul ":title"', + 'store_new_rule' => 'Salvati regulă nouă', + 'updated_rule' => 'S-a actualizat o regulă cu titlul ":title"', + 'default_rule_group_name' => 'Reguli standard', + 'default_rule_group_description' => 'Toate regulile - nu sunt într-un anumit grup.', + 'default_rule_name' => 'Prima dvs. regulă implicită', + 'default_rule_description' => 'Această regulă este un exemplu. Puteți să o ștergeți în siguranță.', + 'default_rule_trigger_description' => 'Omul care a vandut lumea', + 'default_rule_trigger_from_account' => 'David Bowie', + 'default_rule_action_prepend' => 'A cumparat lumea de la', + 'default_rule_action_set_category' => 'Cheltuieli mari', + 'trigger' => 'Declanșator', + 'trigger_value' => 'Declanșare la valoarea', + 'stop_processing_other_triggers' => 'Opriți procesarea altor declanșatoare', + 'add_rule_trigger' => 'Adăugați un nou declanșator', + 'action' => 'Acțiune', + 'action_value' => 'Valoarea acțiunii', + 'stop_executing_other_actions' => 'Opriți executarea altor acțiuni', + 'add_rule_action' => 'Adăugați o acțiune nouă', + 'edit_rule' => 'Editați regula ":title"', + 'delete_rule' => 'Stergeți regula ":title"', + 'update_rule' => 'Actualizați regula', + 'test_rule_triggers' => 'Consultați tranzacțiile potrivite', + 'warning_transaction_subset' => 'Din motive de performanță, această listă este limitată la :max_num_transactions și poate afișa numai un subset de tranzacții care se potrivesc', + 'warning_no_matching_transactions' => 'Nu au fost găsite tranzacții. Rețineți că, din motive de performanță, au fost verificate numai ultimele operațiuni :num_transactions.', + 'warning_no_valid_triggers' => 'Nu au fost furnizate declanșatoare valide.', + 'apply_rule_selection' => 'Aplicați regula ":title" la o selecție a tranzacțiilor dvs.', + 'apply_rule_selection_intro' => 'Reguli de genul ":title" se aplică, în mod normal, tranzacțiilor noi sau actualizate, dar puteți să-i spuneți aplicației să o ruleze pe o selecție a tranzacțiilor existente. Acest lucru poate fi util atunci când ați actualizat o regulă și aveți nevoie de modificările care vor fi aplicate tuturor celorlalte tranzacții.', + 'include_transactions_from_accounts' => 'Includeți tranzacții din aceste conturi', + 'applied_rule_selection' => 'Regula ":title" a fost aplicată selecției dvs..', + 'execute' => 'Execută', + 'apply_rule_group_selection' => 'Aplicați grupul de reguli ":title" la o selecție a tranzacțiilor dvs.', + 'apply_rule_group_selection_intro' => 'Grupul de reguli precum ":title" se aplică, în mod normal, tranzacțiilor noi sau actualizate, însă puteți spune aplicației că rulează toate regulile din acest grup cu privire la o selecție a tranzacțiilor existente. Acest lucru poate fi util atunci când ați actualizat un grup de reguli și aveți nevoie de modificările care vor fi aplicate tuturor celorlalte tranzacții.', + 'applied_rule_group_selection' => 'Grupul de reguli ":title" a fost aplicat selecției dvs..', + + // actions and triggers + 'rule_trigger_user_action' => 'Acțiunea utilizatorului este ":trigger_value"', + 'rule_trigger_from_account_starts_choice' => 'Contul sursă începe cu..', + 'rule_trigger_from_account_starts' => 'Contul sursă începe cu ":trigger_value"', + 'rule_trigger_from_account_ends_choice' => 'Contul sursă se termină cu..', + 'rule_trigger_from_account_ends' => 'Contul sursă se termină cu ":trigger_value"', + 'rule_trigger_from_account_is_choice' => 'Contul sursă este..', + 'rule_trigger_from_account_is' => 'Contul sursă este ":trigger_value"', + 'rule_trigger_from_account_contains_choice' => 'Contul sursă conține..', + 'rule_trigger_from_account_contains' => 'Contul sursă conține ":trigger_value"', + 'rule_trigger_to_account_starts_choice' => 'Contul destinației începe cu..', + 'rule_trigger_to_account_starts' => 'Contul destinației începe cu ":trigger_value"', + 'rule_trigger_to_account_ends_choice' => 'Contul destinației se termină cu..', + 'rule_trigger_to_account_ends' => 'Contul destinației se termină cu ":trigger_value"', + 'rule_trigger_to_account_is_choice' => 'Contul destinației este..', + 'rule_trigger_to_account_is' => 'Contul destinației este ":trigger_value"', + 'rule_trigger_to_account_contains_choice' => 'Contul destinației conține..', + 'rule_trigger_to_account_contains' => 'Contul destinației conține ":trigger_value"', + 'rule_trigger_transaction_type_choice' => 'Tranzacția este de tip..', + 'rule_trigger_transaction_type' => 'Tranzacția este de tip ":trigger_value"', + 'rule_trigger_category_is_choice' => 'Categoria este..', + 'rule_trigger_category_is' => 'Categoria este ":trigger_value"', + 'rule_trigger_amount_less_choice' => 'Suma este mai mică decât..', + 'rule_trigger_amount_less' => 'Suma este mai mică decât :trigger_value', + 'rule_trigger_amount_exactly_choice' => 'Suma este..', + 'rule_trigger_amount_exactly' => 'Suma este :trigger_value', + 'rule_trigger_amount_more_choice' => 'Suma este mai mare decât..', + 'rule_trigger_amount_more' => 'Suma este mai mare decât :trigger_value', + 'rule_trigger_description_starts_choice' => 'Descrierea începe cu..', + 'rule_trigger_description_starts' => 'Descrierea începe cu ":trigger_value"', + 'rule_trigger_description_ends_choice' => 'Descrierea se termină cu..', + 'rule_trigger_description_ends' => 'Descrierea se termină cu ":trigger_value"', + 'rule_trigger_description_contains_choice' => 'Descrierea conține..', + 'rule_trigger_description_contains' => 'Descrierea conține ":trigger_value"', + 'rule_trigger_description_is_choice' => 'Descrierea este..', + 'rule_trigger_description_is' => 'Descrierea este ":trigger_value"', + 'rule_trigger_budget_is_choice' => 'Bugetul este..', + 'rule_trigger_budget_is' => 'Bugetul este ":trigger_value"', + 'rule_trigger_tag_is_choice' => 'O etichetă este..', + 'rule_trigger_tag_is' => 'O etichetă este ":trigger_value"', + 'rule_trigger_currency_is_choice' => 'Moneda tranzacției este..', + 'rule_trigger_currency_is' => 'Moneda tranzacției este ":trigger_value"', + 'rule_trigger_has_attachments_choice' => 'Are cel puțin atâtea atașamente', + 'rule_trigger_has_attachments' => 'Are cel puțin :trigger_value atașament(e)', + 'rule_trigger_store_journal' => 'Când este o tranzacție creată', + 'rule_trigger_update_journal' => 'Când este o tranzacție actualizată', + 'rule_trigger_has_no_category_choice' => 'Nu are nici o categorie', + 'rule_trigger_has_no_category' => 'Tranzacția nu are nici o categorie', + 'rule_trigger_has_any_category_choice' => 'Are o (orice) categorie', + 'rule_trigger_has_any_category' => 'Tranzacția are o (orice) categorie', + 'rule_trigger_has_no_budget_choice' => 'Nu are niciun buget', + 'rule_trigger_has_no_budget' => 'Tranzacția nu are niciun buget', + 'rule_trigger_has_any_budget_choice' => 'Are un (orice) buget', + 'rule_trigger_has_any_budget' => 'Tranzacția are un (orice) buget', + 'rule_trigger_has_no_tag_choice' => 'Nu are etichetă (e)', + 'rule_trigger_has_no_tag' => 'Tranzacția nu are etichetă (e)', + 'rule_trigger_has_any_tag_choice' => 'Are una sau mai multe etichete', + 'rule_trigger_has_any_tag' => 'Tranzacția are una sau mai multe etichete', + 'rule_trigger_any_notes_choice' => 'Are (orice) notițe', + 'rule_trigger_any_notes' => 'Tranzacția are (orice) notițe', + 'rule_trigger_no_notes_choice' => 'Nu are notițe', + 'rule_trigger_no_notes' => 'Tranzacția nu are notițe', + 'rule_trigger_notes_are_choice' => 'Notele sunt..', + 'rule_trigger_notes_are' => 'Notele sunt ":trigger_value"', + 'rule_trigger_notes_contain_choice' => 'Notele conțin..', + 'rule_trigger_notes_contain' => 'Notele conțin ":trigger_value"', + 'rule_trigger_notes_start_choice' => 'Notele încep cu..', + 'rule_trigger_notes_start' => 'Notele încep cu ":trigger_value"', + 'rule_trigger_notes_end_choice' => 'Notele se termină cu..', + 'rule_trigger_notes_end' => 'Notele se termină cu ":trigger_value"', + 'rule_action_set_category' => 'Setați categoria la ":action_value"', + 'rule_action_clear_category' => 'Șterge categorie', + 'rule_action_set_budget' => 'Setați bugetul la ":action_value"', + 'rule_action_clear_budget' => 'Ștergeți bugetul', + 'rule_action_add_tag' => 'Adaugă etichetă ":action_value"', + 'rule_action_remove_tag' => 'Eliminați eticheta ":action_value"', + 'rule_action_remove_all_tags' => 'Eliminați toate etichetele', + 'rule_action_set_description' => 'Setați descrierea la ":action_value"', + 'rule_action_append_description' => 'Adăugați descrierea cu ":action_value"', + 'rule_action_prepend_description' => 'Prefixați descrierea cu ":action_value"', + 'rule_action_set_category_choice' => 'Setați categoria la..', + 'rule_action_clear_category_choice' => 'Ștergeți any category', + 'rule_action_set_budget_choice' => 'Setați bugetul la..', + 'rule_action_clear_budget_choice' => 'Ștergeți any budget', + 'rule_action_add_tag_choice' => 'Adaugă etichetă..', + 'rule_action_remove_tag_choice' => 'Eliminați eticheta..', + 'rule_action_remove_all_tags_choice' => 'Eliminați toate etichetele', + 'rule_action_set_description_choice' => 'Setați descrierea la..', + 'rule_action_append_description_choice' => 'Adăugați descrierea cu..', + 'rule_action_prepend_description_choice' => 'Prefixați descrierea cu..', + 'rule_action_set_source_account_choice' => 'Setați contul sursă la...', + 'rule_action_set_source_account' => 'Setați contul sursă la :action_value', + 'rule_action_set_destination_account_choice' => 'Setați contul de destinație la...', + 'rule_action_set_destination_account' => 'Setați contul de destinație la :action_value', + 'rule_action_append_notes_choice' => 'Adăugați notițe cu..', + 'rule_action_append_notes' => 'Adăugați notițe cu ":action_value"', + 'rule_action_prepend_notes_choice' => 'Prefixați notițele cu..', + 'rule_action_prepend_notes' => 'Prefixați notițele cu ":action_value"', + 'rule_action_clear_notes_choice' => 'Eliminați orice notiță', + 'rule_action_clear_notes' => 'Eliminați orice notiță', + 'rule_action_set_notes_choice' => 'Setați notițele la..', + 'rule_action_link_to_bill_choice' => 'Legați la o factură..', + 'rule_action_link_to_bill' => 'Legați la factură ":action_value"', + 'rule_action_set_notes' => 'Setați notițele la ":action_value"', + 'rule_action_convert_deposit_choice' => 'Transformați tranzacția într-un depozit', + 'rule_action_convert_deposit' => 'Transformați tranzacția într-un depozit de la ":action_value"', + 'rule_action_convert_withdrawal_choice' => 'Transformați tranzacția într-o retragere', + 'rule_action_convert_withdrawal' => 'Transformați tranzacția într-o retragere la ":action_value"', + 'rule_action_convert_transfer_choice' => 'Transformați tranzacția într-un transfer', + 'rule_action_convert_transfer' => 'Transformați tranzacția într-un transfer cu ":action_value"', + + 'rules_have_read_warning' => 'Ați citit avertismentul?', + 'apply_rule_warning' => 'Avertisment: difuzarea unei reguli (grup) pe o selecție largă de tranzacții ar putea dura foarte mult și s-ar putea să renunțe. În caz contrar, regula (grupul) va fi aplicată numai unui subset necunoscut al tranzacțiilor dvs. Acest lucru ar putea să afecteze finanțele dvs. Vă rugăm să fiți atent.', + 'rulegroup_for_bills_title' => 'Grup de reguli pentru facturi', + 'rulegroup_for_bills_description' => 'Un grup de reguli special pentru toate regulile care implică facturile.', + 'rule_for_bill_title' => 'Regulă generată automat pentru factura ":name"', + 'rule_for_bill_description' => 'Această regulă este generată automat pentru a încerca să se potrivească cu factura ":name".', + 'create_rule_for_bill' => 'Creați o nouă regulă pentru factura ":name"', + 'create_rule_for_bill_txt' => 'Felicitări, tocmai ați creat o nouă factură numită ":name"! Firefly III poate să potrivească automat noi retrageri la acestă factură. De exemplu, ori de câte ori plătiți chiria, factura "chiria" va fi legată de cheltuială. În acest fel, Firefly III vă poate arăta cu exactitate care facturi sunt datorate și care nu sunt. Pentru a face acest lucru, trebuie creată o nouă regulă. Firefly III a completat câteva valori implicite pentru dvs. Asigurați-vă că acestea sunt corecte. Dacă aceste valori sunt corecte, Firefly III va conecta automat retragerea corectă la factura corectă. Verificați declanșatoarele pentru a vedea dacă acestea sunt corecte și adăugați altele dacă sunt greșite.', + 'new_rule_for_bill_title' => 'Regula privind factura ":name"', + 'new_rule_for_bill_description' => 'Această regulă marchează tranzacțiile pentru factura ":name".', + + // tags + 'store_new_tag' => 'Salvați o nouă etichetă', + 'update_tag' => 'Actualizați eticheta', + 'no_location_set' => 'Nu a fost setată nicio locație.', + 'meta_data' => 'Date meta', + 'location' => 'Locație', + 'without_date' => 'Fără data', + 'result' => 'Rezultat', + 'sums_apply_to_range' => 'Toate sumele se aplică gamei selectate', + 'mapbox_api_key' => 'Pentru a utiliza harta, obțineți o cheie API din Mapbox . Deschideți fișierul .env și introduceți acest cod după MAPBOX_API_KEY = .', + 'press_tag_location' => 'Faceți clic dreapta sau apăsați lung pentru a seta locația etichetei.', + 'clear_location' => 'Ștergeți locația', + + // preferences + 'pref_home_screen_accounts' => 'Ecranul de start al conturilor', + 'pref_home_screen_accounts_help' => 'Ce conturi ar trebui afișate pe pagina de pornire?', + 'pref_view_range' => 'Vedeți intervalul', + 'pref_view_range_help' => 'Anumite grafice sunt grupate automat în perioade. Bugeturile dvs. vor fi, de asemenea, grupate în perioade. Ce perioadă ați prefera?', + 'pref_1D' => 'O zi', + 'pref_1W' => 'O saptamână', + 'pref_1M' => 'O lună', + 'pref_3M' => 'Trei luni (trimestru)', + 'pref_6M' => 'Șase luni', + 'pref_1Y' => 'Un an', + 'pref_languages' => 'Limbi', + 'pref_languages_help' => 'Firefly III acceptă mai multe limbi. Pe care o preferați?', + 'pref_custom_fiscal_year' => 'Setări an fiscal', + 'pref_custom_fiscal_year_label' => 'Activat', + 'pref_custom_fiscal_year_help' => 'În țările care utilizează un exercițiu financiar, altul decât 1 ianuarie până la 31 decembrie, puteți să le activați și să specificați zilele de începere / sfârșit ale anului fiscal', + 'pref_fiscal_year_start_label' => 'Data de începere a anului fiscal', + 'pref_two_factor_auth' => 'Verificarea în doi pași', + 'pref_two_factor_auth_help' => 'Când activați verificarea în doi pași (cunoscută și ca autentificare cu doi factori), adăugați un nivel suplimentar de securitate în contul dvs. Conectați-vă cu ceva ce știți (parola dvs.) și ceva ce aveți (un cod de verificare). Codurile de verificare sunt generate de o aplicație de pe telefon, cum ar fi Authy sau Google Authenticator.', + 'pref_enable_two_factor_auth' => 'Activați verificarea în doi pași', + 'pref_two_factor_auth_disabled' => 'Cod de verificare în doi pași eliminat și dezactivat', + 'pref_two_factor_auth_remove_it' => 'Nu uitați să eliminați contul din aplicația dvs. de autentificare!', + 'pref_two_factor_auth_code' => 'Verificați codul', + 'pref_two_factor_auth_code_help' => 'Scanați codul QR cu o aplicație de pe telefon, cum ar fi Authy sau Google Authenticator și introduceți codul generat.', + 'pref_two_factor_auth_reset_code' => 'Resetați codul de verificare', + 'pref_two_factor_auth_disable_2fa' => 'Dezactivați autentificarea cu doi factori', + '2fa_use_secret_instead' => 'If you cannot scan the QR code, feel free to use the secret instead: :secret.', + '2fa_backup_codes' => 'Store these backup codes for access in case you lose your device.', + '2fa_already_enabled' => '2-step verification is already enabled.', + 'wrong_mfa_code' => 'This MFA code is not valid.', + 'pref_save_settings' => 'Salvează setările', + 'saved_preferences' => 'Preferințele sunt salvate!', + 'preferences_general' => 'Generale', + 'preferences_frontpage' => 'Ecranul de start', + 'preferences_security' => 'Securitate', + 'preferences_layout' => 'Schemă', + 'pref_home_show_deposits' => 'Afișați depozitele pe ecranul de pornire', + 'pref_home_show_deposits_info' => 'Ecranul de pornire vă arată deja cont de cheltuieli. Ar trebui să afișeze și conturile dvs. de venit?', + 'pref_home_do_show_deposits' => 'Da, arată-le', + 'successful_count' => 'din care :count au reușit', + 'list_page_size_title' => 'Mărimea paginii', + 'list_page_size_help' => 'Orice listă de lucruri (conturi, tranzacții etc.) arată cel mult atât de multe pe pagină.', + 'list_page_size_label' => 'Mărimea paginii', + 'between_dates' => '(:start și :end)', + 'pref_optional_fields_transaction' => 'Câmpuri opționale pentru tranzacții', + 'pref_optional_fields_transaction_help' => 'În mod prestabilit, toate câmpurile nu sunt activate atunci când creați o nouă tranzacție (din cauza aglomerării). Mai jos, puteți activa aceste câmpuri dacă credeți că acestea ar putea fi utile pentru dvs. Desigur, orice câmp care este dezactivat, dar deja completat, va fi vizibil indiferent de setare.', + 'optional_tj_date_fields' => 'Câmpurile de date', + 'optional_tj_business_fields' => 'Domenii de activitate', + 'optional_tj_attachment_fields' => 'Câmpuri de atașament', + 'pref_optional_tj_interest_date' => 'Data de interes', + 'pref_optional_tj_book_date' => 'Data revervării', + 'pref_optional_tj_process_date' => 'Data procesării', + 'pref_optional_tj_due_date' => 'Data scadentă', + 'pref_optional_tj_payment_date' => 'Data plății', + 'pref_optional_tj_invoice_date' => 'Data facturii', + 'pref_optional_tj_internal_reference' => 'Referință internă', + 'pref_optional_tj_notes' => 'Notițe', + 'pref_optional_tj_attachments' => 'Ataşamente', + 'optional_field_meta_dates' => 'Date', + 'optional_field_meta_business' => 'Afaceri', + 'optional_field_attachments' => 'Ataşamente', + 'optional_field_meta_data' => 'Meta date opționale', + + // profile: + 'change_your_password' => 'Schimbați-vă parola', + 'delete_account' => 'Șterge account', + 'current_password' => 'Parola actuală', + 'new_password' => 'Parola nouă', + 'new_password_again' => 'Parola nouă (din nou)', + 'delete_your_account' => 'Ștergeți contul dvs', + 'delete_your_account_help' => 'Ștergerea contului dvs. va șterge, de asemenea, orice cont, tranzacție, orice ați salvat în aplicație. Acestea vor dispărea.', + 'delete_your_account_password' => 'Introduceți parola pentru a continua.', + 'password' => 'Parola', + 'are_you_sure' => 'Esti sigur? Nu poți anula acest lucru.', + 'delete_account_button' => 'Ștergeți contul dvs', + 'invalid_current_password' => 'Parola curentă nevalidă!', + 'password_changed' => 'Parolă schimbată!', + 'should_change' => 'Ideea este să vă schimbați parola.', + 'invalid_password' => 'Parolă nevalidă!', + 'what_is_pw_security' => 'Ce este "verificarea securității parolei"?', + 'secure_pw_title' => 'Cum alegeți o parolă sigură', + 'secure_pw_history' => 'Nu trece nici săptămână și citiți în știri despre un site care pierde parolele utilizatorilor săi. Hackerii și hoții utilizează aceste parole pentru a încerca să vă fure informațiile private. Aceste informații sunt valoroase.', + 'secure_pw_ff' => 'Utilizați aceeași parolă pe tot internetul? Dacă un site pierde parola, hackerii au acces la toate datele. Firefly III se bazează pe dvs. pentru a alege o parolă puternică și unică pentru a vă proteja înregistrările financiare.', + 'secure_pw_check_box' => 'Pentru a vă ajuta să faceți acest lucru Firefly III poate verifica dacă parola pe care doriți să o utilizați a fost furată în trecut. Dacă este cazul, Firefly III vă sfătuiește NU să utilizați parola respectivă.', + 'secure_pw_working_title' => 'Cum funcționează?', + 'secure_pw_working' => 'Dacă bifați caseta, Firefly III va trimite primele cinci caractere ale hash-ului SHA1 al parolei dvs. la site-ul Troy Hunt pentru a vedea dacă este în listă. Acest lucru vă va împiedica să utilizați parole nesigure, așa cum se recomandă în cele mai recente articole de specialitate pe NIST pe acest subiect.', + 'secure_pw_should' => 'Ar trebui să bifez caseta??', + 'secure_pw_long_password' => 'Da. Verificați întotdeauna că parola este sigură.', + 'command_line_token' => 'Token-ul liniei de comandă', + 'explain_command_line_token' => 'Aveți nevoie de acest token pentru a efectua opțiunile din linie de comandă, cum ar fi importul sau exportul de date. Fără el, astfel de comenzi sensibile nu vor funcționa. Nu partajați token-ul. Nimeni nu vă va cere acest token, nici măcar această aplicație. Dacă vă temeți că ați pierdut acest lucru sau când sunteți paranoic, regenerați acest token folosind butonul.', + 'regenerate_command_line_token' => 'Regenerați token-ul liniei de comandă', + 'token_regenerated' => 'A fost generat un nou token de linie de comandă', + 'change_your_email' => 'Schimbați adresa dvs. de e-mail', + 'email_verification' => 'Va fi trimis un email pe vechea si noua adresă de e-mail. Din motive de securitate, nu veți putea să vă conectați până când nu vă confirmați noua adresă de e-mail. Dacă nu sunteți sigur că instalarea dvs. Firefly III este capabilă să trimită e-mailuri, vă rugăm să nu utilizați această caracteristică. Dacă sunteți administrator, puteți testa acest lucru în Admin .', + 'email_changed_logout' => 'Până când nu vă confirmați adresa de e-mail, nu vă puteți conecta.', + 'login_with_new_email' => 'Acum vă puteți conecta cu noua dvs. adresă de e-mail.', + 'login_with_old_email' => 'Acum puteți să vă conectați din nou cu vechea adresă de e-mail.', + 'login_provider_local_only' => 'Această acțiune nu este disponibilă când vă autentificați prin ":login_provider".', + 'delete_local_info_only' => 'Pentru că vă autentificați prin ":login_provider", acest lucru va șterge numai informațiile locale despre Firefly III.', + + // attachments + 'nr_of_attachments' => 'Un atașament|:count atașamente', + 'attachments' => 'Atașamente', + 'edit_attachment' => 'Editați attachment ":name"', + 'update_attachment' => 'Actualizați atașament', + 'delete_attachment' => 'Șterge atașament ":name"', + 'attachment_deleted' => 'Atașament ":name" șters', + 'liabilities_deleted' => 'Deleted liability ":name"', + 'attachment_updated' => 'Atașament ":name" actualizat', + 'upload_max_file_size' => 'Dimensiunea maximă a fișierului: :size', + 'list_all_attachments' => 'Lista tuturor atașamentelor', + + // transaction index + 'title_expenses' => 'Cheltuieli', + 'title_withdrawal' => 'Cheltuieli', + 'title_revenue' => 'Venituri', + 'title_deposit' => 'Venituri', + 'title_transfer' => 'Transferuri', + 'title_transfers' => 'Transferuri', + + // convert stuff: + 'convert_is_already_type_Withdrawal' => 'Această tranzacție este deja o retragere', + 'convert_is_already_type_Deposit' => 'Această tranzacție este deja un depozit', + 'convert_is_already_type_Transfer' => 'Această tranzacție este deja un transfer', + 'convert_to_Withdrawal' => 'Convertește ":description" la o retragere', + 'convert_to_Deposit' => 'Convertește ":description" la un depozit', + 'convert_to_Transfer' => 'Convertește ":description" la un transfer', + 'convert_options_WithdrawalDeposit' => 'Convertește o retragere intr-un depozit', + 'convert_options_WithdrawalTransfer' => 'Convertește o retragere intr-un transfer', + 'convert_options_DepositTransfer' => 'Convertește un depozit intr-un transfer', + 'convert_options_DepositWithdrawal' => 'Convertește un depozit intr-o retragere', + 'convert_options_TransferWithdrawal' => 'Convertește un transfer intr-o retragere', + 'convert_options_TransferDeposit' => 'Convertește un transfer intr-un depozit', + 'convert_Withdrawal_to_deposit' => 'Convertește această retragere la un depozit', + 'convert_Withdrawal_to_transfer' => 'Convertește această retragere la un transfer', + 'convert_Deposit_to_withdrawal' => 'Convertește aceast depozit la o retragere', + 'convert_Deposit_to_transfer' => 'Convertește această depozit la un transfer', + 'convert_Transfer_to_deposit' => 'Convertește aceast transfer la un depozit', + 'convert_Transfer_to_withdrawal' => 'Convertește aceast transfer la o retragere', + 'convert_please_set_revenue_source' => 'Alegeți un cont de venituri din care vor proveni banii.', + 'convert_please_set_asset_destination' => 'Alegeți un cont de active unde vor merge banii.', + 'convert_please_set_expense_destination' => 'Alegeți un cont de cheltuieli unde vor merge banii.', + 'convert_please_set_asset_source' => 'Alegeți un cont de active unde vor veni banii.', + 'convert_explanation_withdrawal_deposit' => 'Dacă convertiți această retragere în depozit, :amount va fi depus în :sourceName în loc să fie preluat din acesta.', + 'convert_explanation_withdrawal_transfer' => 'Dacă convertiți această retragere în transfer, :amount va fi transferată din :sourceName către un nou cont de activ, în loc să fie plătite pentru :destinationName .', + 'convert_explanation_deposit_withdrawal' => 'Dacă convertiți acest depozit intr-o retragere, :amount va fi eliminată din :destinationName în loc să fie adăugate la acestea.', + 'convert_explanation_deposit_transfer' => 'Dacă convertiți acest depozit intr-un transfer, :amount va fi transferată de la un cont de activ la alegere :destinationName.', + 'convert_explanation_transfer_withdrawal' => 'Dacă convertiți acest transfer intr-o retragere, :amount va merge de la :sourceName la o nouă destinație ca o cheltuială, în loc să meargă :destinationName ca trensfer.', + 'convert_explanation_transfer_deposit' => 'Dacă convertiți acest transfer intr-un depozit, :amount va fi depusă în contul :destinationName în loc să fie transferată acolo.', + 'converted_to_Withdrawal' => 'Tranzacția a fost transformată în retragere', + 'converted_to_Deposit' => 'Tranzacția a fost transformată în depozit', + 'converted_to_Transfer' => 'Tranzacția a fost transformată în transfer', + 'invalid_convert_selection' => 'Contul pe care l-ați selectat este deja utilizat în această tranzacție sau nu există.', + 'source_or_dest_invalid' => 'Nu se pot găsi detaliile corecte ale tranzacției. Conversia nu este posibilă.', + 'convert_to_withdrawal' => 'Convertește la retragere', + 'convert_to_deposit' => 'Convertește la depozit', + 'convert_to_transfer' => 'Convertește la transfer', + + // create new stuff: + 'create_new_withdrawal' => 'Creați o nouă retragere', + 'create_new_deposit' => 'Creați un nou depozit', + 'create_new_transfer' => 'Creați un nou transfer', + 'create_new_asset' => 'Creați un nou cont de active', + 'create_new_expense' => 'Creați un nou cont de cheltuieli', + 'create_new_revenue' => 'Creați un nou cont de venituri', + 'create_new_piggy_bank' => 'Creați o nouă pușculiță', + 'create_new_bill' => 'Creați o nouă factură', + + // currencies: + 'create_currency' => 'Creați o nouă monedă', + 'store_currency' => 'Salvați o nouă monedă', + 'update_currency' => 'Actualizați monedă', + 'new_default_currency' => ':name este acum moneda implicită.', + 'cannot_delete_currency' => 'Nu se poate șterge :nume deoarece este încă în uz.', + 'cannot_disable_currency' => 'Nu se poate dezactiva :name deoarece este încă în uz.', + 'deleted_currency' => 'Moneda :name stearsă', + 'created_currency' => 'Moneda :name creată', + 'could_not_store_currency' => 'Nu am putut stoca o nouă monedă.', + 'updated_currency' => 'Moneda :name actualizată', + 'ask_site_owner' => 'Vă rugăm să întrebați :owner să adăuge, elimine sau editeze valute.', + 'currencies_intro' => 'Firefly III acceptă diferite valute pe care le puteți seta și activa aici.', + 'make_default_currency' => 'Faceți implicit', + 'default_currency' => 'implicit', + 'currency_is_disabled' => 'Dezactivat', + 'enable_currency' => 'Activează', + 'disable_currency' => 'Dezactivează', + 'currencies_default_disabled' => 'Cele mai multe dintre aceste monede sunt dezactivate în mod prestabilit. Pentru a le folosi, trebuie să le activați mai întâi.', + 'currency_is_now_enabled' => 'Moneda ":name" a fost activată', + 'currency_is_now_disabled' => 'Moneda ":name" a fost dezactivată', + + // forms: + 'mandatoryFields' => 'Câmpuri obligatorii', + 'optionalFields' => 'Câmpuri opționale', + 'options' => 'Opțiuni', + + // budgets: + 'create_new_budget' => 'Creați un nou budget', + 'store_new_budget' => 'Salvați un nou budget', + 'stored_new_budget' => 'Bugetul ":name" a fost salvat', + 'available_between' => 'Disponibil între :start și :end', + 'transactionsWithoutBudget' => 'Cheltuieli fără buget', + 'transactions_no_budget' => 'Cheltuieli fără buget între :start și :end', + 'spent_between' => 'Cheltuit între :start și :end', + 'createBudget' => 'Buget nou', + 'inactiveBudgets' => 'Bugete inactive', + 'without_budget_between' => 'Tranzacții fără un buget între :start și :end', + 'delete_budget' => 'Șterge buget ":name"', + 'deleted_budget' => 'Buget ":name" șters', + 'edit_budget' => 'Editați buget ":name"', + 'updated_budget' => 'Buget ":name" actualizat', + 'update_amount' => 'Actualizați suma', + 'update_budget' => 'Actualizați bugetul', + 'update_budget_amount_range' => 'Actualizați suma disponibilă între :start și :end', + 'budget_period_navigator' => 'Navigator pe perioada', + 'info_on_available_amount' => 'Ce am disponibil?', + 'available_amount_indication' => 'Utilizați aceste sume pentru a obține o indicație cu privire la bugetul dvs. total.', + 'suggested' => 'Sugerat', + 'average_between' => 'Media între :start și :end', + 'over_budget_warn' => ' Usually you budget about :amount per day. This time it\'s :over_amount per day. Are you sure?', + 'transferred_in' => 'Transferred (in)', + 'transferred_away' => 'Transferred (away)', + + // bills: + 'match_between_amounts' => 'Factura se potrivește tranzacțiilor între :low și :high.', + 'bill_related_rules' => 'Reguli legate de această factură', + 'repeats' => 'Repetă', + 'connected_journals' => 'Tranzacții conectate', + 'auto_match_on' => 'Potrivire automată făcută de Firefly III', + 'auto_match_off' => 'Potrivirea nu este făcută automat de Firefly III', + 'next_expected_match' => 'Următoarea potrivire asteptată', + 'delete_bill' => 'Șterge factura ":name"', + 'deleted_bill' => 'Factura ":name" stearsă', + 'edit_bill' => 'Editați factura ":name"', + 'more' => 'Mai mult', + 'rescan_old' => 'Rulează din nou regulile pentru toate tranzacțiile', + 'update_bill' => 'Actualizați factura', + 'updated_bill' => 'Factura ":name" actualizată', + 'store_new_bill' => 'Salvați o nouă factură', + 'stored_new_bill' => 'Factura noua ":name" a fost salvată', + 'cannot_scan_inactive_bill' => 'Facturile inactive nu pot fi scanate.', + 'rescanned_bill' => 'Scanați din nou totul si and conectați :total tranzacții la factură.', + 'average_bill_amount_year' => 'Suma medie a facturilor (:year)', + 'average_bill_amount_overall' => 'Suma medie a facturilor (în ansamblu)', + 'bill_is_active' => 'Factura este activă', + 'bill_expected_between' => 'Așteptat între :start și :end', + 'bill_will_automatch' => 'Factura se va conecta automat la tranzacții ce se potrivesc', + 'skips_over' => 'sari peste', + 'bill_store_error' => 'A apărut o eroare neașteptată în timpul stocării facturii noi. Verificați log-urile', + 'list_inactive_rule' => 'regulă inactivă', + + // accounts: + 'account_missing_transaction' => 'Contul #:id (":name") nu pot fi vizualizate direct.', + 'details_for_asset' => 'Detalii pentru contul de active ":name"', + 'details_for_expense' => 'Detalii pentru contul de cheltuieli ":name"', + 'details_for_revenue' => 'Detalii pentru contul de venituri ":name"', + 'details_for_cash' => 'Detalii pentru contul de numerar ":name"', + 'store_new_asset_account' => 'Salvați un nou cont de active', + 'store_new_expense_account' => 'Salvați un nou cont de cheltuieli', + 'store_new_revenue_account' => 'Salvați un nou cont de venituri', + 'edit_asset_account' => 'Editați contul de active ":name"', + 'edit_expense_account' => 'Editați contul de cheltuieli ":name"', + 'edit_revenue_account' => 'Editați contul de venituri ":name"', + 'delete_asset_account' => 'Șterge contul de active ":name"', + 'delete_expense_account' => 'Șterge contul de cheltuieli ":name"', + 'delete_revenue_account' => 'Șterge contul de venituri ":name"', + 'delete_liabilities_account' => 'Șterge provizionul ":name"', + 'asset_deleted' => 'Contul de active ":name" a fost șters cu succes', + 'expense_deleted' => 'Contul de cheltuieli ":name" a fost șters cu succes', + 'revenue_deleted' => 'Contul de venituri ":name" a fost șters cu succes', + 'update_asset_account' => 'Actualizați contul de active', + 'update_liabilities_account' => 'Actualizați provizionul', + 'update_expense_account' => 'Actualizați cont de cheltuieli', + 'update_revenue_account' => 'Actualizați cont de venituri', + 'make_new_asset_account' => 'Creați un nou cont de active', + 'make_new_expense_account' => 'Creați un nou cont de cheltuieli', + 'make_new_revenue_account' => 'Creați un nou cont de venituri', + 'make_new_liabilities_account' => 'Creați un nou provizion', + 'asset_accounts' => 'Conturile de active', + 'expense_accounts' => 'Conturi de cheltuieli', + 'revenue_accounts' => 'Conturi de venituri', + 'cash_accounts' => 'Conturi de numerar', + 'Cash account' => 'Cont de numerar', + 'liabilities_accounts' => 'Provizioane', + 'reconcile_account' => 'Reconciliază/Potrivește contul ":account"', + 'overview_of_reconcile_modal' => 'Privire de ansamblu asupra reconcilierii', + 'delete_reconciliation' => 'Șterge reconcilierea', + 'update_reconciliation' => 'Actualizați reconcilierea', + 'amount_cannot_be_zero' => 'Suma nu poate fi zero', + 'end_of_reconcile_period' => 'Sfârșitul perioadei de reconciliere: :period', + 'start_of_reconcile_period' => 'Începutul perioadei de reconciliere: :period', + 'start_balance' => 'Sold de început', + 'end_balance' => 'Sold final', + 'update_balance_dates_instruction' => 'Potriviți sumele și datele de mai sus la extrasul de cont bancar și apăsați pe "Porniți reconcilierea"', + 'select_transactions_instruction' => 'Selectați tranzacțiile care apar în extrasul dvs. bancar.', + 'select_range_and_balance' => 'Mai întâi verificați intervalul de date și balanțele. Apoi apăsați "Porniți reconcilierea"', + 'date_change_instruction' => 'Dacă schimbați acum intervalul de date, orice progres va fi pierdut.', + 'update_selection' => 'Actualizați selectaia', + 'store_reconcile' => 'Salvați reconcilierea', + 'reconciliation_transaction' => 'Tranzacție de reconciliere', + 'Reconciliation' => 'Reconciliere', + 'reconciliation' => 'Reconciliere', + 'reconcile_options' => 'Optiuni reconciliere', + 'reconcile_range' => 'Domeniu de reconciliere', + 'start_reconcile' => 'Porniți reconcilierea', + 'cash_account_type' => 'Cash', + 'cash' => 'numerar', + 'account_type' => 'Tip cont', + 'save_transactions_by_moving' => 'Salvați aceste tranzacții prin mutarea acestora într-un alt cont:', + 'stored_new_account' => 'Cont nou ":name" salvat!', + 'updated_account' => 'Contul ":name" actualizat', + 'credit_card_options' => 'Opțiuni pentru carduri de credit', + 'no_transactions_account' => 'Nu există tranzacții (în această perioadă) pentru contul de activ ":name".', + 'no_transactions_period' => 'Nu există nicio tranzacție (in această perioadă).', + 'no_data_for_chart' => 'Nu există suficiente informații (încă) pentru a genera acest grafic.', + 'select_at_least_one_account' => 'Selectați cel puțin un cont de active', + 'select_at_least_one_category' => 'Selectați cel puțin o categorie', + 'select_at_least_one_budget' => 'Selectați cel puțin un buget', + 'select_at_least_one_tag' => 'Selectați cel puțin o eticheta', + 'select_at_least_one_expense' => 'Selectați cel puțin o combinație cont de cheltuieli/venituri. Dacă nu ai încă nici una (lista este goală) acest raport nu este disponibil.', + 'account_default_currency' => 'Aceasta va fi moneda implicită asociată acestui cont.', + 'reconcile_has_more' => 'Registrul dvs are mai mulți bani în ea decât banca dvs. susține că ar trebui să aveți. Există mai multe opțiuni. Alegeți ce să faceți. Apoi, apăsați "Confirmați reconcilierea".', + 'reconcile_has_less' => 'Registrul dvs are mai puțini bani în ea decât banca dvs. susține că ar trebui să aveți. Există mai multe opțiuni. Alegeți ce să faceți. Apoi, apăsați "Confirmați reconcilierea".', + 'reconcile_is_equal' => 'Registrul si declarațiile dvs. de cont se potrivesc. Nu e nimic de facut. Te rog apasă "Confirmați reconcilierea" to confirm your input.', + 'create_pos_reconcile_transaction' => 'Goliți tranzacțiile selectate și creați o corecție adăugând :amount la acest cont de active.', + 'create_neg_reconcile_transaction' => 'Goliți tranzacțiile selectate și creați o corecție ștergând :amount din acest cont de active.', + 'reconcile_do_nothing' => 'Goliți tranzacțiile selectate, dar nu corectați.', + 'reconcile_go_back' => 'Puteți modifica sau șterge întotdeauna o corecție mai târziu.', + 'must_be_asset_account' => 'Puteți reconcilia numai contul de active', + 'reconciliation_stored' => 'Reconciliere salvată', + 'reconciliation_error' => 'Due to an error the transactions were marked as reconciled but the correction has not been stored: :error.', + 'reconciliation_transaction_title' => 'Reconciliation (:from to :to)', + 'sum_of_reconciliation' => 'Sum of reconciliation', + 'reconcile_this_account' => 'Reconciliați acest cont', + 'confirm_reconciliation' => 'Confirmați reconcilierea', + 'submitted_start_balance' => 'Balanța inițială afișată', + 'selected_transactions' => 'Tranzacții selectate (:count)', + 'already_cleared_transactions' => 'Au fost deja tranzacții eliminate (:count)', + 'submitted_end_balance' => 'Sold final afițat', + 'initial_balance_description' => 'Sold inițial pentru ":account"', + 'interest_calc_' => 'necunoscut', + 'interest_calc_daily' => 'Pe zi', + 'interest_calc_monthly' => 'Pe lună', + 'interest_calc_yearly' => 'Pe an', + 'initial_balance_account' => 'Initial balance account of :account', + + // categories: + 'new_category' => 'Categorie nouă', + 'create_new_category' => 'Creați o nouă categorie', + 'without_category' => 'Fară o categorie', + 'update_category' => 'Actualizați categoria', + 'updated_category' => 'Categorie ":name" actualizată', + 'categories' => 'Categorii', + 'edit_category' => 'Editați categoria ":name"', + 'no_category' => '(nici o categorie)', + 'category' => 'Categorie', + 'delete_category' => 'Șterge categoria ":name"', + 'deleted_category' => 'Categorie ":name" ștearsă', + 'store_category' => 'Salvați o nouă categorie', + 'stored_category' => 'Categorie nouă ":name" salvată', + 'without_category_between' => 'Fără categorie între :start și :end', + + // transactions: + 'update_withdrawal' => 'Actualizați retragere', + 'update_deposit' => 'Actualizați depozit', + 'update_transfer' => 'Actualizați transfer', + 'updated_withdrawal' => 'Retragerea ":description" actualizată', + 'updated_deposit' => 'Depozitul ":description" actualizat', + 'updated_transfer' => 'Transferul ":description" actualizat', + 'delete_withdrawal' => 'Șterge retragere ":description"', + 'delete_deposit' => 'Șterge depozit ":description"', + 'delete_transfer' => 'Șterge transfer ":description"', + 'deleted_withdrawal' => 'Retragerea ":description" ștearsă cu succes', + 'deleted_deposit' => 'Depozitul ":description" șters cu succes', + 'deleted_transfer' => 'Transferul ":description" șters cu succes', + 'stored_journal' => 'A fost creată cu succes o tranzacție nouă ":description"', + 'stored_journal_no_descr' => 'Tranzacția s-a creat cu succes', + 'updated_journal_no_descr' => 'Successfully updated your transaction', + 'select_transactions' => 'Selectați tranzacțiile', + 'rule_group_select_transactions' => 'Aplică ":title" la tranzacții', + 'rule_select_transactions' => 'Aplică ":title" la tranzacții', + 'stop_selection' => 'Opriți selectarea tranzacțiilor', + 'reconcile_selected' => 'Reconcilia', + 'mass_delete_journals' => 'Ștergeți un număr de tranzacții', + 'mass_edit_journals' => 'Editați un număr de tranzacții', + 'mass_bulk_journals' => 'Editarea în bloc un număr de tranzacții', + 'mass_bulk_journals_explain' => 'This form allows you to change properties of the transactions listed below in one sweeping update. All the transactions in the table will be updated when you change the parameters you see here.', + 'part_of_split' => 'This transaction is part of a split transaction. If you have not selected all the splits, you may end up with changing only half the transaction.', + 'bulk_set_new_values' => 'Utilizați input-urile de mai jos pentru a seta noi valori. Dacă le lăsați goale, vor fi goale toate. De asemenea, rețineți că doar bugetele vor primi un buget.', + 'no_bulk_category' => 'Nu actualizați categoria', + 'no_bulk_budget' => 'Nu actualizați budgetul', + 'no_bulk_tags' => 'Nu actualizați etichetă(e) ', + 'mass_edit' => 'Edit selected individually', + 'bulk_edit' => 'Edit selected in bulk', + 'mass_delete' => 'Delete selected', + 'cannot_edit_other_fields' => 'Nu poți edita alte câmpuri decât cele de aici, pentru că nu există loc pentru a le arăta. Urmați linkul și editați-l câte unul, dacă aveți nevoie să editați aceste câmpuri.', + 'cannot_change_amount_reconciled' => 'You can\'t change the amount of reconciled transactions.', + 'no_budget' => '(nici un buget)', + 'no_budget_squared' => '(nici un buget)', + 'perm-delete-many' => 'Deleting many items in one go can be very disruptive. Please be cautious. You can delete part of a split transaction from this page, so take care.', + 'mass_deleted_transactions_success' => 'S-au șters :amount tranzacții.', + 'mass_edited_transactions_success' => 'S-au actualizat :amount tranzacții', + 'opt_group_' => '(niciun tip de cont)', + 'opt_group_no_account_type' => '(niciun tip de cont)', + 'opt_group_defaultAsset' => 'Ccont de active implicit', + 'opt_group_savingAsset' => 'Cont de economii', + 'opt_group_sharedAsset' => 'Cont de active partajat', + 'opt_group_ccAsset' => 'Carduri de credit', + 'opt_group_cashWalletAsset' => 'Cash - Numerar', + 'opt_group_expense_account' => 'Expense accounts', + 'opt_group_revenue_account' => 'Revenue accounts', + 'opt_group_l_Loan' => 'Provizion: Împrumut', + 'opt_group_cash_account' => 'Cash account', + 'opt_group_l_Debt' => 'Provizion: Datorie', + 'opt_group_l_Mortgage' => 'Provizion: Credit ipotecar', + 'opt_group_l_Credit card' => 'Provizion: Card de credit', + 'notes' => 'Notițe', + 'unknown_journal_error' => 'Nu a putut fi stocată tranzacția. Te rog verifică log-urile.', + 'attachment_not_found' => 'Acest atașament nu a putut fi găsit.', + 'journal_link_bill' => 'Această tranzacție este legată de factura :nume . Pentru a elimina conexiunea, debifați caseta de selectare. Utilizați regulile pentru conectarea la o altă factură.', + + // new user: + 'welcome' => 'Bine ați venit!', + 'submit' => 'Trimite', + 'submit_yes_really' => 'Trimite (Știu ce fac)', + 'getting_started' => 'Introducere', + 'to_get_started' => 'Este bine să vedeți că ați instalat cu succes Firefly III. Pentru a începe cu acest instrument, introduceți numele băncii dvs. și soldul contului de control principal. Nu vă faceți griji încă dacă aveți mai multe conturi. Puteți să le adăugați mai târziu. Firefly III are nevoie de ceva de început.', + 'savings_balance_text' => 'Firefly III va crea automat un cont de economii pentru dvs. În mod implicit, în contul dvs. de economii nu vor mai fi bani, dar dacă scrii balanța, acesta va fi salvat.', + 'finish_up_new_user' => 'Asta e! Puteți continua apăsând pe Trimiteți . Veți fi duși la indexul Firefly III.', + 'stored_new_accounts_new_user' => 'Ura! Conturile dvs. noi au fost salvate.', + 'set_preferred_language' => 'Dacă preferați să utilizați Firefly III într-o altă limbă, vă rugăm să indicați aici.', + 'language' => 'Limbă', + 'new_savings_account' => ':bank_name cont de economii', + 'cash_wallet' => 'Portofel în numerar', + 'currency_not_present' => 'Dacă moneda pe care o utilizați în mod normal nu este listată, nu vă faceți griji. Puteți crea propriile valute în Opțiuni> Monede.', + + // home page: + 'yourAccounts' => 'Conturile dvs.', + 'your_accounts' => 'Prezentarea generală a contului', + 'category_overview' => 'Categorie - prezentare generală', + 'expense_overview' => 'Prezentare generală cont cheltuială', + 'revenue_overview' => 'Prezentare generală cont venituri', + 'budgetsAndSpending' => 'Bugetele și cheltuielile', + 'budgets_and_spending' => 'Bugete și cheltuieli', + 'go_to_budget' => 'Mergi la buget "{budget}"', + 'savings' => 'Economii', + 'newWithdrawal' => 'Cheltuieli noi', + 'newDeposit' => 'Depozit nou', + 'newTransfer' => 'Transfer nou', + 'bills_to_pay' => 'Facturile de plată', + 'per_day' => 'Pe zi', + 'left_to_spend_per_day' => 'Rămas de cheltui pe zi', + 'bills_paid' => 'Facturile plătite', + + // menu and titles, should be recycled as often as possible: + 'currency' => 'Monedă', + 'preferences' => 'Preferințe', + 'logout' => 'Ieșire', + 'toggleNavigation' => 'Navigare', + 'searchPlaceholder' => 'Cautare...', + 'version' => 'Versiunea', + 'dashboard' => 'Panou de control', + 'available_budget' => 'Buget disponibil ({currency})', + 'currencies' => 'Monede', + 'activity' => 'Activitate', + 'usage' => 'Utilizare', + 'accounts' => 'Conturi', + 'Asset account' => 'Cont de active', + 'Default account' => 'Cont activ principal', + 'Expense account' => 'Cont de cheltuieli', + 'Revenue account' => 'Contul de venituri', + 'Initial balance account' => 'Sold cont inițial', + 'account_type_Debt' => 'Datorie', + 'account_type_Loan' => 'Împrumut', + 'account_type_Mortgage' => 'Credit ipotecar', + 'account_type_Credit card' => 'Card de credit', + 'budgets' => 'Buget', + 'tags' => 'Etichete', + 'reports' => 'Rapoarte', + 'transactions' => 'Tranzacții', + 'expenses' => 'Cheltuieli', + 'income' => 'Venituri', + 'transfers' => 'Transferuri', + 'moneyManagement' => 'Gestionarea banilor', + 'money_management' => 'Gestionarea banilor', + 'tools' => 'Instrumente', + 'piggyBanks' => 'Pușculiță', + 'piggy_banks' => 'Pușculiță', + 'amount_x_of_y' => '{current} din {total}', + 'bills' => 'Facturi', + 'withdrawal' => 'Retragere', + 'opening_balance' => 'Soldul de deschidere', + 'deposit' => 'Depozit', + 'account' => 'Cont', + 'transfer' => 'Transfer', + 'Withdrawal' => 'Retragere', + 'Deposit' => 'Depozit', + 'Transfer' => 'Transfer', + 'bill' => 'Factură', + 'yes' => 'Da', + 'no' => 'Nu', + 'amount' => 'Sumă', + 'overview' => 'Imagine de ansamblu', + 'saveOnAccount' => 'Salvați în cont', + 'unknown' => 'Necunoscut', + 'daily' => 'Zilnic', + 'monthly' => 'Lunar', + 'profile' => 'Profil', + 'errors' => 'Erori', + 'debt_start_date' => 'Data de începere a datoriilor', + 'debt_start_amount' => 'Valoarea inițială a datoriei', + 'debt_start_amount_help' => 'If you owe an amount its best to enter a negative amount, because it influences your net worth. If you\'re owed an amount the same applies. Check out the help pages for more information.', + 'store_new_liabilities_account' => 'Salvați provizion nou', + 'edit_liabilities_account' => 'Editați provizion ":name"', + + // reports: + 'report_default' => 'Raportul financiar prestabilit între :start și :end', + 'report_audit' => 'Afișarea istoricului tranzacțiilor între :start și :end', + 'report_category' => 'Raport privind categoria între :start și :end', + 'report_account' => 'Raport privind contul de cheltuieli / venituri între :start și :end', + 'report_budget' => 'Raport privind bugetul între :start și :end', + 'report_tag' => 'Raport privind etichetele între :start și :end', + 'quick_link_reports' => 'Link-uri rapide', + 'quick_link_examples' => 'Acestea sunt doar câteva exemple de linkuri pentru a începe. Consultați paginile de ajutor de la butonul (?) pentru informații despre toate rapoartele și cuvintele magice pe care le puteți utiliza.', + 'quick_link_default_report' => 'Raportul financiar prestabilit', + 'quick_link_audit_report' => 'Afișarea istoricului tranzacțiilor', + 'report_this_month_quick' => 'Luna curentă, toate conturile', + 'report_last_month_quick' => 'Luna curentă, toate conturile', + 'report_this_year_quick' => 'Anul curent, toate conturile', + 'report_this_fiscal_year_quick' => 'Anul fiscal curent, toate conturile', + 'report_all_time_quick' => 'Tot timpul, toate conturile', + 'reports_can_bookmark' => 'Rețineți că rapoartele pot fi marcate ca favorite.', + 'incomeVsExpenses' => 'Venituri vs. cheltuieli', + 'accountBalances' => 'Solduri de cont', + 'balanceStart' => 'Sold la începutul perioadei', + 'balanceEnd' => 'Sold la sfârșitul perioadei', + 'splitByAccount' => 'Împărțire după cont', + 'coveredWithTags' => 'Acoperite cu etichete', + 'leftInBudget' => 'Rămasă în bugetul', + 'sumOfSums' => 'Suma sumelor', + 'noCategory' => '(nici o categorie)', + 'notCharged' => 'Nu este taxat (încă)', + 'inactive' => 'Inactiv', + 'active' => 'Activ', + 'difference' => 'Diferență', + 'money_flowing_in' => 'În', + 'money_flowing_out' => 'Afară', + 'topX' => 'top :number', + 'show_full_list' => 'Afișați întreaga listă', + 'show_only_top' => 'Afișează numai topul :number', + 'report_type' => 'Tip de raport', + 'report_type_default' => 'Raportul financiar prestabilit', + 'report_type_audit' => 'Afișarea istoricului tranzacțiilor (audit)', + 'report_type_category' => 'Raport privind categoria', + 'report_type_budget' => 'Raport privind bugetul', + 'report_type_tag' => 'Raport privind etichetele', + 'report_type_account' => 'Raport privind contul de cheltuieli / venituri', + 'more_info_help' => 'Mai multe informații despre aceste tipuri de rapoarte pot fi găsite în paginile de ajutor. Apăsați pictograma (?) Din colțul din dreapta sus.', + 'report_included_accounts' => 'Conturi incluse', + 'report_date_range' => 'Interval de date', + 'report_preset_ranges' => 'Valori prestabilite', + 'shared' => 'Partajate', + 'fiscal_year' => 'An fiscal', + 'income_entry' => 'Venituri din cont ":name" între :start și :end', + 'expense_entry' => 'Cheltuieli în cont ":name" între :start și :end', + 'category_entry' => 'Cheltuieli în categoria ":name" între :start și :end', + 'budget_spent_amount' => 'Cheltuieli în bugetul ":budget" între :start și :end', + 'balance_amount' => 'Cheltuieli în bugetul ":budget" plătit din cont ":account" între :start și :end', + 'no_audit_activity' => 'Nu a fost înregistrată nici o activitate în contul :account_name între :start și :end.', + 'audit_end_balance' => 'Soldul contului :account_name la sfârșitul :end a fost: :balance', + 'reports_extra_options' => 'Opțiuni suplimentare', + 'report_has_no_extra_options' => 'Acest raport nu are opțiuni suplimentare', + 'reports_submit' => 'Vizualizează raportul', + 'end_after_start_date' => 'Data de încheiere a raportului trebuie să fie după data de începere.', + 'select_category' => 'Selectați categoria (categoriile)', + 'select_budget' => 'Selectați bugetul (bugetele).', + 'select_tag' => 'Selectați eticheta (-urile).', + 'income_per_category' => 'Venituri pe categorie', + 'expense_per_category' => 'Cheltuială pe categorie', + 'expense_per_budget' => 'Cheltuială pe buget', + 'income_per_account' => 'Venituri pe cont', + 'expense_per_account' => 'Cheltuială pe cont', + 'expense_per_tag' => 'Cheltuială pe eticheta', + 'income_per_tag' => 'Venituri pe eticheta', + 'include_expense_not_in_budget' => 'Cheltuielile incluse nu sunt în bugetul (bugetele) selectat (e)', + 'include_expense_not_in_account' => 'Cheltuielile incluse nu sunt în contul (conturile) selectat (e)', + 'include_expense_not_in_category' => 'Cheltuielile incluse nu sunt în categoria (categoriile) selectat (e)', + 'include_income_not_in_category' => 'Veniturile incluse nu sunt în categoria (categoriile) selectat (e)', + 'include_income_not_in_account' => 'Veniturile incluse nu sunt în contul (conturile) selectat (e)', + 'include_income_not_in_tags' => 'Veniturile incluse nu sunt în etichetă(e) selectat (e)', + 'include_expense_not_in_tags' => 'Cheltuielile incluse nu sunt în etichete', + 'everything_else' => 'Orice altceva', + 'income_and_expenses' => 'Venituri și cheltuieli', + 'spent_average' => 'Cheltuit (in medie)', + 'income_average' => 'Venituri (in medie)', + 'transaction_count' => 'Numărul de tranzacții', + 'average_spending_per_account' => 'Cheltuielile medii pe cont', + 'average_income_per_account' => 'Venitul mediu pe cont', + 'total' => 'Total', + 'description' => 'Descriere', + 'sum_of_period' => 'Suma perioadei', + 'average_in_period' => 'Media în perioada', + 'account_role_defaultAsset' => 'Contul implicit activ', + 'account_role_sharedAsset' => 'Contul de active partajat', + 'account_role_savingAsset' => 'Cont de economii', + 'account_role_ccAsset' => 'Card de credit', + 'account_role_cashWalletAsset' => 'Cash - Numerar', + 'budget_chart_click' => 'Faceți clic pe un nume de buget din tabelul de mai sus pentru a vedea o grafic.', + 'category_chart_click' => 'Clic pe numele unei categorii din tabelul de mai sus pentru a vedea un grafic.', + 'in_out_accounts' => 'Câștigat și cheltuit pe combinație', + 'in_out_per_category' => 'Câștigat și cheltuit pe categorie', + 'out_per_budget' => 'Cheltuit pe buget', + 'select_expense_revenue' => 'Selectați contul de cheltuieli / venituri', + 'multi_currency_report_sum' => 'Deoarece această listă conține conturi cu mai multe valute, suma (sumele) pe care o vedeți este posibil să nu aibă sens. Raportul va reveni întotdeauna la moneda dvs. prestabilită.', + 'sum_in_default_currency' => 'Suma va fi întotdeauna în moneda dvs. prestabilită.', + 'net_filtered_prefs' => 'Acest grafic nu va include niciodată conturi care nu au selectat opțiunea "Include în valoare netă".', + + // charts: + 'chart' => 'Grafic', + 'month' => 'Lună', + 'budget' => 'Buget', + 'spent' => 'Cheltuit', + 'spent_in_budget' => 'Cheltuit în budget', + 'left_to_spend' => 'Ramas de cheltuit', + 'earned' => 'Câștigat', + 'overspent' => 'Depășire de buget', + 'left' => 'Rămas', + 'max-amount' => 'Sumă maximă', + 'min-amount' => 'Sumă minimă', + 'journal-amount' => 'Intrare factură curentă', + 'name' => 'Nume', + 'date' => 'Dată', + 'paid' => 'Plătit', + 'unpaid' => 'Neplătit', + 'day' => 'Zi', + 'budgeted' => 'Bugetat', + 'period' => 'Perioada', + 'balance' => 'Balantă', + 'sum' => 'Sumă', + 'summary' => 'Rezumat', + 'average' => 'In medie', + 'balanceFor' => 'Balanta pentru :name', + 'no_tags_for_cloud' => 'Nu există etichete pentru a genera un cloud', + 'tag_cloud' => 'Nor de etichete', + + // piggy banks: + 'add_money_to_piggy' => 'Adăugați bani la pușculiță ":name"', + 'piggy_bank' => 'Pușculiță', + 'new_piggy_bank' => 'Pușculiță nouă', + 'store_piggy_bank' => 'Salvați pușculița', + 'stored_piggy_bank' => 'Pușculița ":name" a fost salvată', + 'account_status' => 'Starea contului', + 'left_for_piggy_banks' => 'Rămas pentru pușculiță', + 'sum_of_piggy_banks' => 'Suma pușculiței', + 'saved_so_far' => 'Salvat până acum', + 'left_to_save' => 'Rămas de salvat', + 'suggested_amount' => 'Sumă lunară sugerată pentru salvare', + 'add_money_to_piggy_title' => 'Adăugați bani la pușculița ":name"', + 'remove_money_from_piggy_title' => 'Scoateți bani de la pușculița ":name"', + 'add' => 'Adaugă', + 'no_money_for_piggy' => 'Nu ai încă bani de pus în această pușculiță.', + 'suggested_savings_per_month' => 'Sugerat pe lună', + + 'remove' => 'Elimină', + 'max_amount_add' => 'Suma maximă pe care o puteți adăuga este', + 'max_amount_remove' => 'Suma maximă pe care o puteți elimina este', + 'update_piggy_button' => 'Actualizați pușculiță', + 'update_piggy_title' => 'Actualizați pușculița ":name"', + 'updated_piggy_bank' => 'Pușculiță ":name" a fost actualizată', + 'details' => 'Detalii', + 'events' => 'Evenimente', + 'target_amount' => 'Sumă țintă', + 'start_date' => 'Data de început', + 'no_start_date' => 'Nu există o dată de începere', + 'target_date' => 'Data țintă', + 'no_target_date' => 'Nu există o dată vizată', + 'table' => 'Tabel', + 'delete_piggy_bank' => 'Șterge pușculița ":name"', + 'cannot_add_amount_piggy' => 'Nu s-a putut adăuga :amount la ":name".', + 'cannot_remove_from_piggy' => 'Nu s-a putut elimina :amount din ":name".', + 'deleted_piggy_bank' => 'Pușculița ":name" a fost ștearsă', + 'added_amount_to_piggy' => 'Adăugat :amount la ":name"', + 'removed_amount_from_piggy' => 'Eliminat :amount din ":name"', + 'piggy_events' => 'Pușculițe asociate', + + // tags + 'delete_tag' => 'Șterge eticheta ":tag"', + 'deleted_tag' => 'Eticheta ":tag" a fost ștearsă', + 'new_tag' => 'Faceți o nouă eticheta', + 'edit_tag' => 'Editați eticheta ":tag"', + 'updated_tag' => 'Eticheta ":tag" a fost actualizat', + 'created_tag' => 'Eticheta ":tag" a fost creat!', + + 'transaction_journal_information' => 'Informații despre tranzacții', + 'transaction_journal_meta' => 'Informații meta', + 'transaction_journal_more' => 'Mai multe informaţii', + 'att_part_of_journal' => 'Salvat în ":journal"', + 'total_amount' => 'Valoare totală', + 'number_of_decimals' => 'Număr de zecimale', + + // administration + 'administration' => 'Administrare', + 'user_administration' => 'Administrarea utilizatorilor', + 'list_all_users' => 'Toți utilizatorii', + 'all_users' => 'Toți utilizatorii', + 'instance_configuration' => 'Configurare', + 'firefly_instance_configuration' => 'Opțiuni configurare', + 'setting_single_user_mode' => 'Mod de utilizator unic', + 'setting_single_user_mode_explain' => 'În mod implicit, Firefly III acceptă numai o înregistrare: dvs. Aceasta este o măsură de securitate, împiedicând pe alții să vă folosească instanța dacă nu le permiteți. Inregistrările viitoare sunt blocate. Când debifați această casetă, alții vă pot folosi și instanța, presupunând că pot ajunge la ea (când este conectată la internet).', + 'store_configuration' => 'Salvați configurarea', + 'single_user_administration' => 'Administrare utilizator pentru :email', + 'edit_user' => 'Editați user :email', + 'hidden_fields_preferences' => 'Aveți mai multe opțiuni pentru tranzacții în setări.', + 'user_data_information' => 'Datele utilizatorului', + 'user_information' => 'Informații utilizator', + 'total_size' => 'marimea totală', + 'budget_or_budgets' => 'buget(e)', + 'budgets_with_limits' => 'buget(e) cu o sumă configurată', + 'nr_of_rules_in_total_groups' => ':count_rules regulă(reguli) în :count_groups grup (uri) de reguli', + 'tag_or_tags' => 'etichete', + 'configuration_updated' => 'Configurația a fost actualizată', + 'setting_is_demo_site' => 'Site-ul demo', + 'setting_is_demo_site_explain' => 'Dacă bifați această casetă, această instalare se va comporta ca și cum ar fi site-ul demo, care poate avea efecte secundare ciudate.', + 'block_code_bounced' => 'Mesaje e-mail (uri) returnate', + 'block_code_expired' => 'Contul Demo a expirat', + 'no_block_code' => 'Nu există motive pentru blocarea sau blocarea utilizatorului', + 'block_code_email_changed' => 'Utilizatorul nu a confirmat încă nicio adresă de e-mail nouă', + 'admin_update_email' => 'Contrar paginii de profil, utilizatorul NU va fi anunțat că adresa sa de email a fost modificată!', + 'update_user' => 'Actualizați user', + 'updated_user' => 'Datele utilizatorilor au fost modificate.', + 'delete_user' => 'Șterge user :email', + 'user_deleted' => 'Utilizatorul a fost șters', + 'send_test_email' => 'Trimiteți mesajul de e-mail test', + 'send_test_email_text' => 'Pentru a vedea dacă instalarea dvs. este capabilă să trimită un e-mail, vă rugăm să apăsați acest buton. Nu veți vedea o eroare aici (dacă există), log-urile vor reflecta orice eroare . Puteți apăsa acest buton ori de câte ori doriți. Nu există niciun control spam. Mesajul va fi trimis la :email și ar trebui să sosească în scurt timp.', + 'send_message' => 'Trimite mesaj', + 'send_test_triggered' => 'Testul a fost declanșat. Verificați mesajele primite și log-urile.', + + 'split_transaction_title' => 'Description of the split transaction', + 'split_title_help' => 'If you create a split transaction, there must be a global description for all splits of the transaction.', + 'transaction_information' => 'Transaction information', + 'you_create_transfer' => 'You\'re creating a transfer.', + 'you_create_withdrawal' => 'You\'re creating a withdrawal.', + 'you_create_deposit' => 'You\'re creating a deposit.', + + + // links + 'journal_link_configuration' => 'Configurare link-uri de tranzacție', + 'create_new_link_type' => 'Creați un nou tip de legătură', + 'store_new_link_type' => 'Salvați un nou tip de legătură', + 'update_link_type' => 'Actualizați tip de legătură', + 'edit_link_type' => 'Editați tip de legătură ":name"', + 'updated_link_type' => 'Tip de legătură ":name" actualizat', + 'delete_link_type' => 'Șterge tip de legătură ":name"', + 'deleted_link_type' => 'Tip de legătură ":name" șters', + 'stored_new_link_type' => 'Salvați new tip de legătură ":name"', + 'cannot_edit_link_type' => 'Nu se poate edita tipul de legătură ":name"', + 'link_type_help_name' => 'Ex. "Dubluri"', + 'link_type_help_inward' => 'Ex. "dubluri"', + 'link_type_help_outward' => 'Ex. "este duplicat de către"', + 'save_connections_by_moving' => 'Salvați legătura dintre aceste tranzacții prin mutarea acestora în alt tip de legătură:', + 'do_not_save_connection' => '(nu salvați conexiunea)', + 'link_transaction' => 'Link tranzacție', + 'link_to_other_transaction' => 'Conectați această tranzacție la o altă tranzacție', + 'select_transaction_to_link' => 'Select a transaction to link this transaction to. The links are currently unused in Firefly III (apart from being shown), but I plan to change this in the future. Use the search box to select a transaction either by title or by ID. If you want to add custom link types, check out the administration section.', + 'this_transaction' => 'Această tranzacție', + 'transaction' => 'Tranzacţie', + 'comments' => 'Comentarii', + 'link_notes' => 'Any notes you wish to store with the link.', + 'invalid_link_selection' => 'Nu se poate lega aceste tranzacții', + 'selected_transaction' => 'Selected transaction', + 'journals_linked' => 'Tranzacțiile sunt legate.', + 'journals_error_linked' => 'Aceste tranzacții sunt deja legate.', + 'journals_link_to_self' => 'Nu puteți conecta o tranzacție la sine', + 'journal_links' => 'Link-uri de tranzacții', + 'this_withdrawal' => 'Această retragere', + 'this_deposit' => 'Acest depozit', + 'this_transfer' => 'Acest transfer', + 'overview_for_link' => 'Prezentare generală pentru tipul de legătură ":name"', + 'source_transaction' => 'Tranzacție sursă', + 'link_description' => 'Descrierea legăturii', + 'destination_transaction' => 'Tranzacție de destinație', + 'delete_journal_link' => 'Șterge legătura dintre :source și :destination', + 'deleted_link' => 'Link șters', + + // link translations: + 'Paid_name' => 'Plătit', + 'Refund_name' => 'Restituire', + 'Reimbursement_name' => 'Rambursare', + 'Related_name' => 'Legate de', + 'relates to_inward' => 'se referă la', + 'is (partially) refunded by_inward' => 'este rambursat (parțial) de către', + 'is (partially) paid for by_inward' => 'este (parțial) plătit de către', + 'is (partially) reimbursed by_inward' => 'este (parțial) rambursat de către', + 'inward_transaction' => 'Tranzacție internă', + 'outward_transaction' => 'Tranzacție externă', + 'relates to_outward' => 'se referă la', + '(partially) refunds_outward' => '(parțial) restituiri', + '(partially) pays for_outward' => '(parțial) plătește pentru', + '(partially) reimburses_outward' => '(parțial) ramburseaza', + + // split a transaction: + 'splits' => 'Desparte', + 'add_another_split' => 'Adăugați o divizare', + 'split-transactions' => 'Tranzacții separate', + 'do_split' => 'Faceți o despărțire', + 'split_this_withdrawal' => 'Împărțiți această retragere', + 'split_this_deposit' => 'Împărțiți acest depozit', + 'split_this_transfer' => 'Împărțiți acest transfer', + 'cannot_edit_opening_balance' => 'Nu puteți edita soldul de deschidere al unui cont.', + 'no_edit_multiple_left' => 'Nu ați selectat niciun fel de tranzacții valide pentru a le edita.', + 'breadcrumb_convert_group' => 'Convert transaction', + 'convert_invalid_source' => 'Source information is invalid for transaction #%d.', + 'convert_invalid_destination' => 'Destination information is invalid for transaction #%d.', + + // Import page (general strings only) + 'import_index_title' => 'Importă tranzacții în Firefly III', + 'import_data' => 'Importă date', + 'import_transactions' => 'Importă tranzacții', + + // sandstorm.io errors and messages: + 'sandstorm_not_available' => 'Această funcție nu este disponibilă când utilizați Firefly III într-un mediu Sandstorm.io.', + + // empty lists? no objects? instructions: + 'no_accounts_title_asset' => 'Să cream un cont de active!', + 'no_accounts_intro_asset' => 'Nu ai încă nici un cont de active. Contul de active sunt conturile principale: contul dvs. de verificare, contul de economii, contul partajat sau chiar cardul dvs. de credit.', + 'no_accounts_imperative_asset' => 'Pentru a începe să utilizați Firefly III, trebuie să creați cel puțin un cont de active. Să o facem acum:', + 'no_accounts_create_asset' => 'Creați un cont de active', + 'no_accounts_title_expense' => 'Să cream un cont de cheltuieli!', + 'no_accounts_intro_expense' => 'Nu ai încă nici un cont de cheltuieli. Cont de cheltuieli sunt locurile unde cheltuiți bani, cum ar fi magazinele și supermarketurile.', + 'no_accounts_imperative_expense' => 'Cont de cheltuieli sunt create automat când creați tranzacții, dar puteți crea și una manuală, dacă doriți. Să creăm unul acum:', + 'no_accounts_create_expense' => 'Creați un cont de cheltuieli', + 'no_accounts_title_revenue' => 'Să cream un cont de venituri!', + 'no_accounts_intro_revenue' => 'Nu ai încă nici un cont de venituri.Conturile de venituri sunt locurile de unde primiți bani, cum ar fi angajatorul dvs.', + 'no_accounts_imperative_revenue' => 'Conturile de venituri sunt create automat când creați tranzacții, dar puteți crea și una manuală, dacă doriți. Să creăm unul acum:', + 'no_accounts_create_revenue' => 'Creați un cont de venituri', + 'no_accounts_title_liabilities' => 'Să cream un provizion!', + 'no_accounts_intro_liabilities' => 'Nu ai încă nici un provizion. Provizioanele sunt conturile care vă înregistrează cardurile de credit, împrumuturile și alte datorii.', + 'no_accounts_imperative_liabilities' => 'Nu trebuie să utilizați această funcție, dar poate fi utilă dacă doriți să urmăriți aceste lucruri.', + 'no_accounts_create_liabilities' => 'Creați un provizion', + 'no_budgets_title_default' => 'Să cream un provizion', + 'no_budgets_intro_default' => 'Nu ai încă nici un buget. Bugetele sunt folosite pentru a vă organiza cheltuielile în grupuri logice, pe care le puteți supune unei limite de cheltuieli.', + 'no_budgets_imperative_default' => 'Bugetele sunt instrumentele de bază ale gestiunii financiare. Să creăm unul acum:', + 'no_budgets_create_default' => 'Creați un buget', + 'no_categories_title_default' => 'Să cream o categorie!', + 'no_categories_intro_default' => 'Nu ai încă nici o categorie. Categoriile sunt utilizate pentru a regla tranzacțiile și a le eticheta cu categoria lor desemnată.', + 'no_categories_imperative_default' => 'Categoriile sunt create automat atunci când creați tranzacții, dar puteți crea și una manuală. Să creăm una acum:', + 'no_categories_create_default' => 'Creați o categorie', + 'no_tags_title_default' => 'Să cream o eticheta!', + 'no_tags_intro_default' => 'Nu ai încă nici o eticheta. Etichetele sunt utilizate pentru etichetarea tranzacțiilor cu cuvinte cheie specifice.', + 'no_tags_imperative_default' => 'Etichetele sunt create automat atunci când creați tranzacții, dar puteți crea și una manuală. Să creăm una acum:', + 'no_tags_create_default' => 'Creați o etichetă', + 'no_transactions_title_withdrawal' => 'Să cream o cheltuială!', + 'no_transactions_intro_withdrawal' => 'Nu ai încă nici o cheltuială. Trebuie să creați cheltuieli pentru a începe să vă gestionați finanțele.', + 'no_transactions_imperative_withdrawal' => 'Ai cheltuit niște bani? Atunci ar trebui să o scrieți:', + 'no_transactions_create_withdrawal' => 'Creați o cheltuială', + 'no_transactions_title_deposit' => 'Să creăm niște venituri!', + 'no_transactions_intro_deposit' => 'Nu ai încă nici un venit înregistrat. Ar trebui să creați intrări de venituri pentru a începe să vă gestionați finanțele.', + 'no_transactions_imperative_deposit' => 'Ai primit niște bani? Atunci ar trebui să o scrieți:', + 'no_transactions_create_deposit' => 'Creați un depozit', + 'no_transactions_title_transfers' => 'Să cream un transfer!', + 'no_transactions_intro_transfers' => 'Nu ai încă nici o transfer. Când transferați bani între contul de active, acesta este înregistrat ca transfer.', + 'no_transactions_imperative_transfers' => 'Ai mutat niște bani? Atunci ar trebui să o scrieți:', + 'no_transactions_create_transfers' => 'Creați un transfer', + 'no_piggies_title_default' => 'Să cream o pușculiță!', + 'no_piggies_intro_default' => 'Nu ai încă nici o pușculiță. Puteți crea pușculita pentru a vă împărți economiile și pentru a urmări ceea ce economisiți.', + 'no_piggies_imperative_default' => 'Aveți lucruri pentru care economisiți bani? Creați o pușculiță și urmăriți-o:', + 'no_piggies_create_default' => 'Creați o nouă pușculiță', + 'no_bills_title_default' => 'Să cream o factură!', + 'no_bills_intro_default' => 'Nu ai încă nici o factură. Puteți crea facturi pentru a urmări cheltuielile obișnuite, cum ar fi chiria sau asigurarea.', + 'no_bills_imperative_default' => 'Aveți astfel de facturi obișnuite? Creați o factură și țineți evidența plăților dvs.:', + 'no_bills_create_default' => 'Creați o factură', + + // recurring transactions + 'recurrences' => 'Tranzacții recurente', + 'recurring_calendar_view' => 'Calendar', + 'no_recurring_title_default' => 'Să cream o tranzacție recurentă!', + 'no_recurring_intro_default' => 'Nu ai încă nici o tranzacție recurentă. Puteți utiliza aceste pentru a face Firefly III să creeze automat tranzacții pentru dvs..', + 'no_recurring_imperative_default' => 'Aceasta este o caracteristică destul de avansată, dar poate fi extrem de utilă. Asigurați-vă că ați citit documentația (?) - pictograma din colțul din dreapta sus) înainte de a continua.', + 'no_recurring_create_default' => 'Creați o tranzacție recurentă', + 'make_new_recurring' => 'Creați o tranzacție recurentă', + 'recurring_daily' => 'Zilnic', + 'recurring_weekly' => 'În fiecare săptămână :weekday', + 'recurring_monthly' => 'În fiecare lună in ziua de :dayOfMonth(st/nd/rd/th)', + 'recurring_ndom' => 'În fiecare lună pe :dayOfMonth(st/nd/rd/th) :weekday', + 'recurring_yearly' => 'În fiecare an :date', + 'overview_for_recurrence' => 'Prezentare generală a tranzacției recurente ":title"', + 'warning_duplicates_repetitions' => 'În cazuri rare, datele apar de două ori în această listă. Acest lucru se poate întâmpla când mai multe repetări se ciocnesc. Firefly III va genera întotdeauna o tranzacție pe zi.', + 'created_transactions' => 'Operațiuni înrudite', + 'expected_withdrawals' => 'Retragerile preconizate', + 'expected_deposits' => 'Depozitele preconizate', + 'expected_transfers' => 'Transferurile preconizate', + 'created_withdrawals' => 'Retragerile create', + 'created_deposits' => 'Depozitele create', + 'created_transfers' => 'Transferurile create', + 'created_from_recurrence' => 'Creat din tranzacții recurente ":title" (#:id)', + 'recurring_never_cron' => 'Se pare că cron-job-ul necesar pentru a susține tranzacțiile recurente nu a avut loc niciodată. Acest lucru este, desigur, normal când ați instalat Firefly III, dar acest lucru ar trebui să fie ceva de instalat cât mai curând posibil. Consultați paginile de ajutor utilizând pictograma (?) - în colțul din dreapta sus al paginii.', + 'recurring_cron_long_ago' => 'Se pare că au trecut mai mult de 36 de ore de când cron-job-ul pentru susținerea tranzacțiilor recurente a fost utilizat. Sunteți sigur că a fost configurat corect? Consultați paginile de ajutor utilizând pictograma (?) - în colțul din dreapta sus al paginii.', + + 'recurring_meta_field_tags' => 'Etichete', + 'recurring_meta_field_notes' => 'Notițe', + 'recurring_meta_field_bill_id' => 'Factură', + 'recurring_meta_field_piggy_bank_id' => 'Pușculiță', + 'create_new_recurrence' => 'Creați o nouă tranzacție recurentă', + 'help_first_date' => 'Indicați prima recurență așteptată. Aceasta trebuie să fie în viitor.', + 'help_first_date_no_past' => 'Indicați prima recurență așteptată. Firefly III nu va crea tranzacții în trecut.', + 'no_currency' => '(nici o monedă)', + 'mandatory_for_recurring' => 'Informații obligatorii despre recurență', + 'mandatory_for_transaction' => 'Informații obligatorii despre tranzacții', + 'optional_for_recurring' => 'Informații opționale despre recurență', + 'optional_for_transaction' => 'Informații opționale despre tranzacții', + 'change_date_other_options' => 'Modificați "prima dată" pentru a vedea mai multe opțiuni.', + 'mandatory_fields_for_tranaction' => 'Valorile de aici vor rezulta în tranzacția (tranzacțiile) care se creează', + 'click_for_calendar' => 'Dați clic aici pentru un calendar care vă arată când tranzacția se va repeta.', + 'repeat_forever' => 'Repetați pentru totdeauna', + 'repeat_until_date' => 'Repetați până la data', + 'repeat_times' => 'Repetați de mai multe ori', + 'recurring_skips_one' => 'Toate celelalte', + 'recurring_skips_more' => 'Sari peste :count apariții', + 'store_new_recurrence' => 'Salvați tranzacție recurentă', + 'stored_new_recurrence' => 'tranzacție recurentă ":title" salvată cu succes.', + 'edit_recurrence' => 'Editați tranzacția recurentă ":title"', + 'recurring_repeats_until' => 'Se repetă până la :date', + 'recurring_repeats_forever' => 'Se repetă pentru totdeauna', + 'recurring_repeats_x_times' => 'Se repetă de :count ori', + 'update_recurrence' => 'Actualizați tranzacția recurentă', + 'updated_recurrence' => 'Tranzacție recurentă ":title" a fost actualizată', + 'recurrence_is_inactive' => 'Această tranzacție recurentă nu este activă și nu va genera noi tranzacții.', + 'delete_recurring' => 'Șterge tranzacția recurentă ":title"', + 'new_recurring_transaction' => 'Tranzacție recurentă nouă', + 'help_weekend' => 'Ce ar trebui să facă Firefly III atunci când tranzacția recurentă cade într-o sâmbătă sau duminică?', + 'do_nothing' => 'Doar creați tranzacția', + 'skip_transaction' => 'Treceți peste apariție', + 'jump_to_friday' => 'Creați tranzacția din vineri precedentă', + 'jump_to_monday' => 'Creați tranzacția de luni viitoare', + 'will_jump_friday' => 'Va fi creat vineri în loc de weekend.', + 'will_jump_monday' => 'Va fi creat luni, în loc de weekend.', + 'except_weekends' => 'Cu excepția weekend-urilor', + 'recurrence_deleted' => 'tranzacție recurentă ":title" ștearsă', + + // new lines for summary controller. + 'box_balance_in_currency' => 'Balance (:currency)', + 'box_spent_in_currency' => 'Spent (:currency)', + 'box_earned_in_currency' => 'Earned (:currency)', + 'box_bill_paid_in_currency' => 'Bills paid (:currency)', + 'box_bill_unpaid_in_currency' => 'Bills unpaid (:currency)', + 'box_left_to_spend_in_currency' => 'Left to spend (:currency)', + 'box_net_worth_in_currency' => 'Net worth (:currency)', + 'box_spend_per_day' => 'Left to spend per day: :amount', + +]; diff --git a/resources/lang/ro_RO/form.php b/resources/lang/ro_RO/form.php new file mode 100644 index 0000000000..279906f054 --- /dev/null +++ b/resources/lang/ro_RO/form.php @@ -0,0 +1,259 @@ +. + */ + +declare(strict_types=1); + +return [ + // new user: + 'bank_name' => 'Numele băncii', + 'bank_balance' => 'Balanța', + 'savings_balance' => 'Soldul de economii', + 'credit_card_limit' => 'Limita cardului de credit', + 'automatch' => 'Se potrivește automat', + 'skip' => 'Sari peste', + 'enabled' => 'Activat', + 'name' => 'Nume', + 'active' => 'Activ', + 'amount_min' => 'Suma minimă', + 'amount_max' => 'suma maximă', + 'match' => 'Se potrivește', + 'strict' => 'Modul strict', + 'repeat_freq' => 'Repetă', + 'journal_currency_id' => 'Monedă', + 'currency_id' => 'Monedă', + 'transaction_currency_id' => 'Monedă', + 'external_ip' => 'IP-ul extern al serverului dvs.', + 'attachments' => 'Fișiere atașate', + 'journal_amount' => 'Suma', + 'journal_source_name' => 'Contul de venituri (sursă)', + 'keep_bill_id' => 'Factură', + 'journal_source_id' => 'Cont activ (sursă)', + 'BIC' => 'BIC', + 'verify_password' => 'Verificați securitatea parolei', + 'source_account' => 'Contul sursă', + 'destination_account' => 'Contul destinației', + 'journal_destination_id' => 'Cont de active (destinație)', + 'asset_destination_account' => 'Contul destinației', + 'include_net_worth' => 'Includeți în valoare netă', + 'asset_source_account' => 'Contul sursă', + 'journal_description' => 'Descriere', + 'note' => 'Notițe', + 'store_new_transaction' => 'Store new transaction', + 'split_journal' => 'Împărțiți această tranzacție', + 'split_journal_explanation' => 'Împărțiți această tranzacție în mai multe părți', + 'currency' => 'Monedă', + 'account_id' => 'Cont de active', + 'budget_id' => 'Buget', + 'opening_balance' => 'Opening balance', + 'tagMode' => 'Mod de etichetare', + 'tag_position' => 'Locația etichetei', + 'virtual_balance' => 'Virtual balance', + 'targetamount' => 'Sumă țintă', + 'account_role' => 'Account role', + 'opening_balance_date' => 'Opening balance date', + 'cc_type' => 'Credit card payment plan', + 'cc_monthly_payment_date' => 'Credit card monthly payment date', + 'piggy_bank_id' => 'Pușculită', + 'returnHere' => 'Întoarce-te aici', + 'returnHereExplanation' => 'După salvare, reveniți aici pentru a crea alta.', + 'returnHereUpdateExplanation' => 'După actualizare, reveniți aici.', + 'description' => 'Descriere', + 'expense_account' => 'Cont de cheltuieli', + 'revenue_account' => 'Contul de venituri', + 'decimal_places' => 'Zecimale', + 'exchange_rate_instruction' => 'Monede străine', + 'source_amount' => 'Sumă (sursă)', + 'destination_amount' => 'Sumă (destinație)', + 'native_amount' => 'Valoare nativă', + 'new_email_address' => 'Adresă de email nouă', + 'verification' => 'Verificare', + 'api_key' => 'Cheie API', + 'remember_me' => 'Ține-mă minte', + 'liability_type_id' => 'Tipul de provizion', + 'interest' => 'Interes', + 'interest_period' => 'Perioadă de interes', + + 'source_account_asset' => 'Contul sursă (asset account)', + 'destination_account_expense' => 'Contul destinației (expense account)', + 'destination_account_asset' => 'Contul destinației (asset account)', + 'source_account_revenue' => 'Contul sursă (cont de venituri)', + 'type' => 'Tip', + 'convert_Withdrawal' => 'Convertește retragere', + 'convert_Deposit' => 'Convertește depozit', + 'convert_Transfer' => 'Convertește transfer', + + 'amount' => 'Sumă', + 'foreign_amount' => 'Sumă străină', + 'existing_attachments' => 'Atașamentele existente', + 'date' => 'Dată', + 'interest_date' => 'Data de interes', + 'book_date' => 'Rezervă dată', + 'process_date' => 'Data procesării', + 'category' => 'Categorie', + 'tags' => 'Etichete', + 'deletePermanently' => 'Șterge permanent', + 'cancel' => 'Anulare', + 'targetdate' => 'Data-țintă', + 'startdate' => 'Data de început', + 'tag' => 'Etichetă', + 'under' => 'Sub', + 'symbol' => 'Simbol', + 'code' => 'Cod', + 'iban' => 'IBAN', + 'account_number' => 'Account number', + 'creditCardNumber' => 'Numărul cărții de credit', + 'has_headers' => 'Antet', + 'date_format' => 'Formatul datei', + 'specifix' => 'Fișiere specifice băncii', + 'attachments[]' => 'Atașamente', + 'store_new_withdrawal' => 'Stocați retragere nouă', + 'store_new_deposit' => 'Stocați depozit nou', + 'store_new_transfer' => 'Stocați transfer nou', + 'add_new_withdrawal' => 'Adăugați o nouă retragere', + 'add_new_deposit' => 'Adăugați un nou depozit', + 'add_new_transfer' => 'Adăugați un nou transfer', + 'title' => 'Titlu', + 'notes' => 'Notițe', + 'filename' => 'Nume de fișier', + 'mime' => 'Tipuri Mime', + 'size' => 'Mărime', + 'trigger' => 'Declanșator', + 'stop_processing' => 'Opriți procesarea', + 'start_date' => 'Start de interval', + 'end_date' => 'Șfârșit de interval', + 'include_attachments' => 'Includeți atașamente încărcate', + 'include_old_uploads' => 'Includeți datele importate', + 'delete_account' => 'Șterge cont ":name"', + 'delete_bill' => 'Șterge factură ":name"', + 'delete_budget' => 'Șterge buget ":name"', + 'delete_category' => 'Șterge categorie ":name"', + 'delete_currency' => 'Șterge moneă ":name"', + 'delete_journal' => 'Șterge tranzacția cu descrierea ":description"', + 'delete_attachment' => 'Șterge atașamentul ":name"', + 'delete_rule' => 'Șterge regula ":title"', + 'delete_rule_group' => 'Șterge grupul de reguli ":title"', + 'delete_link_type' => 'Șterge tipul de link-uri ":name"', + 'delete_user' => 'Șterge user-ul ":email"', + 'delete_recurring' => 'Șterge tranzacția recurentă ":title"', + 'user_areYouSure' => 'Dacă ștergeți utilizatorul ":email", totul va dispărea. Nu există nici o undo (anulare), anulare ștergere sau orice altceva. Dacă vă ștergeți, veți pierde accesul la aplicație.', + 'attachment_areYouSure' => 'Sunteți sigur că doriți să ștergeți atașamentul ":name"?', + 'account_areYouSure' => 'Sunteți sigur că doriți să ștergeți contul ":name"?', + 'bill_areYouSure' => 'Sunteți sigur că doriți să ștergeți factura ":name"?', + 'rule_areYouSure' => 'Sunteți sigur că doriți să ștergeți regula ":title"?', + 'ruleGroup_areYouSure' => 'Sunteți sigur că doriți să ștergeți grupul de reguli ":title"?', + 'budget_areYouSure' => 'Sunteți sigur că doriți să ștergeți bugetul ":name"?', + 'category_areYouSure' => 'Sunteți sigur că doriți să ștergeți categoria ":name"?', + 'recurring_areYouSure' => 'Sunteți sigur că doriți să ștergeți tranzacția recurentă ":title"?', + 'currency_areYouSure' => 'Sunteți sigur că doriți să ștergeți moneda ":name"?', + 'piggyBank_areYouSure' => 'Sunteți sigur că doriți să ștergeți pușculita ":name"?', + 'journal_areYouSure' => 'Sunteți sigur că doriți să ștergeți tranzacția ":description"?', + 'mass_journal_are_you_sure' => 'Sunteți sigur că doriți să ștergeți aceste tranzacții?', + 'tag_areYouSure' => 'Sunteți sigur că doriți să ștergeți eticheta ":tag"?', + 'journal_link_areYouSure' => 'Sunteți sigur că doriți să ștergeți legătura dintre :source și :destination?', + 'linkType_areYouSure' => 'Sunteți sigur că doriți să ștergeți tipul de legătură ":name" (":inward" / ":outward")?', + 'permDeleteWarning' => 'Ștergerea este permanentă și nu poate fi anulată.', + 'mass_make_selection' => 'Încă puteți împiedica ștergerea articolelor eliminând caseta de selectare.', + 'delete_all_permanently' => 'Ștergeți selectat definitiv', + 'update_all_journals' => 'Actualizați aceste tranzacții', + 'also_delete_transactions' => 'Singura tranzacție conectată la acest cont va fi, de asemenea, ștearsă.|Toate cele :count tranzacții conectate la acest cont vor fi șterse.', + 'also_delete_connections' => 'Singura tranzacție legată de acest tip de legătură va pierde această conexiune.|Toate cele :count tranzacții legate de acest tip de legătură vor pierde conexiunea.', + 'also_delete_rules' => 'Singura regulă legată de acest grup de reguli va fi ștersă, de asemenea.|Toate cele :count reguli conectate la acest grup de reguli vor fi șterse, de asemenea.', + 'also_delete_piggyBanks' => 'Singura pușculita conectată la acest cont va fi ștersă.|Toate cele :count pușculițe conectate la acest cont vor fi șterse, de asemenea.', + 'bill_keep_transactions' => 'Singura tranzacție conectată la această factură nu va fi ștearsă.|Toate cele :count tranzacții conectate la această factură vor fi scutite de ștergere.', + 'budget_keep_transactions' => 'Singura tranzacție conectată la acest buget nu va fi ștearsă.|Toate cele :count tranzacții conectate la acest budet vor fi scutite de ștergere.', + 'category_keep_transactions' => 'Singura tranzacție conectată la această categorie nu va fi ștearsă.|Toate cele :count tranzacții conectate la această categorie vor fi scutite de ștergere.', + 'recurring_keep_transactions' => 'Singura tranzacție creată de această tranzacție recurentă nu va fi ștearsă.|Toate cele :count tranzacții create de această tranzacție recurente vor fi scutite de ștergere.', + 'tag_keep_transactions' => 'Singura tranzacție conectată la this tag nu va fi ștearsă.|Toate cele :count tranzacții conectate la această etichetă vor fi scutite de ștergere.', + 'check_for_updates' => 'Verifică pentru actualizări', + + 'email' => 'Email', + 'password' => 'Parolă', + 'password_confirmation' => 'Parolă (din nou)', + 'blocked' => 'Este blocat?', + 'blocked_code' => 'Motiv pentru blocare', + 'login_name' => 'Logare', + + // import + 'apply_rules' => 'Aplică reguli', + 'artist' => 'Artist', + 'album' => 'Album', + 'song' => 'Melodie', + + + // admin + 'domain' => 'Domeniu', + 'single_user_mode' => 'Dezactivați înregistrarea utilizatorilor', + 'is_demo_site' => 'Este un site demo', + + // import + 'import_file' => 'Fișier de import', + 'configuration_file' => 'Fișier de configurare', + 'import_file_type' => 'Importați tipul de fișier', + 'csv_comma' => 'O virgulă (,)', + 'csv_semicolon' => 'Un punct și virgulă (;)', + 'csv_tab' => 'O filă (invizibilă)', + 'csv_delimiter' => 'Delimitator CSV', + 'csv_import_account' => 'Contul de import implicit', + 'csv_config' => 'Configurare import CSV', + 'client_id' => 'ID Client', + 'service_secret' => 'Serviciu secret', + 'app_secret' => 'Secret Aplicație', + 'app_id' => 'ID Aplicație', + 'secret' => 'Secret', + 'public_key' => 'Cheie publică', + 'country_code' => 'Codul țării', + 'provider_code' => 'Bancă sau furnizor de date', + 'fints_url' => 'URL-ul FinTS API', + 'fints_port' => 'Port', + 'fints_bank_code' => 'Cod bancar', + 'fints_username' => 'Nume de utilizator', + 'fints_password' => 'PIN / parola', + 'fints_account' => 'Cont FinTS', + 'local_account' => 'Cont Firefly III', + 'from_date' => 'Data din', + 'to_date' => 'Data până la', + + + 'due_date' => 'Data scadentă', + 'payment_date' => 'Data de plată', + 'invoice_date' => 'Data facturii', + 'internal_reference' => 'Referință internă', + 'inward' => 'Descrierea interioară', + 'outward' => 'Descrierea exterioară', + 'rule_group_id' => 'Grup de reguli', + 'transaction_description' => 'Descrierea tranzacției', + 'first_date' => 'Prima dată', + 'transaction_type' => 'Tipul tranzacției', + 'repeat_until' => 'Repetați până la', + 'recurring_description' => 'Descrierea tranzacției recurente', + 'repetition_type' => 'Tip de repetare', + 'foreign_currency_id' => 'Monedă străină', + 'repetition_end' => 'Repetarea se termină', + 'repetitions' => 'Repetări', + 'calendar' => 'Calendar', + 'weekend' => 'Sfârșit de săptămână', + 'client_secret' => 'Codul secret al clientului', + + 'withdrawal_destination_id' => 'Destination account', + 'deposit_source_id' => 'Source account', + +]; diff --git a/resources/lang/ro_RO/import.php b/resources/lang/ro_RO/import.php new file mode 100644 index 0000000000..8cb95a4d79 --- /dev/null +++ b/resources/lang/ro_RO/import.php @@ -0,0 +1,317 @@ +. + */ + +declare(strict_types=1); + +return [ + // ALL breadcrumbs and subtitles: + 'index_breadcrumb' => 'Importă tranzacții în Firefly III', + 'prerequisites_breadcrumb_fake' => 'Cerințe preliminare pentru furnizorul de import fals', + 'prerequisites_breadcrumb_spectre' => 'Premisele pentru Spectre', + 'prerequisites_breadcrumb_bunq' => 'Premisele pentru bunq', + 'prerequisites_breadcrumb_ynab' => 'Premisele pentru YNAB', + 'job_configuration_breadcrumb' => 'Configurare pentru ":key"', + 'job_status_breadcrumb' => 'Statutul import pentru ":key"', + 'disabled_for_demo_user' => 'dezactivat în demo', + + // index page: + 'general_index_intro' => 'Bine ați venit la rutina de import Firefly III. Există câteva moduri de a importa date în Firefly III, afișate aici ca butoane.', + + // import provider strings (index): + 'button_fake' => 'Simulează un import', + 'button_file' => 'Importă un fișier', + 'button_bunq' => 'Import din bunq', + 'button_spectre' => 'Import folosind Spectre', + 'button_plaid' => 'Import folosind Plaid', + 'button_yodlee' => 'Import folosind Yodlee', + 'button_quovo' => 'Import folosind Quovo', + 'button_ynab' => 'Import din You Need A Budget', + 'button_fints' => 'Import folosind FinTS', + + + // prerequisites box (index) + 'need_prereq_title' => 'Premise de import', + 'need_prereq_intro' => 'Unele metode de import necesită atenția dvs. înainte ca acestea să poată fi utilizate. De exemplu, pot necesita chei API speciale sau parole de aplicație. Puteți să le configurați aici. Pictograma indică dacă aceste condiții preliminare au fost îndeplinite.', + 'do_prereq_fake' => 'Cerințe preliminare pentru furnizorul de import fals', + 'do_prereq_file' => 'Premisele pentru importurile de fişier', + 'do_prereq_bunq' => 'Premisele pentru importurile din bunq', + 'do_prereq_spectre' => 'Premisele pentru importurile folosind Spectre', + 'do_prereq_plaid' => 'Premisele pentru importurile din Plaid', + 'do_prereq_yodlee' => 'Premisele pentru importurile folosind Yodlee', + 'do_prereq_quovo' => 'Premisele pentru importurile folosind Quovo', + 'do_prereq_ynab' => 'Premisele pentru importurile din YNAB', + + // prerequisites: + 'prereq_fake_title' => 'Premisele pentru un import fals de la furnizorul de servicii', + 'prereq_fake_text' => 'Acest furnizor de fals necesită un o cheie API falsă. Acesta trebuie să fie de 32 de caractere. Îl puteţi folosi pe acesta: 123456789012345678901234567890AA', + 'prereq_spectre_title' => 'Premisele pentru un import folosind API-ul Spectre', + 'prereq_spectre_text' => 'Pentru a importa date utilizând API-ul Spectre (v4), trebuie să furnizaţi către Firefly III două valori secrete. Acestea pot fi găsite în pagina de secrete.', + 'prereq_spectre_pub' => 'De asemenea, API-ul Spectre trebuie să cunoască cheia publică pe care o vedeţi mai jos. Fără ea, acesta nu vă va recunoaşte. Vă rugăm să introduceţi această cheie publică în pagina de secrete.', + 'prereq_bunq_title' => 'Premisele pentru un import din bunq', + 'prereq_bunq_text' => 'Pentru a importa din bunq, aveţi nevoie să obţineți o cheie API. Puteţi face acest lucru prin intermediul aplicașiei. Vă rugăm să reţineţi că funcţia de import pentru bunq este în versiune BETA. Aceasta a fost testată doar cu API-ul din sandbox.', + 'prereq_bunq_ip' => 'bunq cere adresa dvs IP externă. Firefly al III-lea a încercat să completeze acest lucru folosind serviciul ipify . Asiguraţi-vă că acest IP este corect sau importul va eşua.', + 'prereq_ynab_title' => 'Premisele pentru un import din YNAB', + 'prereq_ynab_text' => 'Pentru a putea descărca tranzacții de la YNAB, creați o nouă aplicație pe Pagina cu setările pentru dezvoltatori și introduceți ID-ul clientului și parola secretă pe această pagină.', + 'prereq_ynab_redirect' => 'Pentru a finaliza configurația, introduceți următoarea adresă URL în Pagina cu setări pentru dezvoltatori în secțiunea "Redirect URI(s) - URI de redirecționare".', + 'callback_not_tls' => 'Firefly III a detectat următorul calback URI. Se pare că serverul dvs. nu este configurat să accepte conexiuni TLS (https). YNAB nu va accepta acest URI. Puteți continua importul (deoarece Firefly III ar putea greși), dar vă rugăm să păstrați acest lucru în minte.', + // prerequisites success messages: + 'prerequisites_saved_for_fake' => 'Cheia API falsă a fost stocată cu succes!', + 'prerequisites_saved_for_spectre' => 'App ID și secret stocate!', + 'prerequisites_saved_for_bunq' => 'Cheia API și adresa IP stocată!', + 'prerequisites_saved_for_ynab' => 'ID-ul clientului YNAB și parola secretă au fost stocate!', + + // job configuration: + 'job_config_apply_rules_title' => 'Configurarea Job-ului - aplicați regulile dvs.?', + 'job_config_apply_rules_text' => 'Odată ce furnizorul fals a rulat, regulile dvs. pot fi aplicate tranzacțiilor. Aceasta adaugă timp la import.', + 'job_config_input' => 'Datele introduse de dvs.', + // job configuration for the fake provider: + 'job_config_fake_artist_title' => 'Introduceți numele albumului', + 'job_config_fake_artist_text' => 'Multe rutine de import au câțiva pași de configurare ce trebuie să-i faceți. În cazul furnizorului fals de import, trebuie să răspundeți la câteva întrebări ciudate. În acest caz, introduceți "David Bowie" pentru a continua.', + 'job_config_fake_song_title' => 'Introduceţi numele cântecului', + 'job_config_fake_song_text' => 'Menţiona melodia "Golden years - Anii de aur" pentru a continua cu importul fals.', + 'job_config_fake_album_title' => 'Introduceți numele albumului', + 'job_config_fake_album_text' => 'Unele rutine de import necesită date suplimentare la jumătatea perioadei de import. În cazul furnizorului fals de import, trebuie să răspundeți la câteva întrebări ciudate. Introduceți "Station to station" pentru a continua.', + // job configuration form the file provider + 'job_config_file_upload_title' => 'Configurare import (1/4) - Încărcați fișierul', + 'job_config_file_upload_text' => 'Această rutină vă va ajuta să importați fișiere din banca dvs. în Firefly III.', + 'job_config_file_upload_help' => 'Selectaţi fişierul. Asigurați-vă că fişierul este codificat UTF-8.', + 'job_config_file_upload_config_help' => 'Dacă ați importat anterior date în Firefly III, este posibil să aveți un fișier de configurare, care va preseta valorile de configurare pentru dvs. Pentru unele bănci, alți utilizatori au oferit cu amabilitate fișierul de configurare ', + 'job_config_file_upload_type_help' => 'Selectați tipul de fișier pe care îl încărcați', + 'job_config_file_upload_submit' => 'Încarcă fişiere', + 'import_file_type_csv' => 'CSV (valori separate prin virgulă)', + 'import_file_type_ofx' => 'OFX', + 'file_not_utf8' => 'Fișierul pe care l-ați încărcat nu este codificat ca UTF-8 sau ASCII. Firefly III nu poate gestiona astfel de fișiere. Utilizați Notepad ++ sau Sublime pentru a vă converti fișierul în UTF-8.', + 'job_config_uc_title' => 'Configurare import (2/4) - configurare fișier de bază', + 'job_config_uc_text' => 'Pentru a putea importa fișierul corect, validați opțiunile de mai jos.', + 'job_config_uc_header_help' => 'Bifați această casetă dacă primul rând al fișierului dvs. CSV reprezintă titlurile coloanei.', + 'job_config_uc_date_help' => 'Formatul datei n fișierul dvs. Urmați formatul din această pagină . Valoarea implicită va analiza datele care arată astfel: :dateExample.', + 'job_config_uc_delimiter_help' => 'Alegeți delimitatorul de câmp utilizat în fișierul de intrare. Dacă nu sunteți sigur, virgula este cea mai sigură opțiune.', + 'job_config_uc_account_help' => 'Dacă fișierul dvs. NU conține informații despre contul(conturile) de active, utilizați acest dropdown pentru a selecta în ce cont aparțin tranzacțiile din fișier.', + 'job_config_uc_apply_rules_title' => 'Aplică reguli', + 'job_config_uc_apply_rules_text' => 'Aplică regulile dvs. pentru fiecare tranzacție importată. Rețineți că acest lucru încetinește semnificativ importul.', + 'job_config_uc_specifics_title' => 'Opţiunile specifice pentru banca', + 'job_config_uc_specifics_txt' => 'Unele bănci furnizează fișiere prost formatate. Firefly III le poate remedia în mod automat. Dacă banca dvs. furnizează astfel de fișiere, dar nu este listată aici, vă rugăm să deschideți o problemă pe GitHub.', + 'job_config_uc_submit' => 'Continuă', + 'invalid_import_account' => 'Ați selectat un cont nevalid în care să importați.', + 'import_liability_select' => 'Provizioane', + // job configuration for Spectre: + 'job_config_spectre_login_title' => 'Alegeţi datele de conectare', + 'job_config_spectre_login_text' => 'Firefly III a găsit :count login-urile existente în contul dvs. Spectre. De la care doriți să importați?', + 'spectre_login_status_active' => 'Activ', + 'spectre_login_status_inactive' => 'Inactiv', + 'spectre_login_status_disabled' => 'Dezactivat', + 'spectre_login_new_login' => 'Conectați-vă la o altă bancă sau la una dintre aceste bănci cu acreditări diferite.', + 'job_config_spectre_accounts_title' => 'Selectaţi conturile din care doriți să se importe', + 'job_config_spectre_accounts_text' => 'Ați selectat ":name" (:country). Aveți :count cont(uri)disponibile de la acest furnizor. Selectați contul (urile) de active Firefly III în care trebuie să fie stocate tranzacțiile din aceste conturi. Rețineți că, pentru a importa date, contul Firefly III și contul ":name" trebuie să aibă aceeași monedă.', + 'spectre_do_not_import' => '(nu importați)', + 'spectre_no_mapping' => 'Se pare că nu ați selectat niciun cont de unde să importați.', + 'imported_from_account' => 'Importat din ":account"', + 'spectre_account_with_number' => 'Contul :number', + 'job_config_spectre_apply_rules' => 'Aplică reguli', + 'job_config_spectre_apply_rules_text' => 'Implicit, regulile dvs. vor fi aplicate tranzacțiilor create în timpul acestei rutine de import. Dacă nu doriți ca acest lucru să se întâmple, deselectați această casetă de selectare.', + + // job configuration for bunq: + 'job_config_bunq_accounts_title' => 'Conturi bunq', + 'job_config_bunq_accounts_text' => 'Acestea sunt conturile asociate contului tău bunq. Selectați conturile din care doriți să importați și în ce cont trebuie să fie importate tranzacțiile.', + 'bunq_no_mapping' => 'Se pare că nu ați selectat niciun cont de unde să importați.', + 'should_download_config' => 'Ar trebui să descărcați fișierul de configurare pentru acest job. Acest lucru va ușura importurile viitoare.', + 'share_config_file' => 'Dacă ați importat date dintr-o bancă publică, trebuie să partajați fișierul de configurare , astfel încât să fie ușor pentru alți utilizatori să importe datele lor. Partajarea fișierului dvs. de configurare nu va expune detaliile dvs. financiare.', + 'job_config_bunq_apply_rules' => 'Aplică reguli', + 'job_config_bunq_apply_rules_text' => 'Implicit, regulile dvs. vor fi aplicate tranzacțiilor create în timpul acestei rutine de import. Dacă nu doriți ca acest lucru să se întâmple, deselectați această casetă de selectare.', + 'bunq_savings_goal' => 'Obiectiv economisire: :amount (:percentage%)', + 'bunq_account_status_CANCELLED' => 'Închide contul bunq', + + 'ynab_account_closed' => 'Contul este inchis!', + 'ynab_account_deleted' => 'Contul este șters!', + 'ynab_account_type_savings' => 'cont de economii', + 'ynab_account_type_checking' => 'cont curent', + 'ynab_account_type_cash' => 'cont de numerar', + 'ynab_account_type_creditCard' => 'card de credit', + 'ynab_account_type_lineOfCredit' => 'linie de credit', + 'ynab_account_type_otherAsset' => 'alt cont de active', + 'ynab_account_type_otherLiability' => 'alte datorii', + 'ynab_account_type_payPal' => 'Paypal', + 'ynab_account_type_merchantAccount' => 'cont de comerciant', + 'ynab_account_type_investmentAccount' => 'cont de investitii', + 'ynab_account_type_mortgage' => 'credit ipotecar', + 'ynab_do_not_import' => '(nu importați)', + 'job_config_ynab_apply_rules' => 'Aplică reguli', + 'job_config_ynab_apply_rules_text' => 'Implicit, regulile dvs. vor fi aplicate tranzacțiilor create în timpul acestei rutine de import. Dacă nu doriți ca acest lucru să se întâmple, deselectați această casetă de selectare.', + + // job configuration for YNAB: + 'job_config_ynab_select_budgets' => 'Selectaţi bugetul', + 'job_config_ynab_select_budgets_text' => 'Aveți :count bugete stocate în YNAB. Vă rugăm să selectaţi din care Firefly III va importa tranzacţiile.', + 'job_config_ynab_no_budgets' => 'Nu există bugete disponibile pentru a fi importate.', + 'ynab_no_mapping' => 'Se pare că nu ați selectat niciun cont de unde să importați.', + 'job_config_ynab_bad_currency' => 'Nu puteți importa din următorul buget (următoarele bugete), deoarece nu aveți conturi cu aceeași monedă ca și aceste bugete.', + 'job_config_ynab_accounts_title' => 'Selectaţi contul', + 'job_config_ynab_accounts_text' => 'Aveți la dispoziție următoarele conturi în acest buget. Selectați din conturile pe care doriți să le importați și unde ar trebui să fie stocate tranzacțiile.', + + + // keys from "extra" array: + 'spectre_extra_key_iban' => 'IBAN', + 'spectre_extra_key_swift' => 'SWIFT', + 'spectre_extra_key_status' => 'Statut', + 'spectre_extra_key_card_type' => 'Tip card', + 'spectre_extra_key_account_name' => 'Nume cont', + 'spectre_extra_key_client_name' => 'Nume client', + 'spectre_extra_key_account_number' => 'Număr de cont', + 'spectre_extra_key_blocked_amount' => 'Suma blocată', + 'spectre_extra_key_available_amount' => 'Sumă disponibilă', + 'spectre_extra_key_credit_limit' => 'Limita de credit', + 'spectre_extra_key_interest_rate' => 'Rata dobânzii', + 'spectre_extra_key_expiry_date' => 'Data expirării', + 'spectre_extra_key_open_date' => 'Data deschidere', + 'spectre_extra_key_current_time' => 'Ora curentă', + 'spectre_extra_key_current_date' => 'Data curentă', + 'spectre_extra_key_cards' => 'Carduri', + 'spectre_extra_key_units' => 'Unităţi', + 'spectre_extra_key_unit_price' => 'Preţ unitar', + 'spectre_extra_key_transactions_count' => 'Numărul de tranzacții', + + //job configuration for finTS + 'fints_connection_failed' => 'A apărut o eroare în timp ce încercați să vă conectați la banca dvs. Asigurați-vă că toate datele pe care le-ați introdus sunt corecte. Mesaj de eroare original: :originalError', + + 'job_config_fints_url_help' => 'Exemplu https://banking-dkb.s-fints-pt-dkb.de/fints30', + 'job_config_fints_username_help' => 'Pentru mai multe bănci, acesta este numărul de cont.', + 'job_config_fints_port_help' => 'Portul prestabilit este 443.', + 'job_config_fints_account_help' => 'Selectaţi contul pentru care doriţi să importaţi tranzacţii.', + 'job_config_local_account_help' => 'Alegeți contul Firefly III corespunzător contului dvs. bancar ales mai sus.', + // specifics: + 'specific_ing_name' => 'ING NL', + 'specific_ing_descr' => '41/5000 +Creați descrieri mai bune în exporturile ING', + 'specific_sns_name' => 'SNS / Volksbank NL', + 'specific_sns_descr' => 'Reguli din exportul de fișiere SNS / Volksbank', + 'specific_abn_name' => 'ABN AMRO NL', + 'specific_abn_descr' => 'Remediază posibile probleme cu fișierele ABN AMRO', + 'specific_rabo_name' => 'Rabobank NL', + 'specific_rabo_descr' => 'Remediază posibile probleme cu fișierele Rabobank', + 'specific_pres_name' => 'Președintele CA pentru alegerea financiară', + 'specific_pres_descr' => 'Remediază posibile probleme cu fișierele PC', + 'specific_belfius_name' => 'Belfius BE', + 'specific_belfius_descr' => 'Remediază posibile probleme cu fișierele Belfius', + 'specific_ingbelgium_name' => 'ING BE', + 'specific_ingbelgium_descr' => 'Fixes potential problems with ING Belgium files', + // job configuration for file provider (stage: roles) + 'job_config_roles_title' => 'Configurarea importului (3/4) - Definiți rolul fiecărei coloane', + 'job_config_roles_text' => 'Fiecare coloană din fișierul dvs. CSV conține anumite date. Vă rugăm să indicați ce fel de date ar trebui să aștepte importatorul. Opțiunea de a "mapa" datele înseamnă că veți conecta fiecare intrare găsită în coloană cu o valoare din baza dvs. de date. O coloană desenată de multe ori este coloana care conține IBAN-ul contului opus. Acest lucru poate fi ușor comparat cu prezența IBAN în baza dvs. de date.', + 'job_config_roles_submit' => 'Continuă', + 'job_config_roles_column_name' => 'Numele coloanei', + 'job_config_roles_column_example' => 'Exemplu de date de coloană', + 'job_config_roles_column_role' => 'Semnificația datelor din coloană', + 'job_config_roles_do_map_value' => 'Harta acestor valori', + 'job_config_roles_no_example' => 'Nu există exemple disponibile', + 'job_config_roles_fa_warning' => 'Dacă marcați o coloană ca având o sumă într-o valută străină, trebuie să setați și coloana care conține ce valută este.', + 'job_config_roles_rwarning' => 'Cel puțin, marcați o coloană ca și coloană sumă. Se recomandă de asemenea să selectați o coloană pentru descriere, data și contul opus.', + 'job_config_roles_colum_count' => 'Coloană', + // job config for the file provider (stage: mapping): + 'job_config_map_title' => 'Configurare import (4/4) - Conectați date de import la Firefly III', + 'job_config_map_text' => 'În tabelele următoare, valoarea din stânga vă arată informațiile găsite în fișierul încărcat. Sarcina dvs. este aceea de a mapa această valoare, dacă este posibil, la o valoare deja prezentă în baza dvs. de date. Firefly se va lipi de această cartografiere. Dacă nu există nicio valoare pentru care să fie mapată sau dacă nu doriți să cartografiați valoarea specifică, nu selectați nimic.', + 'job_config_map_nothing' => 'Nu există date prezente în fișierul dvs. pe care să le puteți mapa la valorile existente. Vă rugăm să apăsați "Start import" pentru a continua.', + 'job_config_field_value' => 'Valoarea câmpului', + 'job_config_field_mapped' => 'Mapat la', + 'map_do_not_map' => '(nu mapați)', + 'job_config_map_submit' => 'Porniți importul', + + + // import status page: + 'import_with_key' => 'Importați cu cheia \':key\'', + 'status_wait_title' => 'Vă rugăm să așteptați...', + 'status_wait_text' => 'Această casetă va dispărea într-o clipă.', + 'status_running_title' => 'Importul se execută', + 'status_job_running' => 'Așteptați, importul se execută...', + 'status_job_storing' => 'Așteptați, stocăm datele...', + 'status_job_rules' => 'Așteptați, rulăm regulile...', + 'status_fatal_title' => 'Eroare fatala', + 'status_fatal_text' => 'Importul a întampinat o eroare și nu s-a putut recupera. Ne cerem scuze!', + 'status_fatal_more' => 'Acest mesaj de eroare (posibil foarte criptic) este completat de fișierele jurnal, pe care le puteți găsi pe unitatea hard disk sau în containerul Docker de unde executați Firefly III.', + 'status_finished_title' => 'Importul s-a terminat', + 'status_finished_text' => 'Importul s-a terminat.', + 'finished_with_errors' => 'Au existat unele erori în timpul importului. Revedeți-le cu atenție.', + 'unknown_import_result' => 'Rezultat necunoscut pentru import', + 'result_no_transactions' => 'Nu au fost importate tranzacții. Poate că toate au fost duplicate. Poate că fișierele de jurnale vă pot spune ce s-a întâmplat. Dacă importați date în mod regulat, este normal.', + 'result_one_transaction' => 'Exact o tranzacție a fost importată. Aceasta este stocată sub eticheta :tag unde o puteți inspecta mai departe.', + 'result_many_transactions' => 'Firefly III a importat :count tranzacții. Ele sunt stocate sub eticheta :tag unde le puteți inspecta mai departe.', + + + // general errors and warnings: + 'bad_job_status' => 'Pentru a accesa această pagină, job-ul de import nu poate avea statusul ":status".', + + // column roles for CSV import: + 'column__ignore' => '(ignorați această coloană)', + 'column_account-iban' => 'Contul contului de active (IBAN)', + 'column_account-id' => 'ID-ul contului de activ (care se potrivește cu FF3)', + 'column_account-name' => 'Cont activ (nume)', + 'column_account-bic' => 'Cont activ (BIC)', + 'column_amount' => 'Sumă', + 'column_amount_foreign' => 'Sumă (în monedă străină)', + 'column_amount_debit' => 'Sumă (coloana de debit)', + 'column_amount_credit' => 'Sumă (coloana de credit)', + 'column_amount_negated' => 'Sumă (coloană negată)', + 'column_amount-comma-separated' => 'Sumă (virgula ca separator zecimal)', + 'column_bill-id' => 'ID-ul facturii (care se potrivește cu FF3)', + 'column_bill-name' => 'Nume de factură', + 'column_budget-id' => 'ID-ul bugetului (care se potrivește cu FF3)', + 'column_budget-name' => 'Nume buget', + 'column_category-id' => 'ID-ul categoriei (care se potrivește cu FF3)', + 'column_category-name' => 'Numele categoriei', + 'column_currency-code' => 'Cod valută (ISO 4217)', + 'column_foreign-currency-code' => 'Codul de valută străină (ISO 4217)', + 'column_currency-id' => 'ID-ul monedei (care se potrivește cu FF3)', + 'column_currency-name' => 'Numele monedei (care se potrivește cu FF3)', + 'column_currency-symbol' => 'Simbolul monedei (care se potrivește cu FF3)', + 'column_date-interest' => 'Data de calcul a dobânzii', + 'column_date-book' => 'Data rezervării tranzacției', + 'column_date-process' => 'Data procesării tranzacției', + 'column_date-transaction' => 'Dată', + 'column_date-due' => 'Data expirării tranzacției', + 'column_date-payment' => 'Data plății tranzacției', + 'column_date-invoice' => 'Data facturării tranzacției', + 'column_description' => 'Descriere', + 'column_opposing-iban' => 'Contul opus (IBAN)', + 'column_opposing-bic' => 'Contul opus (BIC)', + 'column_opposing-id' => 'ID-ul contului opus (care se potrivește cu FF3)', + 'column_external-id' => 'ID Extern', + 'column_opposing-name' => 'Contul opus (nume)', + 'column_rabo-debit-credit' => 'Indicatorul specific de debit / credit Rabobank', + 'column_ing-debit-credit' => 'Indicatorul ING de debit / credit specific', + 'column_generic-debit-credit' => 'Indicatorul de debit / credit bancar general', + 'column_sepa_ct_id' => 'Identificator final SEPA', + 'column_sepa_ct_op' => 'Identificatorul contului opus SEPA', + 'column_sepa_db' => 'Identificatorul creditorului SEPA', + 'column_sepa_cc' => 'Cod de compensare SEPA', + 'column_sepa_ci' => 'Identificatorul creditorului SEPA', + 'column_sepa_ep' => 'Scopul extern SEPA', + 'column_sepa_country' => 'Codul țării SEPA', + 'column_sepa_batch_id' => 'ID-ul lotului SEPA', + 'column_tags-comma' => 'Etichete (separate prin virgulă)', + 'column_tags-space' => 'Etichete (separate prin spațiu)', + 'column_account-number' => 'Cont activ (numărul contului)', + 'column_opposing-number' => 'Cont opus (numărul contului)', + 'column_note' => 'Notițe', + 'column_internal-reference' => 'Referință internă', + + // error message + 'duplicate_row' => 'Row #:row (":description") could not be imported. It already exists.', + +]; diff --git a/resources/lang/ro_RO/intro.php b/resources/lang/ro_RO/intro.php new file mode 100644 index 0000000000..5dba558b39 --- /dev/null +++ b/resources/lang/ro_RO/intro.php @@ -0,0 +1,160 @@ +. + */ + +declare(strict_types=1); + +return [ + // index + 'index_intro' => 'Bun venit pe pagina principală a Firefly III. Vă rugăm să parcurgeţi acest intro pentru a vedea cum funcționează Firefly III.', + 'index_accounts-chart' => 'Acest grafic arată soldul curent al conturilor dvs. de active. Puteți selecta conturile vizibile aici în preferințele dvs.', + 'index_box_out_holder' => 'Aceast dreptunghi mic și cele de lângă el vă vor oferi o imagine de ansamblu rapidă a situației financiare.', + 'index_help' => 'Dacă aveți nevoie vreodată de ajutor cu o pagină sau un formular, apăsați acest buton.', + 'index_outro' => 'Cele mai multe pagini ale Firefly III vor începe cu un mic tur ca acesta. Contactați-mă atunci când aveți întrebări sau comentarii. Bucurați-vă!', + 'index_sidebar-toggle' => 'Pentru a crea noi tranzacții, conturi sau alte lucruri, utilizați meniul de sub această pictogramă.', + 'index_cash_account' => 'These are the accounts created so far. You can use the cash account to track cash expenses but it\'s not mandatory of course.', + + // transactions (withdrawal) + 'transactions_create_withdrawal_source' => 'Select your favorite asset account or liability from this dropdown.', + 'transactions_create_withdrawal_destination' => 'Select an expense account here. Leave it empty if you want to make a cash expense.', + 'transactions_create_withdrawal_foreign_currency' => 'Use this field to set a foreign currency and amount.', + 'transactions_create_withdrawal_more_meta' => 'Plenty of other meta data you set in these fields.', + 'transactions_create_withdrawal_split_add' => 'If you want to split a transaction, add more splits with this button', + + // transactions (deposit) + 'transactions_create_deposit_source' => 'Select or type the payee in this auto-completing dropdown/textbox. Leave it empty if you want to make a cash deposit.', + 'transactions_create_deposit_destination' => 'Select an asset or liability account here.', + 'transactions_create_deposit_foreign_currency' => 'Use this field to set a foreign currency and amount.', + 'transactions_create_deposit_more_meta' => 'Plenty of other meta data you set in these fields.', + 'transactions_create_deposit_split_add' => 'If you want to split a transaction, add more splits with this button', + + // transactions (transfer) + 'transactions_create_transfer_source' => 'Select the source asset account here.', + 'transactions_create_transfer_destination' => 'Select the destination asset account here.', + 'transactions_create_transfer_foreign_currency' => 'Use this field to set a foreign currency and amount.', + 'transactions_create_transfer_more_meta' => 'Plenty of other meta data you set in these fields.', + 'transactions_create_transfer_split_add' => 'If you want to split a transaction, add more splits with this button', + + // create account: + 'accounts_create_iban' => 'Dați conturilor dvs. un IBAN valid. Acest lucru ar putea face ca importul de date să fie foarte ușor în viitor.', + 'accounts_create_asset_opening_balance' => 'Conturile de active pot avea un "sold de deschidere", indicând începutul istoricului acestui cont în Firefly III.', + 'accounts_create_asset_currency' => 'Firefly III acceptă mai multe valute. Conturile de active au o monedă principală, pe care trebuie să o setați aici.', + 'accounts_create_asset_virtual' => 'Câteodată este de ajutor să adăugaţi contului dvs. un sold virtual: o sumă suplimentară adăugată sau retrasă întotdeauna din soldul real.', + + // budgets index + 'budgets_index_intro' => 'Bugetele sunt folosite pentru a vă gestiona finanțele; ele sunt una dintre funcțiile de bază ale Firefly III.', + 'budgets_index_set_budget' => 'Stabiliți bugetul total pentru fiecare perioadă, astfel încât Firefly III vă poate spune dacă ați bugetat toți banii disponibili.', + 'budgets_index_see_expenses_bar' => 'Banii cheltuiți vor umple încet această linie.', + 'budgets_index_navigate_periods' => 'Navigați prin perioade de timp pentru a stabili cu ușurință bugetele viitoare.', + 'budgets_index_new_budget' => 'Creați bugete noi după cum doriți.', + 'budgets_index_list_of_budgets' => 'Utilizați acest tabel pentru a stabili sumele pentru fiecare buget și pentru a vedea cum progresaţi.', + 'budgets_index_outro' => 'Pentru a afla mai multe despre bugetare, verificați pictograma de ajutor din colțul din dreapta sus.', + + // reports (index) + 'reports_index_intro' => 'Utilizați aceste rapoarte pentru a obține informații detaliate despre finanțele dumneavoastră.', + 'reports_index_inputReportType' => 'Alegeți un tip de raport. Consultați paginile de ajutor pentru a vedea ce arată fiecare raport.', + 'reports_index_inputAccountsSelect' => 'Puteți exclude sau include conturi de active după cum doriți.', + 'reports_index_inputDateRange' => 'Intervalul de date selectat depinde în întregime de dvs.: de la o zi la 10 ani.', + 'reports_index_extra-options-box' => 'În funcție de raportul pe care l-ați selectat, puteți selecta filtre și opțiuni suplimentare aici. Urmăriți această casetă când modificați tipurile de rapoarte.', + + // reports (reports) + 'reports_report_default_intro' => 'Acest raport vă va oferi o imagine de ansamblu rapidă și cuprinzătoare a finanțelor. Dacă doriți să vedeți altceva, vă rugăm să nu ezitați să mă contactați!', + 'reports_report_audit_intro' => 'Acest raport vă va oferi informații detaliate despre conturile de active.', + 'reports_report_audit_optionsBox' => 'Utilizați aceste casete pentru a afișa sau a ascunde coloanele care vă interesează.', + + 'reports_report_category_intro' => 'Acest raport vă va oferi informații despre una sau mai multe categorii.', + 'reports_report_category_pieCharts' => 'Aceste diagrame vă vor oferi informații despre cheltuielile și veniturile pe categorii sau pe cont.', + 'reports_report_category_incomeAndExpensesChart' => 'Această diagramă arată cheltuielile și veniturile pe categorii.', + + 'reports_report_tag_intro' => 'Acest raport vă va oferi informații despre una sau mai multe etichete.', + 'reports_report_tag_pieCharts' => 'Aceste diagrame vă vor oferi informații despre cheltuielile și veniturile pe etichete, cont, categorie sau buget.', + 'reports_report_tag_incomeAndExpensesChart' => 'Acest grafic prezintă cheltuielile și venitul pe etichetă.', + + 'reports_report_budget_intro' => 'Acest raport vă va oferi informații despre unul sau mai multe bugete.', + 'reports_report_budget_pieCharts' => 'Aceste diagrame vă vor oferi informații despre cheltuielile pe buget sau pe cont.', + 'reports_report_budget_incomeAndExpensesChart' => 'Acest grafic prezintă cheltuielile dvs. pe buget.', + + // create transaction + 'transactions_create_switch_box' => 'Utilizați aceste butoane pentru a comuta rapid tipul de tranzacție pe care doriți să o salvați.', + 'transactions_create_ffInput_category' => 'Puteți scrie în mod liber în acest câmp. Formele create anterior vor fi sugerate.', + 'transactions_create_withdrawal_ffInput_budget' => 'Legați retragerea la un buget pentru un control financiar mai bun.', + 'transactions_create_withdrawal_currency_dropdown_amount' => 'Utilizați acest dropdown atunci când retragerea se face într-o altă monedă.', + 'transactions_create_deposit_currency_dropdown_amount' => 'Utilizați acest dropdown atunci când depozitul dvs. este în altă monedă.', + 'transactions_create_transfer_ffInput_piggy_bank_id' => 'Selectați o pușculiță și conectați acest transfer la economiile dvs.', + + // piggy banks index: + 'piggy-banks_index_saved' => 'Acest câmp vă arată cât de mult ați salvat în fiecare pușculiță.', + 'piggy-banks_index_button' => 'Lângă această bara de progres sunt două butoane (+ și -) pentru a adăuga sau a elimina bani din fiecare pușculiță.', + 'piggy-banks_index_accountStatus' => 'Pentru fiecare cont de activ cu cel puțin o pușculiță, statutul este menționat în acest tabel.', + + // create piggy + 'piggy-banks_create_name' => 'Care este țelul tău? O canapea nouă, o cameră, bani pentru urgențe?', + 'piggy-banks_create_date' => 'Puteți stabili o dată țintă sau un termen limită pentru pușculița dvs..', + + // show piggy + 'piggy-banks_show_piggyChart' => 'Această diagramă va arăta istoria acestei bănci.', + 'piggy-banks_show_piggyDetails' => 'Unele detalii despre pușculița dvs.', + 'piggy-banks_show_piggyEvents' => 'Orice adăugări sau eliminări sunt de asemenea enumerate aici.', + + // bill index + 'bills_index_rules' => 'Aici vedeți care reguli vor verifica dacă acestă factură este afectată', + 'bills_index_paid_in_period' => 'Acest câmp indică momentul în care factura a fost plătită ultima dată.', + 'bills_index_expected_in_period' => 'Acest câmp indică pentru fiecare factură dacă și când se așteaptă să apară următoarea factură.', + + // show bill + 'bills_show_billInfo' => 'Acest tabel prezintă câteva informații generale despre această factură.', + 'bills_show_billButtons' => 'Utilizați acest buton pentru a re-scana tranzacțiile vechi, astfel încât acestea să fie potrivite cu această factură.', + 'bills_show_billChart' => 'Acest grafic arată tranzacțiile legate de această factură.', + + // create bill + 'bills_create_intro' => 'Utilizați facturile pentru a urmări cantitatea de bani pe care o plătiți în fiecare perioadă. Gândiți-vă la cheltuieli cum ar fi chiria, asigurarea sau plățile ipotecare.', + 'bills_create_name' => 'Utilizați un nume descriptiv, cum ar fi "Chirie" sau "Asigurarea de sănătate".', + //'bills_create_match' => 'To match transactions, use terms from those transactions or the expense account involved. All words must match.', + 'bills_create_amount_min_holder' => 'Selectați o sumă minimă și maximă pentru această factură.', + 'bills_create_repeat_freq_holder' => 'Cele mai multe facturi se repetă lunar, dar puteți stabili o altă frecvență aici.', + 'bills_create_skip_holder' => 'Dacă o factură se repetă la fiecare 2 săptămâni, câmpul "săriți" ar trebui să fie setat la "1" pentru a sări peste o săptămână.', + + // rules index + 'rules_index_intro' => 'Firefly III vă permite să gestionați reguli, care vor fi aplicate automat pentru orice tranzacție pe care o creați sau o editați.', + 'rules_index_new_rule_group' => 'Puteți combina regulile în grupuri pentru o gestionare mai ușoară.', + 'rules_index_new_rule' => 'Creați câte reguli doriți.', + 'rules_index_prio_buttons' => 'Comandați-le în orice fel doriți.', + 'rules_index_test_buttons' => 'Puteți testa regulile sau le puteți aplica tranzacțiilor existente.', + 'rules_index_rule-triggers' => 'Regulile au "declanșatoare" și "acțiuni" pe care le puteți comanda prin drag-and-drop.', + 'rules_index_outro' => 'Asigurați-vă că verificați paginile de ajutor utilizând pictograma (?) din partea dreaptă sus!', + + // create rule: + 'rules_create_mandatory' => 'Alegeți un titlu descriptiv și stabiliți când ar trebui să fie declanșată regula.', + 'rules_create_ruletriggerholder' => 'Adăugați cât mai mulți declanșatori, după cum doriți, dar rețineți că toate declanșatoarele trebuie să se potrivească înainte de declanșarea oricăror acțiuni.', + 'rules_create_test_rule_triggers' => 'Utilizați acest buton pentru a vedea care tranzacții s-ar potrivi regulii dvs.', + 'rules_create_actions' => 'Setați câte acțiuni doriți.', + + // preferences + 'preferences_index_tabs' => 'Mai multe opțiuni sunt disponibile în spatele acestor file.', + + // currencies + 'currencies_index_intro' => 'Firefly III acceptă mai multe valute, pe care le puteți schimba în această pagină.', + 'currencies_index_default' => 'Firefly III are o monedă implicită.', + 'currencies_index_buttons' => 'Utilizați aceste butoane pentru a modifica moneda prestabilită sau a activa alte valute.', + + // create currency + 'currencies_create_code' => 'Acest cod ar trebui să fie conform ISO (căutați pe Google noua dvs. monedă).', +]; diff --git a/resources/lang/ro_RO/list.php b/resources/lang/ro_RO/list.php new file mode 100644 index 0000000000..55afd3d79f --- /dev/null +++ b/resources/lang/ro_RO/list.php @@ -0,0 +1,136 @@ +. + */ + +declare(strict_types=1); + +return [ + 'buttons' => 'Butoane', + 'icon' => 'Iconiță', + 'id' => 'ID', + 'create_date' => 'Creat la', + 'update_date' => 'actualizat la', + 'updated_at' => 'actualizat la', + 'balance_before' => 'Sold înainte', + 'balance_after' => 'Sold după', + 'name' => 'Nume', + 'role' => 'Rol', + 'currentBalance' => 'Sold curent', + 'linked_to_rules' => 'Reguli relevante', + 'active' => 'Este activ?', + 'transaction_type' => 'Type', + 'lastActivity' => 'Ultima activitate', + 'balanceDiff' => 'Diferența de sold', + 'matchesOn' => 'Se potrivește', + 'account_type' => 'Tip de cont', + 'created_at' => 'Creat la', + 'account' => 'Cont', + 'matchingAmount' => 'Sumă', + 'split_number' => 'Împarte #', + 'destination' => 'Destinație', + 'source' => 'Sursă', + 'next_expected_match' => 'Următoarea potrivire așteptată', + 'automatch' => 'Potrivire automată?', + 'repeat_freq' => 'Repetă', + 'description' => 'Descriere', + 'amount' => 'Sumă', + 'internal_reference' => 'Referință internă', + 'date' => 'Dată', + 'interest_date' => 'Dată de interes', + 'book_date' => 'Data revervării', + 'process_date' => 'Data procesării', + 'due_date' => 'Data scadentă', + 'payment_date' => 'Data de plată', + 'invoice_date' => 'Data facturii', + 'interal_reference' => 'Referință internă', + 'notes' => 'Notițe', + 'from' => 'Din', + 'piggy_bank' => 'Pușculiță', + 'to' => 'La', + 'budget' => 'Buget', + 'category' => 'Categorii', + 'bill' => 'Factură', + 'withdrawal' => 'Retragere', + 'deposit' => 'Depozit', + 'transfer' => 'Transfer', + 'type' => 'Tip', + 'completed' => 'Finalizată', + 'iban' => 'IBAN', + 'paid_current_period' => 'Plătit această perioadă', + 'email' => 'E-mail', + 'registered_at' => 'Înregistrat la', + 'is_blocked' => 'Este blocat', + 'is_admin' => 'Este admin', + 'has_two_factor' => 'Are autentificare prin 2 factori (2FA)', + 'blocked_code' => 'Cod de blocare', + 'source_account' => 'Contul sursă', + 'destination_account' => 'Contul de destinație', + 'accounts_count' => 'Număr de conturi', + 'journals_count' => 'Număr de tranzacții', + 'attachments_count' => 'Număr de atașamente', + 'bills_count' => 'Număr de facturi', + 'categories_count' => 'Număr de categorii', + 'import_jobs_count' => 'Număr de servicii importate', + 'budget_count' => 'Număr de bugete', + 'rule_and_groups_count' => 'Număr de reguli și grupuri de reguli', + 'tags_count' => 'Număr de etichete', + 'tags' => 'Etichete', + 'inward' => 'Descrierea interioară', + 'outward' => 'Descrierea exterioară', + 'number_of_transactions' => 'Număr de tranzacții', + 'total_amount' => 'Valoare totală', + 'sum' => 'Sumă', + 'sum_excluding_transfers' => 'Sumă (cu excepția transferurilor)', + 'sum_withdrawals' => 'Suma retragerilor', + 'sum_deposits' => 'Suma depozitelor', + 'sum_transfers' => 'Suma transferurilor', + 'reconcile' => 'Reconcilia', + 'account_on_spectre' => 'Cont (Spectre)', + 'account_on_ynab' => 'Cont (YNAB)', + 'do_import' => 'Importați din acest cont', + 'sepa_ct_id' => 'Identificator final SEPA', + 'sepa_ct_op' => 'Identificatorul contului opus SEPA', + 'sepa_db' => 'Identificatorul mandatului SEPA', + 'sepa_country' => 'Codul țării SEPA', + 'sepa_cc' => 'Codul de compensare SEPA', + 'sepa_ep' => 'Scopul extern SEPA', + 'sepa_ci' => 'Identificatorul creditorului SEPA', + 'sepa_batch_id' => 'ID-ul lotului SEPA', + 'external_id' => 'ID Extern', + 'account_at_bunq' => 'Cont cu bunq', + 'file_name' => 'Nume de fișier', + 'file_size' => 'Mărime fișier', + 'file_type' => 'Tip fișier', + 'attached_to' => 'Atașat la', + 'file_exists' => 'Fișierul există', + 'spectre_bank' => 'Bancă', + 'spectre_last_use' => 'Ultima logare', + 'spectre_status' => 'Statut', + 'bunq_payment_id' => 'ID plată bunq', + 'repetitions' => 'Repetări', + 'title' => 'Titlu', + 'transaction_s' => 'Tranzacție(tranzacții)', + 'field' => 'Câmp', + 'value' => 'Valoare', + 'interest' => 'Interes', + 'interest_period' => 'perioadă de interes', + 'liability_type' => 'Tip de provizion', +]; diff --git a/resources/lang/ro_RO/pagination.php b/resources/lang/ro_RO/pagination.php new file mode 100644 index 0000000000..e4610c10f4 --- /dev/null +++ b/resources/lang/ro_RO/pagination.php @@ -0,0 +1,28 @@ +. + */ + +declare(strict_types=1); + +return [ + 'previous' => '« Anterior', + 'next' => 'Următor »', +]; diff --git a/resources/lang/ro_RO/passwords.php b/resources/lang/ro_RO/passwords.php new file mode 100644 index 0000000000..cf9c824b47 --- /dev/null +++ b/resources/lang/ro_RO/passwords.php @@ -0,0 +1,32 @@ +. + */ + +declare(strict_types=1); + +return [ + 'password' => 'Parolele trebuie să aibă cel puțin șase caractere și să corespundă confirmării.', + 'user' => 'Nu putem găsi un utilizator cu acea adresă de e-mail.', + 'token' => 'Acest token pentru resetarea parolei nu este valid.', + 'sent' => 'V-am trimis prin e-mail adresa web de resetare a parolei!', + 'reset' => 'Parola a fost resetată!', + 'blocked' => 'Oricum, bună încercare.', +]; diff --git a/resources/lang/ro_RO/validation.php b/resources/lang/ro_RO/validation.php new file mode 100644 index 0000000000..72c6f61ff9 --- /dev/null +++ b/resources/lang/ro_RO/validation.php @@ -0,0 +1,195 @@ +. + */ + +declare(strict_types=1); + +return [ + 'iban' => 'Acesta nu este un IBAN valabil.', + 'zero_or_more' => 'Valoarea nu poate fi negativă.', + 'date_or_time' => 'Valoarea trebuie să fie o dată validă sau o valoare în timp (ISO 8601).', + 'source_equals_destination' => 'Contul sursă este egal cu contul de destinație.', + 'unique_account_number_for_user' => 'Se pare că acest număr de cont este deja utilizat.', + 'unique_iban_for_user' => 'Se pare că acest IBAN este deja utilizat.', + 'deleted_user' => 'Din cauza constrângerilor de securitate, nu vă puteți înregistra utilizând această adresă de e-mail.', + 'rule_trigger_value' => 'Această valoare nu este validă pentru declanșatorul selectat.', + 'rule_action_value' => 'Această valoare nu este validă pentru acțiunea selectată.', + 'file_already_attached' => 'Fișierul încărcat ":name" este deja atașat acestui obiect.', + 'file_attached' => 'Fișierul ":name" a fost încărcat cu succes.', + 'must_exist' => 'Câmpul ID :attribute nu există în baza de date.', + 'all_accounts_equal' => 'Toate conturile din acest câmp trebuie să fie egale.', + 'group_title_mandatory' => 'Un titlu de grup este obligatoriu atunci când există mai multe tranzacții.', + 'transaction_types_equal' => 'Toate împărțirile trebuie să fie de același tip.', + 'invalid_transaction_type' => 'Tip tranzacție nevalidă.', + 'invalid_selection' => 'Selecția dvs. este nevalidă.', + 'belongs_user' => 'Această valoare este nevalidă pentru acest câmp.', + 'at_least_one_transaction' => 'Aveți nevoie de cel puțin o tranzacție.', + 'at_least_one_repetition' => 'Aveți nevoie de cel puțin o repetare.', + 'require_repeat_until' => 'Solicitați fie un număr de repetări, fie o dată de încheiere (repeat_until). Nu amândouă.', + 'require_currency_info' => 'Conținutul acestui câmp este nevalid fără informații despre monedă.', + 'require_currency_amount' => 'The content of this field is invalid without foreign amount information.', + 'equal_description' => 'Descrierea tranzacției nu trebuie să fie egală cu descrierea globală.', + 'file_invalid_mime' => 'Fișierul ":name" este de tip ":mime" și nu este acceptat ca o încărcare nouă.', + 'file_too_large' => 'Fișierul ":name" este prea mare.', + 'belongs_to_user' => 'Valoarea :attribute este necunoscută.', + 'accepted' => 'Câmpul :attribute trebuie să fie acceptat.', + 'bic' => 'Acesta nu este un BIC valabil.', + 'at_least_one_trigger' => 'Regula trebuie să aibă cel puțin un declanșator.', + 'at_least_one_action' => 'Regula trebuie să aibă cel puțin o acțiune.', + 'base64' => 'Acest lucru nu este valabil pentru datele encoded base64.', + 'model_id_invalid' => 'ID-ul dat nu pare valid pentru acest model.', + 'more' => ':attribute trebuie să fie mai mare decât zero.', + 'less' => ':attribute trebuie să fie mai mic decât 10,000,000', + 'active_url' => ':attribute nu este o adresă URL validă.', + 'after' => ':attribute trebuie să fie o dată ulterioară :date.', + 'alpha' => ':attribute poate conține numai litere.', + 'alpha_dash' => ':attribute poate conține numai litere, numere și liniuțe.', + 'alpha_num' => ':attribute poate conține numai litere și numere.', + 'array' => ':attribute trebuie să fie o matrice (array).', + 'unique_for_user' => 'Există deja o intrare cu acest :attribute.', + 'before' => ':attribute trebuie să fie o dată înainte de :date.', + 'unique_object_for_user' => 'Acest nume este deja folosit.', + 'unique_account_for_user' => 'Acest nume de cont este deja utilizat.', + 'between.numeric' => ':attribute trebuie să fie între :min și :max.', + 'between.file' => ':attribute trebuie să fie între :min și :max kilobyți.', + 'between.string' => ':attribute trebuie să fie între :min și :max caractere.', + 'between.array' => ':attribute trebuie să aibă între :min și :max articole.', + 'boolean' => ':attribute trebuie să fie adevărat sau fals.', + 'confirmed' => ':attribute confirmarea nu se potrivește.', + 'date' => ':attribute nu este o dată validă.', + 'date_format' => ':attribute nu se potrivește cu formatul :format.', + 'different' => ':attribute și :other trebuie să fie diferite.', + 'digits' => ':attribute trebuie să fie :digits digits.', + 'digits_between' => ':attribute trebuie să fie între :min și :max digits.', + 'email' => ':attribute trebuie să fie o adresă de e-mail validă.', + 'filled' => 'Câmpul :attribute este necesar.', + 'exists' => 'Câmpul selectat :attribute este invalid.', + 'image' => 'Câmpul :attribute trebuie să fie o imagine.', + 'in' => 'Câmpul selectat :attribute este invalid.', + 'integer' => ':attribute trebuie să fie un număr întreg.', + 'ip' => ':attribute trebuie să fie o adresă IP valabilă.', + 'json' => ':attribute trebuie să fie un șir JSON valid.', + 'max.numeric' => ':attribute nu poate fi mai mare decât :max.', + 'max.file' => ':attribute nu poate fi mai mare decât :max kilobyți.', + 'max.string' => ':attribute nu poate fi mai mare decât :max caractere.', + 'max.array' => ':attribute nu poate avea mai mult de :max articole.', + 'mimes' => ':attribute trebuie să fie un fișier de tipul: :values.', + 'min.numeric' => ':attribute trebuie să aibă măcar :min.', + 'lte.numeric' => ':attribute trebuie să fie mai mic sau egal :value.', + 'min.file' => ':attribute trebuie să aibă măcar :min kilobyți.', + 'min.string' => ':attribute trebuie să aibă măcar :min caractere.', + 'min.array' => ':attribute trebuie să aibă măcar :min articole.', + 'not_in' => 'Câmpul selectat :attribute este invalid.', + 'numeric' => 'Câmpul :attribute trebuie să fie un număr.', + 'numeric_native' => 'Suma nativă trebuie să fie un număr.', + 'numeric_destination' => 'Suma destinației trebuie să fie un număr.', + 'numeric_source' => 'Suma sursei trebuie să fie un număr.', + 'regex' => 'Câmpul :attribute are format nevalid.', + 'required' => 'Câmpul :attribute este obligatoriu.', + 'required_if' => 'Câmpul :attribute este obligatoriu când :other este :value.', + 'required_unless' => 'Câmpul :attribute este obligatoriu dacă nu :other este în :values.', + 'required_with' => 'Câmpul :attribute este obligatoriu când :values este prezent.', + 'required_with_all' => 'Câmpul :attribute este obligatoriu când :values este prezent.', + 'required_without' => 'Câmpul :attribute este obligatoriu când :values nu este prezent.', + 'required_without_all' => 'Câmpul :attribute este obligatoriu când nici unul dintre :values este prezent.', + 'same' => ':attribute și :other trebuie să se potrivească.', + 'size.numeric' => ':attribute trebuie să fie :size.', + 'amount_min_over_max' => 'Suma minimă nu poate fi mai mare decât suma maximă.', + 'size.file' => ':attribute trebuie să aibă :size kilobyți.', + 'size.string' => ':attribute trebuie să aibă :size caractere.', + 'size.array' => ':attribute trebuie să contină :size articole.', + 'unique' => ':attribute a fost deja luat.', + 'string' => ':attribute trebuie să fie un șir de caractere.', + 'url' => ':attribute format este invalid.', + 'timezone' => ':attribute trebuie să fie o zonă validă.', + '2fa_code' => 'Câmpul :attribute este invalid.', + 'dimensions' => ':attribute are dimensiuni de imagine nevalide.', + 'distinct' => 'Câmpul :attribute are o valoare duplicată.', + 'file' => ':attribute trebuie să fie un fișier.', + 'in_array' => 'Câmpul :attribute nu există în :other.', + 'present' => 'Câmpul :attribute trebuie să fie prezent.', + 'amount_zero' => 'Suma totală nu poate fi zero.', + 'unique_piggy_bank_for_user' => 'Numele pușculiței trebuie să fie unic.', + 'secure_password' => 'Aceasta nu este o parolă sigură. Vă rugăm să încercați din nou. Pentru mai multe informații, vizitați https://bit.ly/FF3-password-security', + 'valid_recurrence_rep_type' => 'Tip de repetare nevalid pentru tranzacțiile recurente.', + 'valid_recurrence_rep_moment' => 'Momentul repetiției nevalid pentru acest tip de repetare.', + 'invalid_account_info' => 'Informațiile contului nevalide.', + 'attributes' => [ + 'email' => 'adresă e-mail', + 'description' => 'descriere', + 'amount' => 'sumă', + 'name' => 'nume', + 'piggy_bank_id' => 'ID-ul pușculiței', + 'targetamount' => 'suma țintă', + 'opening_balance_date' => 'opening balance date', + 'opening_balance' => 'opening balance', + 'match' => 'potrivire', + 'amount_min' => 'suma minimă', + 'amount_max' => 'suma maximă', + 'title' => 'titlu', + 'tag' => 'etichetă', + 'transaction_description' => 'descrierea tranzacției', + 'rule-action-value.1' => 'valoarea regulii de acțiune #1', + 'rule-action-value.2' => 'valoarea regulii de acțiune #2', + 'rule-action-value.3' => 'valoarea regulii de acțiune #3', + 'rule-action-value.4' => 'valoarea regulii de acțiune #4', + 'rule-action-value.5' => 'valoarea regulii de acțiune #5', + 'rule-action.1' => 'regula acțiunii #1', + 'rule-action.2' => 'regula acțiunii #2', + 'rule-action.3' => 'regula acțiunii #3', + 'rule-action.4' => 'regula acțiunii #4', + 'rule-action.5' => 'regula acțiunii #5', + 'rule-trigger-value.1' => 'valoarea regulii de declanșare #1', + 'rule-trigger-value.2' => 'valoarea regulii de declanșare #2', + 'rule-trigger-value.3' => 'valoarea regulii de declanșare #3', + 'rule-trigger-value.4' => 'valoarea regulii de declanșare #4', + 'rule-trigger-value.5' => 'valoarea regulii de declanșare #5', + 'rule-trigger.1' => 'regulă de declanșare #1', + 'rule-trigger.2' => 'regulă de declanșare #2', + 'rule-trigger.3' => 'regulă de declanșare #3', + 'rule-trigger.4' => 'regulă de declanșare #4', + 'rule-trigger.5' => 'regulă de declanșare #5', + ], + + // validation of accounts: + 'withdrawal_source_need_data' => 'Trebuie să continuați să obțineți un ID de cont sursă valabil și / sau un nume de cont sursă valabil.', + 'withdrawal_source_bad_data' => 'Nu s-a găsit un cont sursă valabil la căutarea ID ":id" sau nume ":name".', + 'withdrawal_dest_need_data' => 'Trebuie să continuați să obțineți un ID de cont de destinație valabil și / sau un nume de cont de destinație valabil.', + 'withdrawal_dest_bad_data' => 'Could not find a valid destination account when searching for ID ":id" or name ":name".', + + 'deposit_source_need_data' => 'Trebuie să continuați să obțineți un ID de cont sursă valabil și / sau un nume de cont sursă valabil.', + 'deposit_source_bad_data' => 'Nu s-a găsit un cont sursă valabil la căutarea ID ":id" sau nume ":name".', + 'deposit_dest_need_data' => 'Trebuie să continuați să obțineți un ID de cont de destinație valabil și / sau un nume de cont de destinație valabil.', + 'deposit_dest_bad_data' => 'Nu s-a găsit un cont de destinaţie valabil la căutarea ID ":id" sau nume ":name".', + + 'transfer_source_need_data' => 'Trebuie să continuați să obțineți un ID de cont sursă valabil și / sau un nume de cont sursă valabil.', + 'transfer_source_bad_data' => 'Nu s-a găsit un cont sursă valabil la căutarea ID ":id" sau nume ":name".', + 'transfer_dest_need_data' => 'Trebuie să continuați să obțineți un ID de cont de destinație valabil și / sau un nume de cont de destinație valabil.', + 'transfer_dest_bad_data' => 'Nu s-a găsit un cont de destinaţie valabil la căutarea ID ":id" sau nume ":name".', + 'need_id_in_edit' => 'Each split must have transaction_journal_id (either valid ID or 0).', + + 'ob_source_need_data' => 'Need to get a valid source account ID and/or valid source account name to continue.', + 'ob_dest_need_data' => 'Need to get a valid destination account ID and/or valid destination account name to continue.', + 'ob_dest_bad_data' => 'Could not find a valid destination account when searching for ID ":id" or name ":name".', + + 'generic_invalid_source' => 'You can\'t use this account as the source account.', + 'generic_invalid_destination' => 'You can\'t use this account as the destination account.', +]; diff --git a/resources/lang/ru_RU/breadcrumbs.php b/resources/lang/ru_RU/breadcrumbs.php index 41a0facb3a..54ed698e08 100644 --- a/resources/lang/ru_RU/breadcrumbs.php +++ b/resources/lang/ru_RU/breadcrumbs.php @@ -23,35 +23,37 @@ declare(strict_types=1); return [ - 'home' => 'Главная', - 'edit_currency' => 'Редактирование валюты ":name"', - 'delete_currency' => 'Удаление валюты ":name"', - 'newPiggyBank' => 'Создание новой копилки', - 'edit_piggyBank' => 'Редактирование копилки ":name"', - 'preferences' => 'Настройки', - 'profile' => 'Профиль', - 'changePassword' => 'Изменение вашего пароля', - 'change_email' => 'Изменить адрес электронной почты', - 'bills' => 'Счета к оплате', - 'newBill' => 'Новый счёт к оплате', - 'edit_bill' => 'Редактирование счёта к оплате ":name"', - 'delete_bill' => 'Удаление счёта к оплате ":name"', - 'reports' => 'Отчёты', - 'search_result' => 'Результаты поиска для ":query"', - 'withdrawal_list' => 'Мои расходы', - 'deposit_list' => 'Мои доходы', - 'transfer_list' => 'Переводы', - 'transfers_list' => 'Переводы', - 'reconciliation_list' => 'Сверка', - 'create_withdrawal' => 'Создать новый расход', - 'create_deposit' => 'Создать новый доход', - 'create_transfer' => 'Создать новый перевод', - 'edit_journal' => 'Редактирование транзакции ":description"', - 'edit_reconciliation' => 'Редактировать ":description"', - 'delete_journal' => 'Удаление транзакции ":description"', - 'tags' => 'Метки', - 'createTag' => 'Создать новую метку', - 'edit_tag' => 'Редактирование метки ":tag"', - 'delete_tag' => 'Удаление метки ":tag"', - 'delete_journal_link' => 'Удалить связь между транзакциями', + 'home' => 'Главная', + 'edit_currency' => 'Редактирование валюты ":name"', + 'delete_currency' => 'Удаление валюты ":name"', + 'newPiggyBank' => 'Создание новой копилки', + 'edit_piggyBank' => 'Редактирование копилки ":name"', + 'preferences' => 'Настройки', + 'profile' => 'Профиль', + 'changePassword' => 'Изменение вашего пароля', + 'change_email' => 'Изменить адрес электронной почты', + 'bills' => 'Счета к оплате', + 'newBill' => 'Новый счёт к оплате', + 'edit_bill' => 'Редактирование счёта к оплате ":name"', + 'delete_bill' => 'Удаление счёта к оплате ":name"', + 'reports' => 'Отчёты', + 'search_result' => 'Результаты поиска для ":query"', + 'withdrawal_list' => 'Мои расходы', + 'Withdrawal_list' => 'Expenses', + 'deposit_list' => 'Мои доходы', + 'transfer_list' => 'Переводы', + 'transfers_list' => 'Переводы', + 'reconciliation_list' => 'Сверка', + 'create_withdrawal' => 'Создать новый расход', + 'create_deposit' => 'Создать новый доход', + 'create_transfer' => 'Создать новый перевод', + 'create_new_transaction' => 'Создать новую транзакцию', + 'edit_journal' => 'Редактирование транзакции ":description"', + 'edit_reconciliation' => 'Редактировать ":description"', + 'delete_journal' => 'Удаление транзакции ":description"', + 'tags' => 'Метки', + 'createTag' => 'Создать новую метку', + 'edit_tag' => 'Редактирование метки ":tag"', + 'delete_tag' => 'Удаление метки ":tag"', + 'delete_journal_link' => 'Удалить связь между транзакциями', ]; diff --git a/resources/lang/ru_RU/firefly.php b/resources/lang/ru_RU/firefly.php index 84553f3944..1c7616f0b9 100644 --- a/resources/lang/ru_RU/firefly.php +++ b/resources/lang/ru_RU/firefly.php @@ -33,7 +33,7 @@ return [ 'last_seven_days' => 'Последние 7 дней', 'last_thirty_days' => 'Последние 30 дней', 'welcomeBack' => 'Что происходит с моими финансами?', - 'welcome_back' => 'Что происходит с моими финансами?', + 'welcome_back' => 'Что происходит с моими финансами?', 'everything' => 'Всё', 'today' => 'сегодня', 'customRange' => 'Другой интервал', @@ -54,6 +54,7 @@ return [ 'create_new_stuff' => 'Создать новый объект', 'new_withdrawal' => 'Новый расход', 'create_new_transaction' => 'Создать новую транзакцию', + 'new_transaction' => 'Новая транзакция', 'go_to_asset_accounts' => 'Просмотр ваших основных счетов', 'go_to_budgets' => 'Перейти к вашим бюджетам', 'go_to_categories' => 'Перейти к вашим категориям', @@ -82,28 +83,32 @@ return [ 'help_for_this_page' => 'Справка по этой странице', 'no_help_could_be_found' => 'Невозможно найти текст справки.', 'no_help_title' => 'Нам очень жаль, но произошла ошибка.', - 'two_factor_welcome' => 'Привет, :user!', - 'two_factor_enter_code' => 'Чтобы продолжить, введите ваш код двухфакторной аутентификации. Ваше приложение может сгенерировать его для вас.', - 'two_factor_code_here' => 'Введите код здесь', - 'two_factor_title' => 'Двухфакторная аутентификация', - 'authenticate' => 'Аутентифицироваться', - 'two_factor_forgot_title' => 'Утерян код для двухфакторной аутентификации', - 'two_factor_forgot' => 'Я забыл свой ключ для двухфакторной авторизации.', - 'two_factor_lost_header' => 'Потеряли вашу двухфакторную аутентификацию?', - 'two_factor_lost_intro' => 'К сожалению, это не то, что вы можете сбросить с помощью веб-интерфейса. У вас есть два варианта.', - 'two_factor_lost_fix_self' => 'Если вы запустили свой собственный сервер Firefly III, ознакомьтесь с лог файлами в storage/logs для получения инструкций.', - 'two_factor_lost_fix_owner' => 'Иначе, свяжитесь по email с владельцем сайта :site_owner и попросите сбросить вашу двухфакторную аутентификацию.', - 'warning_much_data' => 'Загрузка данных за :days дней может занять некоторое время.', - 'registered' => 'Вы зарегистрировались успешно!', - 'Default asset account' => 'Счёт по умолчанию', - 'no_budget_pointer' => 'У вас пока нет бюджетов. Вам следует создать их на странице бюджеты. Бюджеты помогут вам отслеживать расходы.', - 'Savings account' => 'Сберегательный счет', - 'Credit card' => 'Кредитная карта', - 'source_accounts' => 'Исходный счет(а)', - 'destination_accounts' => 'Счет(а) назначения', - 'user_id_is' => 'Ваш id пользователя :user', - 'field_supports_markdown' => 'Это поле поддерживает Markdown.', - 'need_more_help' => 'Если вам нужна дополнительная помощь по использованию Firefly III, пожалуйста, откройте issue на Github (желательно, на английском языке).', + 'two_factor_welcome' => 'Hello!', + 'two_factor_enter_code' => 'Чтобы продолжить, введите ваш код двухфакторной аутентификации. Ваше приложение может сгенерировать его для вас.', + 'two_factor_code_here' => 'Введите код здесь', + 'two_factor_title' => 'Двухфакторная аутентификация', + 'authenticate' => 'Аутентифицироваться', + 'two_factor_forgot_title' => 'Утерян код для двухфакторной аутентификации', + 'two_factor_forgot' => 'Я забыл свой ключ для двухфакторной авторизации.', + 'two_factor_lost_header' => 'Потеряли вашу двухфакторную аутентификацию?', + 'two_factor_lost_intro' => 'If you lost your backup codes as well, you have bad luck. This is not something you can fix from the web interface. You have two choices.', + 'two_factor_lost_fix_self' => 'If you run your own instance of Firefly III, check the logs in storage/logs for instructions, or run docker logs <container_id> to see the instructions (refresh this page).', + 'two_factor_lost_fix_owner' => 'Иначе, свяжитесь по email с владельцем сайта :site_owner и попросите сбросить вашу двухфакторную аутентификацию.', + 'mfa_backup_code' => 'You have used a backup code to login to Firefly III. It can\'t be used again, so cross it from your list.', + 'pref_two_factor_new_backup_codes' => 'Get new backup codes', + 'pref_two_factor_backup_code_count' => 'You have :count valid backup code(s).', + '2fa_i_have_them' => 'I stored them!', + 'warning_much_data' => 'Загрузка данных за :days дней может занять некоторое время.', + 'registered' => 'Вы зарегистрировались успешно!', + 'Default asset account' => 'Счёт по умолчанию', + 'no_budget_pointer' => 'У вас пока нет бюджетов. Вам следует создать их на странице бюджеты. Бюджеты помогут вам отслеживать расходы.', + 'Savings account' => 'Сберегательный счет', + 'Credit card' => 'Кредитная карта', + 'source_accounts' => 'Исходный счет(а)', + 'destination_accounts' => 'Счет(а) назначения', + 'user_id_is' => 'Ваш id пользователя :user', + 'field_supports_markdown' => 'Это поле поддерживает Markdown.', + 'need_more_help' => 'Если вам нужна дополнительная помощь по использованию Firefly III, пожалуйста, откройте issue на Github (желательно, на английском языке).', 'reenable_intro_text' => 'Вы также можете повторно включить обучение для начинающих.', 'intro_boxes_after_refresh' => 'Блоки с подсказками появятся, когда вы обновите страницу.', 'show_all_no_filter' => 'Показать все транзакции без группировки по датам.', @@ -220,21 +225,23 @@ return [ 'search_query' => 'Запрос', 'search_found_transactions' => 'Firefly III нашёл :count транзакций за :time секунд.', 'search_for_query' => 'Firefly III ищет транзакции со всеми этими словами: :query', - 'search_modifier_amount_is' => 'Сумма точно равна :value', - 'search_modifier_amount' => 'Сумма точно равна :value', - 'search_modifier_amount_max' => 'Сумма больше, чем :value', - 'search_modifier_amount_min' => 'Сумма не менее, чем :value', - 'search_modifier_amount_less' => 'Сумма меньше, чем :value', - 'search_modifier_amount_more' => 'Сумма больше, чем :value', - 'search_modifier_source' => 'Счёт-источник = :value', - 'search_modifier_destination' => 'Счёт назначения = :value', - 'search_modifier_category' => 'Категория = :value', - 'search_modifier_budget' => 'Бюджет = :value', - 'search_modifier_bill' => 'Счёт на оплату = :value', - 'search_modifier_type' => 'Transaction type is :value', - 'search_modifier_date' => 'Transaction date is :value', - 'search_modifier_date_before' => 'Transaction date is before :value', - 'search_modifier_date_after' => 'Transaction date is after :value', + 'search_modifier_amount_is' => 'Сумма точно равна :value', + 'search_modifier_amount' => 'Сумма точно равна :value', + 'search_modifier_amount_max' => 'Сумма больше, чем :value', + 'search_modifier_amount_min' => 'Сумма не менее, чем :value', + 'search_modifier_amount_less' => 'Сумма меньше, чем :value', + 'search_modifier_amount_more' => 'Сумма больше, чем :value', + 'search_modifier_source' => 'Счёт-источник = :value', + 'search_modifier_from' => 'Source account is :value', + 'search_modifier_destination' => 'Счёт назначения = :value', + 'search_modifier_to' => 'Destination account is :value', + 'search_modifier_category' => 'Категория = :value', + 'search_modifier_budget' => 'Бюджет = :value', + 'search_modifier_bill' => 'Счёт на оплату = :value', + 'search_modifier_type' => 'Transaction type is :value', + 'search_modifier_date' => 'Transaction date is :value', + 'search_modifier_date_before' => 'Transaction date is before :value', + 'search_modifier_date_after' => 'Transaction date is after :value', 'search_modifier_on' => 'Transaction date is :value', 'search_modifier_before' => 'Transaction date is before :value', 'search_modifier_after' => 'Transaction date is after :value', @@ -257,33 +264,6 @@ return [ 'half-year' => 'раз в полгода', 'yearly' => 'ежегодно', - // export data: - 'import_and_export' => 'Импорт и экспорт', - 'export_data' => 'Экспорт данных', - 'export_and_backup_data' => 'Экспорт данных', - 'export_data_intro' => 'Используйте экспортированные данные для перехода на новое финансовое приложение. Обратите внимание, что эти файлы не предназначены для резервного копирования. Они не содержат достаточного количества данных, чтобы полностью восстановить новую копию Firefly III. Если вы хотите сделать резервную копию своих данных, сделайте резервную копию базы данных напрямую.', - 'export_format' => 'Формат для экспорта', - 'export_format_csv' => 'Значения, разделенные запятыми (CSV)', - 'export_format_mt940' => 'Формат, совместимый с MT940', - 'include_old_uploads_help' => 'Firefly III не выбрасывает исходные CSV-файлы, которые вы импортировали в прошлом. Вы можете включить их в свой экспорт.', - 'do_export' => 'Экспорт', - 'export_status_never_started' => 'Экспорт пока не начался', - 'export_status_make_exporter' => 'Создание задания на экспорт...', - 'export_status_collecting_journals' => 'Сбор ваших транзакций...', - 'export_status_collected_journals' => 'Ваши транзакции собраны!', - 'export_status_converting_to_export_format' => 'Преобразование ваших транзакций...', - 'export_status_converted_to_export_format' => 'Ваши транзакции преобразованы!', - 'export_status_creating_journal_file' => 'Создание файла экспорта...', - 'export_status_created_journal_file' => 'Файл экспорта создан!', - 'export_status_collecting_attachments' => 'Сбор всех ваших вложений...', - 'export_status_collected_attachments' => 'Все ваши вложения собраны!', - 'export_status_collecting_old_uploads' => 'Сбор всех ваших предыдущих загрузок...', - 'export_status_collected_old_uploads' => 'Все ваши предыдущие загрузки собраны!', - 'export_status_creating_zip_file' => 'Создание zip-файла...', - 'export_status_created_zip_file' => 'Создан zip-файл!', - 'export_status_finished' => 'Экспорт успешно завершен! Ура!', - 'export_data_please_wait' => 'Подождите пожалуйста...', - // rules 'rules' => 'Правила', 'rule_name' => 'Название правила', @@ -502,30 +482,33 @@ return [ 'pref_custom_fiscal_year_help' => 'Для стран, в которых финансовый год начинается не 1 января, а заканчивается не 31 декабря, вы должны указать даты начала и окончания финансового года', 'pref_fiscal_year_start_label' => 'Дата начала финансового года', 'pref_two_factor_auth' => 'Двухэтапная аутентификация', - 'pref_two_factor_auth_help' => 'Когда вы включаете двухэтапную аутентификацию (также известную как двухфакторная аутентификация), вы добавляете дополнительный уровень безопасности в свою учётную запись. Вы входите в систему со своим паролем и ещё чем-то, что есть только у вас (код подтверждения). Коды подтверждения генерируются приложением на вашем телефоне, например, Authy или Google Authenticator.', - 'pref_enable_two_factor_auth' => 'Включить двухэтапную аутентификацию', - 'pref_two_factor_auth_disabled' => 'Двухэтапную код подтверждения удалён и отключен', - 'pref_two_factor_auth_remove_it' => 'Не забудьте удалить учётную запись из своего приложения для аутентификации!', - 'pref_two_factor_auth_code' => 'Проверить код', - 'pref_two_factor_auth_code_help' => 'Отсканируйте QR-код с помощью приложения на телефоне, например Authy или Google Authenticator, и введите сгенерированный код.', - 'pref_two_factor_auth_reset_code' => 'Сбросить код верификации', - 'pref_two_factor_auth_disable_2fa' => 'Выключить 2FA', - '2fa_use_secret_instead' => 'If you cannot scan the QR code, feel free to use the secret instead: :secret.', - 'pref_save_settings' => 'Сохранить настройки', - 'saved_preferences' => 'Настройки сохранены!', - 'preferences_general' => 'Основные', - 'preferences_frontpage' => 'Сводка', - 'preferences_security' => 'Безопасность', - 'preferences_layout' => 'Отображение', - 'pref_home_show_deposits' => 'Показывать доходы на главной странице', - 'pref_home_show_deposits_info' => 'В сводке уже отображаются ваши счета расходов. Нужно ли показывать там также ваши счета доходов?', - 'pref_home_do_show_deposits' => 'Да, показать их', - 'successful_count' => 'из которых :count успешно', - 'list_page_size_title' => 'Размер страницы', - 'list_page_size_help' => 'Все списки в программе (счета, транзакции и т. п.) будут отображаться с указанным количеством на одну страницу.', - 'list_page_size_label' => 'Размер страницы', - 'between_dates' => '(:start и :end)', - 'pref_optional_fields_transaction' => 'Дополнительные поля для транзакций', + 'pref_two_factor_auth_help' => 'Когда вы включаете двухэтапную аутентификацию (также известную как двухфакторная аутентификация), вы добавляете дополнительный уровень безопасности в свою учётную запись. Вы входите в систему со своим паролем и ещё чем-то, что есть только у вас (код подтверждения). Коды подтверждения генерируются приложением на вашем телефоне, например, Authy или Google Authenticator.', + 'pref_enable_two_factor_auth' => 'Включить двухэтапную аутентификацию', + 'pref_two_factor_auth_disabled' => 'Двухэтапную код подтверждения удалён и отключен', + 'pref_two_factor_auth_remove_it' => 'Не забудьте удалить учётную запись из своего приложения для аутентификации!', + 'pref_two_factor_auth_code' => 'Проверить код', + 'pref_two_factor_auth_code_help' => 'Отсканируйте QR-код с помощью приложения на телефоне, например Authy или Google Authenticator, и введите сгенерированный код.', + 'pref_two_factor_auth_reset_code' => 'Сбросить код верификации', + 'pref_two_factor_auth_disable_2fa' => 'Выключить 2FA', + '2fa_use_secret_instead' => 'If you cannot scan the QR code, feel free to use the secret instead: :secret.', + '2fa_backup_codes' => 'Store these backup codes for access in case you lose your device.', + '2fa_already_enabled' => '2-step verification is already enabled.', + 'wrong_mfa_code' => 'This MFA code is not valid.', + 'pref_save_settings' => 'Сохранить настройки', + 'saved_preferences' => 'Настройки сохранены!', + 'preferences_general' => 'Основные', + 'preferences_frontpage' => 'Сводка', + 'preferences_security' => 'Безопасность', + 'preferences_layout' => 'Отображение', + 'pref_home_show_deposits' => 'Показывать доходы на главной странице', + 'pref_home_show_deposits_info' => 'В сводке уже отображаются ваши счета расходов. Нужно ли показывать там также ваши счета доходов?', + 'pref_home_do_show_deposits' => 'Да, показать их', + 'successful_count' => 'из которых :count успешно', + 'list_page_size_title' => 'Размер страницы', + 'list_page_size_help' => 'Все списки в программе (счета, транзакции и т. п.) будут отображаться с указанным количеством на одну страницу.', + 'list_page_size_label' => 'Размер страницы', + 'between_dates' => '(:start и :end)', + 'pref_optional_fields_transaction' => 'Дополнительные поля для транзакций', 'pref_optional_fields_transaction_help' => 'По умолчанию при создании новой транзакции включены не все поля (чтобы не создавать беспорядок). Но вы можете включить эти поля, если лично вам они могут быть полезны. Любое поле, которое в последствии будет отключено, будет по-прежнему отображаться, если оно уже заполнено (независимо от данный настроек).', 'optional_tj_date_fields' => 'Поля с датами', 'optional_tj_business_fields' => 'Бизнес-поля', @@ -579,24 +562,25 @@ return [ 'login_with_new_email' => 'Теперь вы можете войти с новым адресом электронной почты.', 'login_with_old_email' => 'Теперь вы можете снова войти со своим старым адресом электронной почты.', 'login_provider_local_only' => 'Это действие недоступно при аутентификации через ":login_provider".', - 'delete_local_info_only' => 'Because you authenticate through ":login_provider", this will only delete local Firefly III information.', + 'delete_local_info_only' => 'Because you authenticate through ":login_provider", this will only delete local Firefly III information.', // attachments - 'nr_of_attachments' => 'Одно вложение |:count вложений', - 'attachments' => 'Вложения', - 'edit_attachment' => 'Изменить вложение ":name"', - 'update_attachment' => 'Обновить вложение', - 'delete_attachment' => 'Удалить вложение ":name"', - 'attachment_deleted' => 'Вложение ":name" удалено', - 'attachment_updated' => 'Вложение ":name" обновлено', - 'upload_max_file_size' => 'Максимальный размер файла: :size', - 'list_all_attachments' => 'Список всех вложений', + 'nr_of_attachments' => 'Одно вложение |:count вложений', + 'attachments' => 'Вложения', + 'edit_attachment' => 'Изменить вложение ":name"', + 'update_attachment' => 'Обновить вложение', + 'delete_attachment' => 'Удалить вложение ":name"', + 'attachment_deleted' => 'Вложение ":name" удалено', + 'liabilities_deleted' => 'Deleted liability ":name"', + 'attachment_updated' => 'Вложение ":name" обновлено', + 'upload_max_file_size' => 'Максимальный размер файла: :size', + 'list_all_attachments' => 'Список всех вложений', // transaction index - 'title_expenses' => 'Расходы', - 'title_withdrawal' => 'Расходы', - 'title_revenue' => 'Доход', - 'title_deposit' => 'Доход', + 'title_expenses' => 'Расходы', + 'title_withdrawal' => 'Расходы', + 'title_revenue' => 'Доход', + 'title_deposit' => 'Доход', 'title_transfer' => 'Переводы', 'title_transfers' => 'Переводы', @@ -634,6 +618,9 @@ return [ 'converted_to_Transfer' => 'Транзакция была преобразована в перевод', 'invalid_convert_selection' => 'Выбранный вами счёт уже используется в этой транзакции или не существует.', 'source_or_dest_invalid' => 'Не удается найти правильные сведения о транзакции. Преобразование невозможно.', + 'convert_to_withdrawal' => 'Convert to a withdrawal', + 'convert_to_deposit' => 'Convert to a deposit', + 'convert_to_transfer' => 'Convert to a transfer', // create new stuff: 'create_new_withdrawal' => 'Создать новый расход', @@ -695,7 +682,9 @@ return [ 'available_amount_indication' => 'Используйте эти суммы, чтобы узнать, каким может быть ваш суммарный бюджет.', 'suggested' => 'Рекомендуемые', 'average_between' => 'В среднем между :start и :end', - 'over_budget_warn' => ' Обычно ваш бюджет - около :amount в день. Это :over_amount в день.', + 'over_budget_warn' => ' Usually you budget about :amount per day. This time it\'s :over_amount per day. Are you sure?', + 'transferred_in' => 'Transferred (in)', + 'transferred_away' => 'Transferred (away)', // bills: 'match_between_amounts' => 'Сравнение транзакций по счетам к оплате между :low и :high.', @@ -779,6 +768,7 @@ return [ 'reconcile_options' => 'Параметры сверки', 'reconcile_range' => 'Диапазон сверки', 'start_reconcile' => 'Начать сверку', + 'cash_account_type' => 'Cash', 'cash' => 'наличные', 'account_type' => 'Тип счёта', 'save_transactions_by_moving' => 'Сохраните эти транзакции, переместив их на другой счёт:', @@ -803,7 +793,9 @@ return [ 'reconcile_go_back' => 'Вы сможете изменить или удалить корректировку позже.', 'must_be_asset_account' => 'Вы можете производить сверку только для основных счётов', 'reconciliation_stored' => 'Сверка сохранена', - 'reconcilliation_transaction_title' => 'Сверка (с :from по :to)', + 'reconciliation_error' => 'Due to an error the transactions were marked as reconciled but the correction has not been stored: :error.', + 'reconciliation_transaction_title' => 'Reconciliation (:from to :to)', + 'sum_of_reconciliation' => 'Sum of reconciliation', 'reconcile_this_account' => 'Произвести сверку данного счёта', 'confirm_reconciliation' => 'Подтвердить сверку', 'submitted_start_balance' => 'Подтверждённый начальный баланс', @@ -815,7 +807,7 @@ return [ 'interest_calc_daily' => 'В день', 'interest_calc_monthly' => 'В месяц', 'interest_calc_yearly' => 'В год', - 'initial_balance_account' => 'Начальный баланс для счёта :name', + 'initial_balance_account' => 'Initial balance account of :account', // categories: 'new_category' => 'Новая категория', @@ -847,6 +839,8 @@ return [ 'deleted_deposit' => 'Доход ":description" успешно удалён', 'deleted_transfer' => 'Перевод ":description" успешно удалён', 'stored_journal' => 'Новая транзакция ":description" успешно создана', + 'stored_journal_no_descr' => 'Successfully created your new transaction', + 'updated_journal_no_descr' => 'Successfully updated your transaction', 'select_transactions' => 'Выбрать транзакции', 'rule_group_select_transactions' => 'Применить ":title" к транзакциям', 'rule_select_transactions' => 'Применить ":title" к транзакциям', @@ -855,26 +849,33 @@ return [ 'mass_delete_journals' => 'Удалить несколько транзакций', 'mass_edit_journals' => 'Изменить несколько транзакций', 'mass_bulk_journals' => 'Массовое изменение нескольких транзакций', - 'mass_bulk_journals_explain' => 'Если вы не хотите изменять транзакции по одной, вы можете использовать функцию массового редактирования, чтобы обновить из все разом. Просто выберите нужную категорию, метки или бюджет в полях ниже, и все транзакции в таблице будут обновлены.', + 'mass_bulk_journals_explain' => 'This form allows you to change properties of the transactions listed below in one sweeping update. All the transactions in the table will be updated when you change the parameters you see here.', + 'part_of_split' => 'This transaction is part of a split transaction. If you have not selected all the splits, you may end up with changing only half the transaction.', 'bulk_set_new_values' => 'Используйте эти поля для ввода новых значений. Если вы оставите их пустыми, они будут очищены у всех записей. Также обратите внимание, что бюджет будет сопоставлен только с расходами.', 'no_bulk_category' => 'Не обновлять категорию', 'no_bulk_budget' => 'Не обновлять бюджет', - 'no_bulk_tags' => 'Не обновлять метки', - 'bulk_edit' => 'Массовое изменение', - 'cannot_edit_other_fields' => 'Вы не можете массово редактировать другие поля, кроме тех, которые видите здесь, поскольку для их отображения недостаточно места. Пожалуйста, перейдите по ссылке и отредактируйте их по одной, если вам нужно изменить такие поля.', - 'no_budget' => '(вне бюджета)', - 'no_budget_squared' => '(вне бюджета)', - 'perm-delete-many' => 'Удаление большого числа записей за один раз может вызывать проблемы. Пожалуйста, будьте осторожны.', - 'mass_deleted_transactions_success' => 'Удалено :amount транзакций.', - 'mass_edited_transactions_success' => 'Обновлено :amount транзакций', - 'opt_group_' => '(нет типа счёта)', + 'no_bulk_tags' => 'Не обновлять метки', + 'mass_edit' => 'Edit selected individually', + 'bulk_edit' => 'Edit selected in bulk', + 'mass_delete' => 'Delete selected', + 'cannot_edit_other_fields' => 'Вы не можете массово редактировать другие поля, кроме тех, которые видите здесь, поскольку для их отображения недостаточно места. Пожалуйста, перейдите по ссылке и отредактируйте их по одной, если вам нужно изменить такие поля.', + 'cannot_change_amount_reconciled' => 'You can\'t change the amount of reconciled transactions.', + 'no_budget' => '(вне бюджета)', + 'no_budget_squared' => '(вне бюджета)', + 'perm-delete-many' => 'Deleting many items in one go can be very disruptive. Please be cautious. You can delete part of a split transaction from this page, so take care.', + 'mass_deleted_transactions_success' => 'Удалено :amount транзакций.', + 'mass_edited_transactions_success' => 'Обновлено :amount транзакций', + 'opt_group_' => '(нет типа счёта)', 'opt_group_no_account_type' => '(нет типа счёта)', 'opt_group_defaultAsset' => 'Основные счета по умолчанию', 'opt_group_savingAsset' => 'Сберегательные счета', 'opt_group_sharedAsset' => 'Общие основные счета', 'opt_group_ccAsset' => 'Кредитные карты', 'opt_group_cashWalletAsset' => 'Кошельки с наличными', + 'opt_group_expense_account' => 'Expense accounts', + 'opt_group_revenue_account' => 'Revenue accounts', 'opt_group_l_Loan' => 'Долги (кредит)', + 'opt_group_cash_account' => 'Cash account', 'opt_group_l_Debt' => 'Долги (дебит)', 'opt_group_l_Mortgage' => 'Долги (ипотека)', 'opt_group_l_Credit card' => 'Долги (кредитная карта)', @@ -973,7 +974,7 @@ return [ 'errors' => 'Ошибки', 'debt_start_date' => 'Start date of debt', 'debt_start_amount' => 'Start amount of debt', - 'debt_start_amount_help' => 'Please enter the original amount of this liability as a positive number. You may also enter the current amount. Make sure to edit the date below to match.', + 'debt_start_amount_help' => 'If you owe an amount its best to enter a negative amount, because it influences your net worth. If you\'re owed an amount the same applies. Check out the help pages for more information.', 'store_new_liabilities_account' => 'Store new liability', 'edit_liabilities_account' => 'Редактировать долговой счёт ":name"', @@ -1145,6 +1146,7 @@ return [ 'deleted_piggy_bank' => 'Копилка ":name" удалена', 'added_amount_to_piggy' => 'Добавлено :amount в ":name"', 'removed_amount_from_piggy' => 'Удалено :amount из ":name"', + 'piggy_events' => 'Related piggy banks', // tags 'delete_tag' => 'Удалить метку ":tag"', @@ -1154,47 +1156,57 @@ return [ 'updated_tag' => 'Метка ":tag" обновлена', 'created_tag' => 'Метка ":tag" была создана!', - 'transaction_journal_information' => 'Информация о транзакции', - 'transaction_journal_meta' => 'Дополнительная информация', - 'total_amount' => 'Итого', - 'number_of_decimals' => 'Количество знаков после запятой', + 'transaction_journal_information' => 'Информация о транзакции', + 'transaction_journal_meta' => 'Дополнительная информация', + 'transaction_journal_more' => 'More information', + 'att_part_of_journal' => 'Stored under ":journal"', + 'total_amount' => 'Итого', + 'number_of_decimals' => 'Количество знаков после запятой', // administration - 'administration' => 'Администрирование', - 'user_administration' => 'Управление пользователями', - 'list_all_users' => 'Список пользователей', - 'all_users' => 'Все пользователи', - 'instance_configuration' => 'Конфигурация', - 'firefly_instance_configuration' => 'Базовая конфигурация Firefly III', - 'setting_single_user_mode' => 'Режим одного пользователя', - 'setting_single_user_mode_explain' => 'По умолчанию Firefly III работает только с одним пользователем (это вы). Это сделано с целью обеспечения безопасности, чтобы другие люди не могли использовать ваш Firefly без вашего разрешения. Регистрация других пользователей просто невозможна. Однако, если вы снимите этот флажок, другие смогут использовать ваш Firefly, при условии, что у них есть доступ к нему (например, он доступен через Интернет).', - 'store_configuration' => 'Сохранить конфигурацию', - 'single_user_administration' => 'Управление пользователем :email', - 'edit_user' => 'Редактирование пользователя :email', - 'hidden_fields_preferences' => 'Сейчас видные не все поля. Вы должны включить их в настройках.', - 'user_data_information' => 'Данные пользователя', - 'user_information' => 'Информация о пользователе', - 'total_size' => 'общий размер', - 'budget_or_budgets' => 'бюджет(ы)', - 'budgets_with_limits' => 'бюджет(ы) с установленной суммой', - 'nr_of_rules_in_total_groups' => ':count_rules правил в :count_groups группах', - 'tag_or_tags' => 'метки', - 'configuration_updated' => 'Конфигурация обновлена', - 'setting_is_demo_site' => 'Демо-сайт', - 'setting_is_demo_site_explain' => 'Если вы установите этот флажок, эта копия FireFly будет вести себя как демонстрационный сайт, который может иметь странные побочные эффекты.', - 'block_code_bounced' => 'Отправка письма (писем) с аккаунта', - 'block_code_expired' => 'Срок демо-версии истёк', - 'no_block_code' => 'Нет причины для блокировки или пользователь не заблокирован', - 'block_code_email_changed' => 'Пользователь не подтвердил новый адрес электронной почты', - 'admin_update_email' => 'Внимание! Пользователь НЕ будет уведомлён о смене адреса электронной почты. Новый адрес он увидит только на странице профиля.', - 'update_user' => 'Обновить пользователя', - 'updated_user' => 'Данные пользователя были изменены.', - 'delete_user' => 'Удалить пользователя :email', - 'user_deleted' => 'Пользователь был удален', - 'send_test_email' => 'Отправить тестовое письмо на E-mail пользователя', - 'send_test_email_text' => 'Чтобы узнать, может ли ваша копия FireFly отправлять электронную почту, нажмите эту кнопку. Вы не увидите здесь ошибки (если они возникнут), все ошибки будут зафиксированы в лог-файле. Вы можете нажимать эту кнопку столько раз, сколько хотите. Спам не контролируется. Сообщение будет отправлено на адрес :email и вы сможете получить его в ближайшее время.', - 'send_message' => 'Отправить сообщение', - 'send_test_triggered' => 'Тест был выполнен. Проверьте ваш почтовый ящик и log-файлы.', + 'administration' => 'Администрирование', + 'user_administration' => 'Управление пользователями', + 'list_all_users' => 'Список пользователей', + 'all_users' => 'Все пользователи', + 'instance_configuration' => 'Конфигурация', + 'firefly_instance_configuration' => 'Базовая конфигурация Firefly III', + 'setting_single_user_mode' => 'Режим одного пользователя', + 'setting_single_user_mode_explain' => 'По умолчанию Firefly III работает только с одним пользователем (это вы). Это сделано с целью обеспечения безопасности, чтобы другие люди не могли использовать ваш Firefly без вашего разрешения. Регистрация других пользователей просто невозможна. Однако, если вы снимите этот флажок, другие смогут использовать ваш Firefly, при условии, что у них есть доступ к нему (например, он доступен через Интернет).', + 'store_configuration' => 'Сохранить конфигурацию', + 'single_user_administration' => 'Управление пользователем :email', + 'edit_user' => 'Редактирование пользователя :email', + 'hidden_fields_preferences' => 'You can enable more transaction options in your settings.', + 'user_data_information' => 'Данные пользователя', + 'user_information' => 'Информация о пользователе', + 'total_size' => 'общий размер', + 'budget_or_budgets' => 'бюджет(ы)', + 'budgets_with_limits' => 'бюджет(ы) с установленной суммой', + 'nr_of_rules_in_total_groups' => ':count_rules правил в :count_groups группах', + 'tag_or_tags' => 'метки', + 'configuration_updated' => 'Конфигурация обновлена', + 'setting_is_demo_site' => 'Демо-сайт', + 'setting_is_demo_site_explain' => 'Если вы установите этот флажок, эта копия FireFly будет вести себя как демонстрационный сайт, который может иметь странные побочные эффекты.', + 'block_code_bounced' => 'Отправка письма (писем) с аккаунта', + 'block_code_expired' => 'Срок демо-версии истёк', + 'no_block_code' => 'Нет причины для блокировки или пользователь не заблокирован', + 'block_code_email_changed' => 'Пользователь не подтвердил новый адрес электронной почты', + 'admin_update_email' => 'Внимание! Пользователь НЕ будет уведомлён о смене адреса электронной почты. Новый адрес он увидит только на странице профиля.', + 'update_user' => 'Обновить пользователя', + 'updated_user' => 'Данные пользователя были изменены.', + 'delete_user' => 'Удалить пользователя :email', + 'user_deleted' => 'Пользователь был удален', + 'send_test_email' => 'Отправить тестовое письмо на E-mail пользователя', + 'send_test_email_text' => 'Чтобы узнать, может ли ваша копия FireFly отправлять электронную почту, нажмите эту кнопку. Вы не увидите здесь ошибки (если они возникнут), все ошибки будут зафиксированы в лог-файле. Вы можете нажимать эту кнопку столько раз, сколько хотите. Спам не контролируется. Сообщение будет отправлено на адрес :email и вы сможете получить его в ближайшее время.', + 'send_message' => 'Отправить сообщение', + 'send_test_triggered' => 'Тест был выполнен. Проверьте ваш почтовый ящик и log-файлы.', + + 'split_transaction_title' => 'Description of the split transaction', + 'split_title_help' => 'If you create a split transaction, there must be a global description for all splits of the transaction.', + 'transaction_information' => 'Transaction information', + 'you_create_transfer' => 'Вы создаёте перевод.', + 'you_create_withdrawal' => 'Вы создаете расход.', + 'you_create_deposit' => 'Вы создаёте вклад.', + // links 'journal_link_configuration' => 'Настройка связей между транзакциями', @@ -1214,12 +1226,13 @@ return [ 'do_not_save_connection' => '(не сохранять соединение)', 'link_transaction' => 'Связать транзакцию', 'link_to_other_transaction' => 'Связать эту транзакцию с другой транзакцией', - 'select_transaction_to_link' => 'Выберите транзакцию, которую требуется связать с...', + 'select_transaction_to_link' => 'Select a transaction to link this transaction to. The links are currently unused in Firefly III (apart from being shown), but I plan to change this in the future. Use the search box to select a transaction either by title or by ID. If you want to add custom link types, check out the administration section.', 'this_transaction' => 'Эта транзакция', 'transaction' => 'Транзакция', 'comments' => 'Комментарии', - 'to_link_not_found' => 'Если транзакция, с которой вы хотите установить связь, не показана в списке, просто введите её ID.', + 'link_notes' => 'Any notes you wish to store with the link.', 'invalid_link_selection' => 'Невозможно связать эти транзакции', + 'selected_transaction' => 'Selected transaction', 'journals_linked' => 'Транзакции были связаны.', 'journals_error_linked' => 'Эти транзакции уже связаны.', 'journals_link_to_self' => 'Вы не можете связать транзакцию с самой собой', @@ -1238,7 +1251,7 @@ return [ 'Paid_name' => 'Оплачено', 'Refund_name' => 'Возврат', 'Reimbursement_name' => 'Возмещение', - 'Related_name' => 'Related', + 'Related_name' => 'Связанные', 'relates to_inward' => 'связано с', 'is (partially) refunded by_inward' => '(частично) возвращён', 'is (partially) paid for by_inward' => '(частично) оплачен', @@ -1258,12 +1271,11 @@ return [ 'split_this_withdrawal' => 'Разделить этот расход', 'split_this_deposit' => 'Разделить этот доход', 'split_this_transfer' => 'Разделить этот перевод', - 'cannot_edit_multiple_source' => 'Вы не можете редактировать разделённую транзакцию #:id с описанием ":description", поскольку она содержит несколько счетов-источников.', - 'cannot_edit_multiple_dest' => 'Вы не можете редактировать разделённую транзакцию #:id с описанием ":description", поскольку она содержит несколько счетов назначения.', - 'cannot_edit_reconciled' => 'Вы не можете редактировать разделённую транзакцию #:id с описанием ":description", поскольку она отмечена как прошедшая сверку.', 'cannot_edit_opening_balance' => 'Вы не можете изменить начальный баланс этого счёта.', 'no_edit_multiple_left' => 'Вы выбрали для редактирования некорректную транзакцию.', - 'cannot_convert_split_journal' => 'Невозможно преобразовать раздельную транзакцию', + 'breadcrumb_convert_group' => 'Convert transaction', + 'convert_invalid_source' => 'Source information is invalid for transaction #%d.', + 'convert_invalid_destination' => 'Destination information is invalid for transaction #%d.', // Import page (general strings only) 'import_index_title' => 'Импорт транзакций в Firefly III', @@ -1389,4 +1401,15 @@ return [ 'will_jump_monday' => 'Будет создана в понедельник, а не в выходной день.', 'except_weekends' => 'Исключить выходные дни', 'recurrence_deleted' => 'Повторяющаяся транзакция ":title" удалена', + + // new lines for summary controller. + 'box_balance_in_currency' => 'Balance (:currency)', + 'box_spent_in_currency' => 'Spent (:currency)', + 'box_earned_in_currency' => 'Earned (:currency)', + 'box_bill_paid_in_currency' => 'Bills paid (:currency)', + 'box_bill_unpaid_in_currency' => 'Bills unpaid (:currency)', + 'box_left_to_spend_in_currency' => 'Left to spend (:currency)', + 'box_net_worth_in_currency' => 'Net worth (:currency)', + 'box_spend_per_day' => 'Left to spend per day: :amount', + ]; diff --git a/resources/lang/ru_RU/form.php b/resources/lang/ru_RU/form.php index 78a4dd4f3c..d390411f1c 100644 --- a/resources/lang/ru_RU/form.php +++ b/resources/lang/ru_RU/form.php @@ -57,20 +57,21 @@ return [ 'asset_source_account' => 'Исходный счёт', 'journal_description' => 'Описание', 'note' => 'Заметки', + 'store_new_transaction' => 'Store new transaction', 'split_journal' => 'Разделить эту транзакцию', 'split_journal_explanation' => 'Разделить эту транзакцию на несколько частей', 'currency' => 'Валюта', 'account_id' => 'Основной счёт', 'budget_id' => 'Бюджет', - 'openingBalance' => 'Начальный баланс', + 'opening_balance' => 'Opening balance', 'tagMode' => 'Режим метки', 'tag_position' => 'Расположение метки', - 'virtualBalance' => 'Виртуальный баланс', + 'virtual_balance' => 'Virtual balance', 'targetamount' => 'Целевая сумма', - 'accountRole' => 'Роль учётной записи', - 'openingBalanceDate' => 'Дата начального баланса', - 'ccType' => 'План оплаты по кредитной карте', - 'ccMonthlyPaymentDate' => 'Дата ежемесячного платежа по кредитной карте', + 'account_role' => 'Account role', + 'opening_balance_date' => 'Opening balance date', + 'cc_type' => 'Credit card payment plan', + 'cc_monthly_payment_date' => 'Credit card monthly payment date', 'piggy_bank_id' => 'Копилка', 'returnHere' => 'Вернуться сюда', 'returnHereExplanation' => 'После сохранения вернуться сюда и создать ещё одну аналогичную запись.', @@ -118,7 +119,7 @@ return [ 'symbol' => 'Символ', 'code' => 'Код', 'iban' => 'IBAN', - 'accountNumber' => 'Номер счета', + 'account_number' => 'Account number', 'creditCardNumber' => 'Номер кредитной карты', 'has_headers' => 'Заголовки', 'date_format' => 'Формат даты', @@ -139,12 +140,8 @@ return [ 'stop_processing' => 'Остановить обработку', 'start_date' => 'Начало диапазона', 'end_date' => 'Конец диапазона', - 'export_start_range' => 'Начало диапазона для экспорта', - 'export_end_range' => 'Конец диапазона для экспорта', - 'export_format' => 'Формат файла', 'include_attachments' => 'Включить загруженные вложения', 'include_old_uploads' => 'Включить импортированные данные', - 'accounts' => 'Экспорт транзакций с этих счетов', 'delete_account' => 'Удалить счёт ":name"', 'delete_bill' => 'Удаление счёта к оплате ":name"', 'delete_budget' => 'Удалить бюджет ":name"', @@ -256,4 +253,7 @@ return [ 'weekend' => 'Выходные', 'client_secret' => 'Закрытый ключ клиента', + 'withdrawal_destination_id' => 'Destination account', + 'deposit_source_id' => 'Source account', + ]; diff --git a/resources/lang/ru_RU/import.php b/resources/lang/ru_RU/import.php index c64a18874a..60d5d9ded5 100644 --- a/resources/lang/ru_RU/import.php +++ b/resources/lang/ru_RU/import.php @@ -207,6 +207,10 @@ return [ 'specific_rabo_descr' => 'Fixes potential problems with Rabobank files', 'specific_pres_name' => 'President\'s Choice Financial CA', 'specific_pres_descr' => 'Fixes potential problems with PC files', + 'specific_belfius_name' => 'Belfius BE', + 'specific_belfius_descr' => 'Fixes potential problems with Belfius files', + 'specific_ingbelgium_name' => 'ING BE', + 'specific_ingbelgium_descr' => 'Fixes potential problems with ING Belgium files', // job configuration for file provider (stage: roles) 'job_config_roles_title' => 'Настройка импорта (3/4). Определите роль каждого столбца', 'job_config_roles_text' => 'Each column in your CSV file contains certain data. Please indicate what kind of data the importer should expect. The option to "map" data means that you will link each entry found in the column to a value in your database. An often mapped column is the column that contains the IBAN of the opposing account. That can be easily matched to IBAN\'s present in your database already.', @@ -291,14 +295,14 @@ return [ 'column_rabo-debit-credit' => 'Индикатор дебита/кредита, специфичный для Rabobank', 'column_ing-debit-credit' => 'Индикатор дебита/кредита, специфичный для ING', 'column_generic-debit-credit' => 'Generic bank debit/credit indicator', - 'column_sepa-ct-id' => 'Идентификатор SEPA end-to-end', - 'column_sepa-ct-op' => 'Идентификатор учетной записи SEPA', - 'column_sepa-db' => 'Идентификатор SEPA Mandate', - 'column_sepa-cc' => 'Код очистки SEPA', - 'column_sepa-ci' => 'Идентификатор кредитора SEPA', - 'column_sepa-ep' => 'Внешняя цель SEPA', - 'column_sepa-country' => 'Код страны SEPA', - 'column_sepa-batch-id' => 'SEPA Batch ID', + 'column_sepa_ct_id' => 'SEPA end-to-end Identifier', + 'column_sepa_ct_op' => 'SEPA Opposing Account Identifier', + 'column_sepa_db' => 'SEPA Mandate Identifier', + 'column_sepa_cc' => 'SEPA Clearing Code', + 'column_sepa_ci' => 'SEPA Creditor Identifier', + 'column_sepa_ep' => 'SEPA External Purpose', + 'column_sepa_country' => 'SEPA Country Code', + 'column_sepa_batch_id' => 'SEPA Batch ID', 'column_tags-comma' => 'Метки (разделены запятыми)', 'column_tags-space' => 'Метки (разделены пробелами)', 'column_account-number' => 'Основной счёт (номер счёта)', @@ -306,4 +310,7 @@ return [ 'column_note' => 'Примечания', 'column_internal-reference' => 'Внутренняя ссылка', + // error message + 'duplicate_row' => 'Row #:row (":description") could not be imported. It already exists.', + ]; diff --git a/resources/lang/ru_RU/intro.php b/resources/lang/ru_RU/intro.php index 90a04ba34f..be96329395 100644 --- a/resources/lang/ru_RU/intro.php +++ b/resources/lang/ru_RU/intro.php @@ -24,39 +24,61 @@ declare(strict_types=1); return [ // index - 'index_intro' => 'Добро пожаловать на стартовую страницу Firefly III. Пожалуйста, найдите время, чтобы ознакомиться с этим кратким введением в возможности Firefly III.', - 'index_accounts-chart' => 'Эта диаграмма показывает текущий баланс ваших счетов. Вы можете выбрать счета, видимые здесь, в настройках.', - 'index_box_out_holder' => 'Небольшие блоки, собранные на этой странице, показывают общее положение дел с вашими финансами.', - 'index_help' => 'Если вам нужна помощь со страницей или формой - нажмите эту кнопку.', - 'index_outro' => 'Большинство страниц Firefly III начнутся с небольшого тура, подобного этому. Пожалуйста, свяжитесь со мной, если у вас возникнут вопросы или комментарии. Наслаждайтесь!', - 'index_sidebar-toggle' => 'Для создания новый транзакций, счётов или другие элементов, используйте меню под этим значком.', + 'index_intro' => 'Добро пожаловать на стартовую страницу Firefly III. Пожалуйста, найдите время, чтобы ознакомиться с этим кратким введением в возможности Firefly III.', + 'index_accounts-chart' => 'Эта диаграмма показывает текущий баланс ваших счетов. Вы можете выбрать счета, видимые здесь, в настройках.', + 'index_box_out_holder' => 'Небольшие блоки, собранные на этой странице, показывают общее положение дел с вашими финансами.', + 'index_help' => 'Если вам нужна помощь со страницей или формой - нажмите эту кнопку.', + 'index_outro' => 'Большинство страниц Firefly III начнутся с небольшого тура, подобного этому. Пожалуйста, свяжитесь со мной, если у вас возникнут вопросы или комментарии. Наслаждайтесь!', + 'index_sidebar-toggle' => 'Для создания новый транзакций, счётов или другие элементов, используйте меню под этим значком.', + 'index_cash_account' => 'These are the accounts created so far. You can use the cash account to track cash expenses but it\'s not mandatory of course.', + + // transactions (withdrawal) + 'transactions_create_withdrawal_source' => 'Select your favorite asset account or liability from this dropdown.', + 'transactions_create_withdrawal_destination' => 'Select an expense account here. Leave it empty if you want to make a cash expense.', + 'transactions_create_withdrawal_foreign_currency' => 'Use this field to set a foreign currency and amount.', + 'transactions_create_withdrawal_more_meta' => 'Plenty of other meta data you set in these fields.', + 'transactions_create_withdrawal_split_add' => 'If you want to split a transaction, add more splits with this button', + + // transactions (deposit) + 'transactions_create_deposit_source' => 'Select or type the payee in this auto-completing dropdown/textbox. Leave it empty if you want to make a cash deposit.', + 'transactions_create_deposit_destination' => 'Select an asset or liability account here.', + 'transactions_create_deposit_foreign_currency' => 'Use this field to set a foreign currency and amount.', + 'transactions_create_deposit_more_meta' => 'Plenty of other meta data you set in these fields.', + 'transactions_create_deposit_split_add' => 'If you want to split a transaction, add more splits with this button', + + // transactions (transfer) + 'transactions_create_transfer_source' => 'Select the source asset account here.', + 'transactions_create_transfer_destination' => 'Select the destination asset account here.', + 'transactions_create_transfer_foreign_currency' => 'Use this field to set a foreign currency and amount.', + 'transactions_create_transfer_more_meta' => 'Plenty of other meta data you set in these fields.', + 'transactions_create_transfer_split_add' => 'If you want to split a transaction, add more splits with this button', // create account: - 'accounts_create_iban' => 'Задайте вашим счетам действующий IBAN. В будущем это может сделать импорт данных очень простым.', - 'accounts_create_asset_opening_balance' => 'У счёта активов всегда есть «начальный баланс», показывающий, какая сумма была на этом счету, когда вы начали работать с ним в Firefly III.', - 'accounts_create_asset_currency' => 'Firefly III поддерживает несколько валют. Каждый счёт связан с одной основной валютой, которую вы должны указать здесь.', - 'accounts_create_asset_virtual' => 'Иногда бывает удобно предоставить вашему счёту виртуальный баланс: дополнительная сумма, всегда добавленная или выведенная из фактического баланса.', + 'accounts_create_iban' => 'Задайте вашим счетам действующий IBAN. В будущем это может сделать импорт данных очень простым.', + 'accounts_create_asset_opening_balance' => 'У счёта активов всегда есть «начальный баланс», показывающий, какая сумма была на этом счету, когда вы начали работать с ним в Firefly III.', + 'accounts_create_asset_currency' => 'Firefly III поддерживает несколько валют. Каждый счёт связан с одной основной валютой, которую вы должны указать здесь.', + 'accounts_create_asset_virtual' => 'Иногда бывает удобно предоставить вашему счёту виртуальный баланс: дополнительная сумма, всегда добавленная или выведенная из фактического баланса.', // budgets index - 'budgets_index_intro' => 'Бюджеты используются для управления финансами и являются одной из основных функций Firefly III.', - 'budgets_index_set_budget' => 'Установите свой общий бюджет на каждый период, чтобы Firefly III мог подсказать вам, если вы потратите все запланированные деньги.', - 'budgets_index_see_expenses_bar' => 'По мере того, как вы будете тратить деньги, эта диаграмма будет заполняться.', - 'budgets_index_navigate_periods' => 'Перемещайтесь между периодами, чтобы планировать бюджеты заранее.', - 'budgets_index_new_budget' => 'Создавайте новые бюджеты по своему усмотрению.', - 'budgets_index_list_of_budgets' => 'Используйте эту таблицу, чтобы установить суммы для каждого бюджета и посмотреть, как у вас дела.', - 'budgets_index_outro' => 'Чтобы узнать больше о бюджете, воспользуйтесь значком справки в верхнем правом углу.', + 'budgets_index_intro' => 'Бюджеты используются для управления финансами и являются одной из основных функций Firefly III.', + 'budgets_index_set_budget' => 'Установите свой общий бюджет на каждый период, чтобы Firefly III мог подсказать вам, если вы потратите все запланированные деньги.', + 'budgets_index_see_expenses_bar' => 'По мере того, как вы будете тратить деньги, эта диаграмма будет заполняться.', + 'budgets_index_navigate_periods' => 'Перемещайтесь между периодами, чтобы планировать бюджеты заранее.', + 'budgets_index_new_budget' => 'Создавайте новые бюджеты по своему усмотрению.', + 'budgets_index_list_of_budgets' => 'Используйте эту таблицу, чтобы установить суммы для каждого бюджета и посмотреть, как у вас дела.', + 'budgets_index_outro' => 'Чтобы узнать больше о бюджете, воспользуйтесь значком справки в верхнем правом углу.', // reports (index) - 'reports_index_intro' => 'Используйте эти отчеты, чтобы получить подробные сведения о ваших финансах.', - 'reports_index_inputReportType' => 'Выберите тип отчета. Просмотрите страницу справки, чтобы узнать, что показывает каждый отчёт.', - 'reports_index_inputAccountsSelect' => 'Вы можете исключить или включить основные счета по своему усмотрению.', - 'reports_index_inputDateRange' => 'Выбранный диапазон дат зависит от вас: от одного дня до 10 лет.', - 'reports_index_extra-options-box' => 'В зависимости от выбранного вами отчёта вы можете выбрать здесь дополнительные фильтры и параметры. Посмотрите этот блок, когда вы меняете типы отчётов.', + 'reports_index_intro' => 'Используйте эти отчеты, чтобы получить подробные сведения о ваших финансах.', + 'reports_index_inputReportType' => 'Выберите тип отчета. Просмотрите страницу справки, чтобы узнать, что показывает каждый отчёт.', + 'reports_index_inputAccountsSelect' => 'Вы можете исключить или включить основные счета по своему усмотрению.', + 'reports_index_inputDateRange' => 'Выбранный диапазон дат зависит от вас: от одного дня до 10 лет.', + 'reports_index_extra-options-box' => 'В зависимости от выбранного вами отчёта вы можете выбрать здесь дополнительные фильтры и параметры. Посмотрите этот блок, когда вы меняете типы отчётов.', // reports (reports) - 'reports_report_default_intro' => 'В этом отчёте вы получите быстрый и исчерпывающий обзор ваших финансов. Если вы хотите увидеть что-нибудь ещё, пожалуйста, не стесняйтесь обращаться ко мне!', - 'reports_report_audit_intro' => 'Этот отчёт покажет вам подробную информацию о ваших активах.', - 'reports_report_audit_optionsBox' => 'Используйте эти флажки, чтобы показать или скрыть интересующие вас столбцы.', + 'reports_report_default_intro' => 'В этом отчёте вы получите быстрый и исчерпывающий обзор ваших финансов. Если вы хотите увидеть что-нибудь ещё, пожалуйста, не стесняйтесь обращаться ко мне!', + 'reports_report_audit_intro' => 'Этот отчёт покажет вам подробную информацию о ваших активах.', + 'reports_report_audit_optionsBox' => 'Используйте эти флажки, чтобы показать или скрыть интересующие вас столбцы.', 'reports_report_category_intro' => 'Этот отчёт даст вам представление об одной или нескольких категориях.', 'reports_report_category_pieCharts' => 'Эти диаграммы дадут вам представление о расходах и доходах по категориям или счетам.', diff --git a/resources/lang/ru_RU/list.php b/resources/lang/ru_RU/list.php index 5e29667050..de1269b0fb 100644 --- a/resources/lang/ru_RU/list.php +++ b/resources/lang/ru_RU/list.php @@ -23,71 +23,71 @@ declare(strict_types=1); return [ - 'buttons' => 'Кнопки', - 'icon' => 'Значок', - 'id' => 'ID', - 'create_date' => 'Создан', - 'update_date' => 'Обновлён', - 'updated_at' => 'Последнее изменение', - 'balance_before' => 'Баланс до', - 'balance_after' => 'Баланс после', - 'name' => 'Имя', - 'role' => 'Роль', - 'currentBalance' => 'Текущий баланс', - 'linked_to_rules' => 'Подходящие правила', - 'active' => 'Активен?', - 'lastActivity' => 'Последняя активность', - 'balanceDiff' => 'Разность баланса', - 'matchesOn' => 'Совпадает на', - 'account_type' => 'Тип профиля', - 'created_at' => 'Создан', - 'account' => 'Счёт', - 'matchingAmount' => 'Сумма', - 'split_number' => 'Часть №', - 'destination' => 'Получатель', - 'source' => 'Источник', - 'next_expected_match' => 'Следующий ожидаемый результат', - 'automatch' => 'Автоподбор?', - 'repeat_freq' => 'Повторы', - 'description' => 'Описание', - 'amount' => 'Сумма', - 'internal_reference' => 'Внутренняя ссылка', - 'date' => 'Дата', - 'interest_date' => 'Проценты', - 'book_date' => 'Забронировать', - 'process_date' => 'Дата открытия', - 'due_date' => 'Срок', - 'payment_date' => 'Дата платежа', - 'invoice_date' => 'Дата выставления счёта', - 'interal_reference' => 'Внутренняя ссылка', - 'notes' => 'Заметки', - 'from' => 'Откуда', - 'piggy_bank' => 'Копилка', - 'to' => 'Куда', - 'budget' => 'Бюджет', - 'category' => 'Категория', - 'bill' => 'Счет к оплате', - 'withdrawal' => 'Расход', - 'deposit' => 'Доход', - 'transfer' => 'Перевод', - 'type' => 'Тип', - 'completed' => 'Завершено', - 'iban' => 'IBAN', - 'paid_current_period' => 'Оплатить в указанный период', - 'email' => 'E-mail', - 'registered_at' => 'Дата регистрации', - 'is_blocked' => 'Заблокирован?', - 'is_admin' => 'Администратор?', - 'has_two_factor' => 'Защита (2FA)?', - 'blocked_code' => 'Код блокировки', - 'source_account' => 'Исходный счет', + 'buttons' => 'Кнопки', + 'icon' => 'Значок', + 'id' => 'ID', + 'create_date' => 'Создан', + 'update_date' => 'Обновлён', + 'updated_at' => 'Последнее изменение', + 'balance_before' => 'Баланс до', + 'balance_after' => 'Баланс после', + 'name' => 'Имя', + 'role' => 'Роль', + 'currentBalance' => 'Текущий баланс', + 'linked_to_rules' => 'Подходящие правила', + 'active' => 'Активен?', + 'transaction_type' => 'Type', + 'lastActivity' => 'Последняя активность', + 'balanceDiff' => 'Разность баланса', + 'matchesOn' => 'Совпадает на', + 'account_type' => 'Тип профиля', + 'created_at' => 'Создан', + 'account' => 'Счёт', + 'matchingAmount' => 'Сумма', + 'split_number' => 'Часть №', + 'destination' => 'Получатель', + 'source' => 'Источник', + 'next_expected_match' => 'Следующий ожидаемый результат', + 'automatch' => 'Автоподбор?', + 'repeat_freq' => 'Повторы', + 'description' => 'Описание', + 'amount' => 'Сумма', + 'internal_reference' => 'Внутренняя ссылка', + 'date' => 'Дата', + 'interest_date' => 'Проценты', + 'book_date' => 'Забронировать', + 'process_date' => 'Дата открытия', + 'due_date' => 'Срок', + 'payment_date' => 'Дата платежа', + 'invoice_date' => 'Дата выставления счёта', + 'interal_reference' => 'Внутренняя ссылка', + 'notes' => 'Заметки', + 'from' => 'Откуда', + 'piggy_bank' => 'Копилка', + 'to' => 'Куда', + 'budget' => 'Бюджет', + 'category' => 'Категория', + 'bill' => 'Счет к оплате', + 'withdrawal' => 'Расход', + 'deposit' => 'Доход', + 'transfer' => 'Перевод', + 'type' => 'Тип', + 'completed' => 'Завершено', + 'iban' => 'IBAN', + 'paid_current_period' => 'Оплатить в указанный период', + 'email' => 'E-mail', + 'registered_at' => 'Дата регистрации', + 'is_blocked' => 'Заблокирован?', + 'is_admin' => 'Администратор?', + 'has_two_factor' => 'Защита (2FA)?', + 'blocked_code' => 'Код блокировки', + 'source_account' => 'Исходный счет', 'destination_account' => 'Счет назначения', 'accounts_count' => 'Всего счетов', 'journals_count' => 'Всего транзакций', 'attachments_count' => 'Всего вложений', 'bills_count' => 'Всего счетов к оплате', 'categories_count' => 'Всего категорий', - 'export_jobs_count' => 'Задачи по экспорту', 'import_jobs_count' => 'Задачи по импорту', 'budget_count' => 'Всего категорий бюджета', 'rule_and_groups_count' => 'Всего правил и групп правил', @@ -106,14 +106,14 @@ return [ 'account_on_spectre' => 'Счёт (Spectre)', 'account_on_ynab' => 'Счёт (YNAB)', 'do_import' => 'Импортировать с этого счёта', - 'sepa-ct-id' => 'Идентификатор SEPA end-to-end', - 'sepa-ct-op' => 'Идентификатор учетной записи SEPA', - 'sepa-db' => 'Идентификатор SEPA Mandate', - 'sepa-country' => 'Страна SEPA', - 'sepa-cc' => 'Код очистки SEPA', - 'sepa-ep' => 'Внешняя цель SEPA', - 'sepa-ci' => 'Идентификатор кредитора SEPA', - 'sepa-batch-id' => 'SEPA Batch ID', + 'sepa_ct_id' => 'SEPA End to End Identifier', + 'sepa_ct_op' => 'SEPA Opposing Account Identifier', + 'sepa_db' => 'SEPA Mandate Identifier', + 'sepa_country' => 'SEPA Country', + 'sepa_cc' => 'SEPA Clearing Code', + 'sepa_ep' => 'SEPA External Purpose', + 'sepa_ci' => 'SEPA Creditor Identifier', + 'sepa_batch_id' => 'SEPA Batch ID', 'external_id' => 'Внешний ID', 'account_at_bunq' => 'Счёт с bunq', 'file_name' => 'Имя файла', diff --git a/resources/lang/ru_RU/validation.php b/resources/lang/ru_RU/validation.php index 4390f77690..96b9b5e395 100644 --- a/resources/lang/ru_RU/validation.php +++ b/resources/lang/ru_RU/validation.php @@ -33,15 +33,19 @@ return [ 'rule_trigger_value' => 'Это значение является недопустимым для выбранного триггера.', 'rule_action_value' => 'Это значение является недопустимым для выбранного действия.', 'file_already_attached' => 'Загруженный файл ":name" уже прикреплён к этому объекту.', - 'file_attached' => 'Файл ":name". успешно загружен.', + 'file_attached' => 'Successfully uploaded file ":name".', 'must_exist' => 'ID в поле field :attribute не существует в базе данных.', 'all_accounts_equal' => 'Все счета в данном поле должны совпадать.', + 'group_title_mandatory' => 'A group title is mandatory when there is more than one transaction.', + 'transaction_types_equal' => 'All splits must be of the same type.', + 'invalid_transaction_type' => 'Invalid transaction type.', 'invalid_selection' => 'Вы сделали неправильный выбор.', 'belongs_user' => 'Данное значение недопустимо для этого поля.', 'at_least_one_transaction' => 'Необходима как минимум одна транзакция.', 'at_least_one_repetition' => 'Необходима как минимум одна транзакция.', 'require_repeat_until' => 'Требуется либо несколько повторений, либо конечная дата (repeat_until). Но не оба параметра разом.', 'require_currency_info' => 'Содержимое этого поля недействительно без информации о валюте.', + 'require_currency_amount' => 'The content of this field is invalid without foreign amount information.', 'equal_description' => 'Описание транзакции не должно совпадать с глобальным описанием.', 'file_invalid_mime' => 'Файл ":name" имеет тип ":mime". Загрузка файлов такого типа невозможна.', 'file_too_large' => 'Файл ":name" слишком большой.', @@ -135,8 +139,8 @@ return [ 'name' => '"Название"', 'piggy_bank_id' => 'ID копилки', 'targetamount' => '"Целевая сумма"', - 'openingBalanceDate' => '"Дата начального баланса"', - 'openingBalance' => '"Начальный баланс"', + 'opening_balance_date' => 'opening balance date', + 'opening_balance' => 'opening balance', 'match' => '"Ключи для связи"', 'amount_min' => '"Минимальная сумма"', 'amount_max' => '"Максимальная сумма"', @@ -164,4 +168,28 @@ return [ 'rule-trigger.4' => '"Условие #4"', 'rule-trigger.5' => '"Условие #5"', ], + + // validation of accounts: + 'withdrawal_source_need_data' => 'Need to get a valid source account ID and/or valid source account name to continue.', + 'withdrawal_source_bad_data' => 'Could not find a valid source account when searching for ID ":id" or name ":name".', + 'withdrawal_dest_need_data' => 'Need to get a valid destination account ID and/or valid destination account name to continue.', + 'withdrawal_dest_bad_data' => 'Could not find a valid destination account when searching for ID ":id" or name ":name".', + + 'deposit_source_need_data' => 'Need to get a valid source account ID and/or valid source account name to continue.', + 'deposit_source_bad_data' => 'Could not find a valid source account when searching for ID ":id" or name ":name".', + 'deposit_dest_need_data' => 'Need to get a valid destination account ID and/or valid destination account name to continue.', + 'deposit_dest_bad_data' => 'Could not find a valid destination account when searching for ID ":id" or name ":name".', + + 'transfer_source_need_data' => 'Need to get a valid source account ID and/or valid source account name to continue.', + 'transfer_source_bad_data' => 'Could not find a valid source account when searching for ID ":id" or name ":name".', + 'transfer_dest_need_data' => 'Need to get a valid destination account ID and/or valid destination account name to continue.', + 'transfer_dest_bad_data' => 'Could not find a valid destination account when searching for ID ":id" or name ":name".', + 'need_id_in_edit' => 'Each split must have transaction_journal_id (either valid ID or 0).', + + 'ob_source_need_data' => 'Need to get a valid source account ID and/or valid source account name to continue.', + 'ob_dest_need_data' => 'Need to get a valid destination account ID and/or valid destination account name to continue.', + 'ob_dest_bad_data' => 'Could not find a valid destination account when searching for ID ":id" or name ":name".', + + 'generic_invalid_source' => 'You can\'t use this account as the source account.', + 'generic_invalid_destination' => 'You can\'t use this account as the destination account.', ]; diff --git a/resources/lang/tr_TR/breadcrumbs.php b/resources/lang/tr_TR/breadcrumbs.php index 82d66d4c65..3d244030d8 100644 --- a/resources/lang/tr_TR/breadcrumbs.php +++ b/resources/lang/tr_TR/breadcrumbs.php @@ -23,35 +23,37 @@ declare(strict_types=1); return [ - 'home' => 'Anasayfa', - 'edit_currency' => '":name" para birimini düzenle', - 'delete_currency' => '":name" para birimini sil', - 'newPiggyBank' => 'Yeni bir kumbara oluştur', - 'edit_piggyBank' => '":name" kumbarasını düzenle', - 'preferences' => 'Tercihler', - 'profile' => 'Profil', - 'changePassword' => 'Şifrenizi değiştirin', - 'change_email' => 'E-posta adresini değiştir', - 'bills' => 'Fatura', - 'newBill' => 'Yeni Fatura', - 'edit_bill' => 'Faturayı düzenle ":name"', - 'delete_bill' => 'Faturayı ":name"', - 'reports' => 'Raporlar', - 'search_result' => '":query" için arama sonuçları', - 'withdrawal_list' => 'Giderler', - 'deposit_list' => 'Varlık, gelir ve mevduatlar', - 'transfer_list' => 'Transferler', - 'transfers_list' => 'Transferler', - 'reconciliation_list' => 'Mutabakatlar', - 'create_withdrawal' => 'Yeni para çekme oluştur', - 'create_deposit' => 'Yeni mevduat oluştur', - 'create_transfer' => 'Yeni transfer oluştur', - 'edit_journal' => '":description" işlemini düzenle', - 'edit_reconciliation' => 'Düzenle ":description"', - 'delete_journal' => '":description" işlemini sil', - 'tags' => 'Etiketler', - 'createTag' => 'Yeni etiket oluştur', - 'edit_tag' => '":tag" etiketini düzenle', - 'delete_tag' => '":tag" etiketini sil', - 'delete_journal_link' => 'Hesap hareketleri arasındaki bağlantıyı sil', + 'home' => 'Anasayfa', + 'edit_currency' => '":name" para birimini düzenle', + 'delete_currency' => '":name" para birimini sil', + 'newPiggyBank' => 'Yeni bir kumbara oluştur', + 'edit_piggyBank' => '":name" kumbarasını düzenle', + 'preferences' => 'Tercihler', + 'profile' => 'Profil', + 'changePassword' => 'Şifrenizi değiştirin', + 'change_email' => 'E-posta adresini değiştir', + 'bills' => 'Fatura', + 'newBill' => 'Yeni Fatura', + 'edit_bill' => 'Faturayı düzenle ":name"', + 'delete_bill' => 'Faturayı ":name"', + 'reports' => 'Raporlar', + 'search_result' => '":query" için arama sonuçları', + 'withdrawal_list' => 'Giderler', + 'Withdrawal_list' => 'Expenses', + 'deposit_list' => 'Varlık, gelir ve mevduatlar', + 'transfer_list' => 'Transferler', + 'transfers_list' => 'Transferler', + 'reconciliation_list' => 'Mutabakatlar', + 'create_withdrawal' => 'Yeni para çekme oluştur', + 'create_deposit' => 'Yeni mevduat oluştur', + 'create_transfer' => 'Yeni transfer oluştur', + 'create_new_transaction' => 'Create a new transaction', + 'edit_journal' => '":description" işlemini düzenle', + 'edit_reconciliation' => 'Düzenle ":description"', + 'delete_journal' => '":description" işlemini sil', + 'tags' => 'Etiketler', + 'createTag' => 'Yeni etiket oluştur', + 'edit_tag' => '":tag" etiketini düzenle', + 'delete_tag' => '":tag" etiketini sil', + 'delete_journal_link' => 'Hesap hareketleri arasındaki bağlantıyı sil', ]; diff --git a/resources/lang/tr_TR/demo.php b/resources/lang/tr_TR/demo.php index b1f0a471b9..3e59e7c549 100644 --- a/resources/lang/tr_TR/demo.php +++ b/resources/lang/tr_TR/demo.php @@ -35,5 +35,5 @@ return [ 'piggy-banks-index' => 'Gördüğünüz gibi, üç tane banka var. Her domuzcuk bankasındaki para miktarını değiştirmek için artı ve eksi düğmelerini kullanın. Her domuzcuk bankasının yönetimini görmek için domuzcuk\'un üzerine tıklayın.', 'import-index' => 'Herhangi bir CSV dosyası Firefly III\'e aktarılabilir. Ayrıca bunq ve Spectre\'den veri almayı da destekliyor. Diğer bankalar ve finansal toplayıcılar gelecekte uygulanacaktır. Ancak bir demo kullanıcısı olarak, yalnızca "fake" -provider etkinlikte görebilirsiniz. Sürecin nasıl çalıştığını göstermek için bazı rasgele işlemler üretecektir. ', - 'profile-index' => 'Keep in mind that the demo site resets every four hours. Your access may be revoked at any time. This happens automatically and is not a bug.', + 'profile-index' => 'Demo sitesinin her dört saatte bir sıfırlandığını unutmayın. Erişiminiz herhangi bir zamanda iptal edilebilir. Bu otomatik olarak gerçekleşir ve bir hata değildir.', ]; diff --git a/resources/lang/tr_TR/firefly.php b/resources/lang/tr_TR/firefly.php index abc7666989..d258f939d5 100644 --- a/resources/lang/tr_TR/firefly.php +++ b/resources/lang/tr_TR/firefly.php @@ -28,12 +28,12 @@ return [ 'actions' => 'Eylemler', 'edit' => 'Düzenle', 'delete' => 'Sil', - 'split' => 'Split', - 'clone' => 'Clone', - 'last_seven_days' => 'Last seven days', - 'last_thirty_days' => 'Last thirty days', + 'split' => 'Böl', + 'clone' => 'Klonla', + 'last_seven_days' => 'Son yedi gün', + 'last_thirty_days' => 'Son otuz gün', 'welcomeBack' => 'Neler oluyor?', - 'welcome_back' => 'What\'s playing?', + 'welcome_back' => 'Neler oluyor?', 'everything' => 'Her şey', 'today' => 'bugün', 'customRange' => 'Özel menzil', @@ -42,7 +42,7 @@ return [ 'cancel' => 'İptal', 'from' => 'Gönderen', 'to' => 'Alıcı', - 'structure' => 'Structure', + 'structure' => 'Yapı', 'help_translating' => 'Bu yardım metni henüz sizin dilinizde bulunmamaktadır. Çeviriye katkı sağlamak ister misiniz?', 'showEverything' => 'Her şeyi göster', 'never' => 'Asla', @@ -54,6 +54,7 @@ return [ 'create_new_stuff' => 'Yeni bir şey oluştur', 'new_withdrawal' => 'Yeni para çekme', 'create_new_transaction' => 'Yeni işlem oluştur', + 'new_transaction' => 'New transaction', 'go_to_asset_accounts' => 'Varlık hesaplarınızı görüntüleyin ', 'go_to_budgets' => 'Bütçelerine git', @@ -83,28 +84,32 @@ return [ 'help_for_this_page' => 'Bu sayfa için yardım', 'no_help_could_be_found' => 'Hiçbir yardım metni bulunamadı.', 'no_help_title' => 'Üzgünüz, bir hata oluştu.', - 'two_factor_welcome' => 'Merhaba, :user!', - 'two_factor_enter_code' => 'Devam etmek için lütfen iki faktörlü kimlik doğrulama kodunuzu girin. Uygulamanız sizin için oluşturabilir.', - 'two_factor_code_here' => 'Kodu buraya girin', - 'two_factor_title' => 'İki faktörlü kimlik doğrulaması', - 'authenticate' => 'Kimliği doğrula', - 'two_factor_forgot_title' => 'Kayıp iki faktörlü kimlik doğrulaması', - 'two_factor_forgot' => 'İki faktörlü kimlik doğrulama cihazını unuttum.', - 'two_factor_lost_header' => 'İki faktörlü kimlik doğrulamanızı mı kaybettiniz?', - 'two_factor_lost_intro' => 'Ne yazık ki, bu web arayüzünden sıfırlayabileceğiniz bir şey değil. İki seçeneğiniz var.', - 'two_factor_lost_fix_self' => 'Kendi Firefly III örneğinizi çalıştırıyorsanız, talimatlar için storage/logs\'daki günlükleri kontrol edin.', - 'two_factor_lost_fix_owner' => 'Aksi takdirde, site sahibine (:site_owner) e-posta gönderin ve iki faktörlü kimlik doğrulamasını sıfırlamasını isteyin.', - 'warning_much_data' => ':days günlük verinin yüklenmesi biraz zaman alabilir.', - 'registered' => 'Başarıyla kaydoldunuz!', - 'Default asset account' => 'Varsayılan varlık hesabı', - 'no_budget_pointer' => 'Henüz bütçeniz yok gibi görünüyor. Bütçe sayfasında bütçe oluşturun. Bütçeler giderleri takip etmenize yardımcı olabilir.', - 'Savings account' => 'Birikim Hesabı', - 'Credit card' => 'Kredi Kartı', - 'source_accounts' => 'Kaynak Hesap(lar)', - 'destination_accounts' => 'Hedef Hesap(lar)', - 'user_id_is' => 'Kullanıcı kimliğiniz :user', - 'field_supports_markdown' => 'Bu alan Markdown\'u destekliyor.', - 'need_more_help' => 'Firefly III kullanımında daha fazla yardıma ihtiyacınız olursa, lütfen Github\'da bir talep açın.', + 'two_factor_welcome' => 'Hello!', + 'two_factor_enter_code' => 'Devam etmek için lütfen iki faktörlü kimlik doğrulama kodunuzu girin. Uygulamanız sizin için oluşturabilir.', + 'two_factor_code_here' => 'Kodu buraya girin', + 'two_factor_title' => 'İki faktörlü kimlik doğrulaması', + 'authenticate' => 'Kimliği doğrula', + 'two_factor_forgot_title' => 'Kayıp iki faktörlü kimlik doğrulaması', + 'two_factor_forgot' => 'İki faktörlü kimlik doğrulama cihazını unuttum.', + 'two_factor_lost_header' => 'İki faktörlü kimlik doğrulamanızı mı kaybettiniz?', + 'two_factor_lost_intro' => 'If you lost your backup codes as well, you have bad luck. This is not something you can fix from the web interface. You have two choices.', + 'two_factor_lost_fix_self' => 'If you run your own instance of Firefly III, check the logs in storage/logs for instructions, or run docker logs <container_id> to see the instructions (refresh this page).', + 'two_factor_lost_fix_owner' => 'Aksi takdirde, site sahibine (:site_owner) e-posta gönderin ve iki faktörlü kimlik doğrulamasını sıfırlamasını isteyin.', + 'mfa_backup_code' => 'You have used a backup code to login to Firefly III. It can\'t be used again, so cross it from your list.', + 'pref_two_factor_new_backup_codes' => 'Get new backup codes', + 'pref_two_factor_backup_code_count' => 'You have :count valid backup code(s).', + '2fa_i_have_them' => 'I stored them!', + 'warning_much_data' => ':days günlük verinin yüklenmesi biraz zaman alabilir.', + 'registered' => 'Başarıyla kaydoldunuz!', + 'Default asset account' => 'Varsayılan varlık hesabı', + 'no_budget_pointer' => 'Henüz bütçeniz yok gibi görünüyor. Bütçe sayfasında bütçe oluşturun. Bütçeler giderleri takip etmenize yardımcı olabilir.', + 'Savings account' => 'Birikim Hesabı', + 'Credit card' => 'Kredi Kartı', + 'source_accounts' => 'Kaynak Hesap(lar)', + 'destination_accounts' => 'Hedef Hesap(lar)', + 'user_id_is' => 'Kullanıcı kimliğiniz :user', + 'field_supports_markdown' => 'Bu alan Markdown\'u destekliyor.', + 'need_more_help' => 'Firefly III kullanımında daha fazla yardıma ihtiyacınız olursa, lütfen Github\'da bir talep açın.', 'reenable_intro_text' => 'Ayrıca tanıtım kılavuzunu yeniden etkinleştirebilirsiniz.', 'intro_boxes_after_refresh' => 'Sayfayı yenilediğinizde tanıtım kutuları yeniden görünecektir.', 'show_all_no_filter' => 'Tüm işlemleri, tarihe göre gruplamadan gösterin.', @@ -116,7 +121,7 @@ return [ 'cannot_redirect_to_account' => 'Firefly III sizi doğru sayfaya yönlendiremiyor. Özür dileriz.', 'sum_of_expenses' => 'Giderlerin Toplamı', 'sum_of_income' => 'Gelirin Toplamı', - 'liabilities' => 'Liabilities', + 'liabilities' => 'Yükümlülükler', 'spent_in_specific_budget' => 'Miktar ":budget" harcandı', 'sum_of_expenses_in_budget' => 'Harcanan toplam bütçe ":budget"', 'left_in_budget_limit' => 'Bütçeye göre harcama ayrıldı', @@ -180,11 +185,11 @@ return [ 'button_reset_password' => 'Parolayı Sıfırla', 'reset_button' => 'Sıfırla', 'want_to_login' => 'Giriş yapmak istiyorum', - 'login_page_title' => 'Login to Firefly III', - 'register_page_title' => 'Register at Firefly III', - 'forgot_pw_page_title' => 'Forgot your password for Firefly III', - 'reset_pw_page_title' => 'Reset your password for Firefly III', - 'cannot_reset_demo_user' => 'You cannot reset the password of the demo user.', + 'login_page_title' => 'Firefly III\'e giriş yapın', + 'register_page_title' => 'Firefly III\'e kayıt olun', + 'forgot_pw_page_title' => 'Firefly III için şifrenizi mi unuttunuz', + 'reset_pw_page_title' => 'Firefly III şifrenizi sıfırlayın', + 'cannot_reset_demo_user' => 'Demo kullanıcısının şifresini sıfırlayamazsınız.', 'button_register' => 'Kayıt ol', 'authorization' => 'Yetkilendirme', 'active_bills_only' => 'sadece aktif faturalar', @@ -220,27 +225,29 @@ return [ // search 'search' => 'Ara', 'search_query' => 'Sorgu', - 'search_found_transactions' => 'Firefly III found :count transaction(s) in :time seconds.', - 'search_for_query' => 'Firefly III is searching for transactions with all of these words in them: :query', - 'search_modifier_amount_is' => 'Amount is exactly :value', - 'search_modifier_amount' => 'Amount is exactly :value', - 'search_modifier_amount_max' => 'Amount is at most :value', - 'search_modifier_amount_min' => 'Amount is at least :value', - 'search_modifier_amount_less' => 'Amount is less than :value', - 'search_modifier_amount_more' => 'Amount is more than :value', - 'search_modifier_source' => 'Source account is :value', - 'search_modifier_destination' => 'Destination account is :value', - 'search_modifier_category' => 'Category is :value', - 'search_modifier_budget' => 'Budget is :value', - 'search_modifier_bill' => 'Bill is :value', - 'search_modifier_type' => 'Transaction type is :value', - 'search_modifier_date' => 'Transaction date is :value', - 'search_modifier_date_before' => 'Transaction date is before :value', - 'search_modifier_date_after' => 'Transaction date is after :value', - 'search_modifier_on' => 'Transaction date is :value', - 'search_modifier_before' => 'Transaction date is before :value', - 'search_modifier_after' => 'Transaction date is after :value', - 'modifiers_applies_are' => 'The following modifiers are applied to the search as well:', + 'search_found_transactions' => 'Firefly III :time saniyede :count hesap haraketi buldu.', + 'search_for_query' => 'Firefly III şu kelimelerin hepsini içeren hareketleri arıyor: :query', + 'search_modifier_amount_is' => 'Miktar tam olarak şu: :value', + 'search_modifier_amount' => 'Miktar tam olarak şu: :value', + 'search_modifier_amount_max' => 'Miktar en fazla şu: :value', + 'search_modifier_amount_min' => 'Miktar en az şu: :value', + 'search_modifier_amount_less' => 'Tutar :value değerinden daha az', + 'search_modifier_amount_more' => 'Tutar şundan daha fazla: :value', + 'search_modifier_source' => 'Kaynak hesabı :value', + 'search_modifier_from' => 'Source account is :value', + 'search_modifier_destination' => 'Hedef hesabı :value', + 'search_modifier_to' => 'Destination account is :value', + 'search_modifier_category' => 'Kategori :value', + 'search_modifier_budget' => 'Bütçe :value', + 'search_modifier_bill' => 'Fatura :value', + 'search_modifier_type' => 'Hesap hareketi türü :value', + 'search_modifier_date' => 'Hesap hareketi tarihi :value', + 'search_modifier_date_before' => 'Hesap hareketi :value tarihinden önce', + 'search_modifier_date_after' => 'Hesap hareketi :value tarihinden sonra', + 'search_modifier_on' => 'Hesap hareketi tarihi :value', + 'search_modifier_before' => 'Hesap hareketi :value tarihinden önce', + 'search_modifier_after' => 'Hesap hareketi :value tarihinden sonra', + 'modifiers_applies_are' => 'Aşağıdaki değiştiriciler arama için de uygulanır:', 'general_search_error' => 'Arama yapılırken bir hata oluştu. Daha fazla bilgi için günlük dosyalarına bakın.', 'search_box' => 'Ara', 'search_box_intro' => 'Firefly III\'ün arama fonksiyonuna hoş geldiniz. Kutuya arama sorgunuzu giriniz. Arama oldukça gelişmiş olduğundan yardım dosyasını kontrol ettiğinizden emin olun.', @@ -259,33 +266,6 @@ return [ 'half-year' => 'her yarı yıl', 'yearly' => 'yıllık', - // export data: - 'import_and_export' => 'İçe al ve dışarı aktar', - 'export_data' => 'Veriyi dışarı aktar', - 'export_and_backup_data' => 'Veriyi dışarı aktar', - 'export_data_intro' => 'Verilen veriler yeni bir mali uygulama taşımak için kullanın. Unutmayın ki bu dosyaları yedek olarak demek değildir. Tümüyle yeni bir ateş böceği III yükleme geri yüklemek için yeterli meta verileri içerdikleri değil. Verilerinizi yedeklemek istiyorsanız, lütfen doğrudan veritabanı yedekleme.', - 'export_format' => 'Dışa Aktarma Biçimi', - 'export_format_csv' => 'Virgülle ayrılmış değerler (CSV file)', - 'export_format_mt940' => 'MT940 uyumlu format', - 'include_old_uploads_help' => 'Firefly III geçmişte aktarmış olduğunuz CSV dosyalarınızı herhangi bir yere atmıyor. Onları ihracata ekleyebilirsiniz.', - 'do_export' => 'Dışa Aktar', - 'export_status_never_started' => 'Dışa aktarım henüz başlamadı', - 'export_status_make_exporter' => 'İhracatçı oluşturuluyor...', - 'export_status_collecting_journals' => 'İşlemleriniz Hesaplanıyor...', - 'export_status_collected_journals' => 'İşlemlerinizi Hesaplandınız!', - 'export_status_converting_to_export_format' => 'İşlemleriniz Değiştiriliyor...', - 'export_status_converted_to_export_format' => 'İşlemlerinizi Değiştirdiniz!', - 'export_status_creating_journal_file' => 'Dışa aktarım dosyası oluşturuluyor...', - 'export_status_created_journal_file' => 'Dışa aktarım dosyası oluşturuldu!', - 'export_status_collecting_attachments' => 'Tüm eklentilerinizi topluyorsunuz...', - 'export_status_collected_attachments' => 'Tüm eklentilerinizi topladınız!', - 'export_status_collecting_old_uploads' => 'Tüm yüklemeleriniz toplanıyor...', - 'export_status_collected_old_uploads' => 'Tüm yüklemeleriniz toplandı!', - 'export_status_creating_zip_file' => 'Zip dosyası oluşturuluyor...', - 'export_status_created_zip_file' => 'Zip dosyası oluşturuldu!', - 'export_status_finished' => 'Satış başarıyla tamamlandı! Yuppi!', - 'export_data_please_wait' => 'Lütfen Bekle...', - // rules 'rules' => 'Kurallar', 'rule_name' => 'Kural adı', @@ -309,7 +289,7 @@ return [ 'save_rules_by_moving' => 'Bu kuralları başka bir kural grubuna taşıyarak kaydedin:', 'make_new_rule' => '":title" kural grubunda yeni kural oluşturun', 'make_new_rule_no_group' => 'Yeni bir kural ekleyin', - 'instructions_rule_from_bill' => 'In order to match transactions to your new bill ":name", Firefly III can create a rule that will automatically be checked against any transactions you store. Please verify the details below and store the rule to have Firefly III automatically match transactions to your new bill.', + 'instructions_rule_from_bill' => 'Hesap hareketlerini ":name" ile eşleştirebilmeniz için Firefly III depolayacağınız tüm hesap hareketlerine karşı otomatik olarak kontrol edebilen bir kural oluşturabilir. Lütfen aşağıdaki detayları doğrulayın ve Firefly III\'ün hesap hareketlerini yeni faturanızla otomatik olarak eşleştirebilmesi için bu kuralı depolayın.', 'rule_is_strict' => 'sıkı kural', 'rule_is_not_strict' => 'sıkı olmayan kural', 'rule_help_stop_processing' => 'Bu kutuyu işaretlediğinizde, bu grupta sonraki kurallar yürütülemez.', @@ -505,30 +485,33 @@ işlemlerin kontrol edildiğini lütfen unutmayın.', 'pref_custom_fiscal_year_help' => '1 Ocak - 31 Aralık arasındaki bir mali yılı kullanan ülkelerde bu ayarı açabilir ve mali yılın başlangıç / bitiş günlerini belirleyebilirsiniz', 'pref_fiscal_year_start_label' => 'Mali yıl başlangıç tarihi', 'pref_two_factor_auth' => '2 adımlı doğrulama', - 'pref_two_factor_auth_help' => '2 adımlı doğrulamayı etkinleştirdiğinizde (iki faktörlü kimlik doğrulama olarak da bilinir) hesabınıza fazladan bir güvenlik katmanı eklersiniz. Bildiğiniz bir şeyle (şifreniz) ve sahip olduğunuz bir şeyle(doğrulama kodu) ile giriş yaparsınız. Doğrulama kodları telefonunuzda Authy ya da Google Authenticator gibi bir uygulama tarafından oluşturulur.', - 'pref_enable_two_factor_auth' => '2 adımlı doğrulamayı etkinleştir', - 'pref_two_factor_auth_disabled' => '2 adımlı doğrulama kodu kaldırıldı ve devre dışı bırakıldı', - 'pref_two_factor_auth_remove_it' => 'Kimlik hesabı doğrulama uygulaması için çıkış yapmayı unutmayın!', - 'pref_two_factor_auth_code' => 'Doğrulama kodu', - 'pref_two_factor_auth_code_help' => 'QR kodunu, telefonunuzda Authy veya Authenticator uygulamalarında bulun ve oluşturulan kodu girin.', - 'pref_two_factor_auth_reset_code' => 'Doğrulama kodunu sıfırla', - 'pref_two_factor_auth_disable_2fa' => '2FA\'yı kapatın', - '2fa_use_secret_instead' => 'If you cannot scan the QR code, feel free to use the secret instead: :secret.', - 'pref_save_settings' => 'Ayarları kaydet', - 'saved_preferences' => 'Tercihler kaydedildi!', - 'preferences_general' => 'Genel', - 'preferences_frontpage' => 'Ana ekran', - 'preferences_security' => 'Güvenlik', - 'preferences_layout' => 'Düzen', - 'pref_home_show_deposits' => 'Ana ekranda mevduat göster', - 'pref_home_show_deposits_info' => 'Ana ekranınızda hesaplarınız zaten görüntülenir. Gelir hesaplarınız da görüntülensin mi?', - 'pref_home_do_show_deposits' => 'Evet, onlara göster', - 'successful_count' => ':count bunlar başarılı oldu', - 'list_page_size_title' => 'Sayfa boyutu', - 'list_page_size_help' => 'Herhangi bir şeyin listesi (hesaplar, işlemler, vb) sayfa başına en fazla bu kadar gösterebilir.', - 'list_page_size_label' => 'Sayfa boyutu', - 'between_dates' => '(:start ​ve :end)', - 'pref_optional_fields_transaction' => 'İşlemler için bağlı alanlar', + 'pref_two_factor_auth_help' => '2 adımlı doğrulamayı etkinleştirdiğinizde (iki faktörlü kimlik doğrulama olarak da bilinir) hesabınıza fazladan bir güvenlik katmanı eklersiniz. Bildiğiniz bir şeyle (şifreniz) ve sahip olduğunuz bir şeyle(doğrulama kodu) ile giriş yaparsınız. Doğrulama kodları telefonunuzda Authy ya da Google Authenticator gibi bir uygulama tarafından oluşturulur.', + 'pref_enable_two_factor_auth' => '2 adımlı doğrulamayı etkinleştir', + 'pref_two_factor_auth_disabled' => '2 adımlı doğrulama kodu kaldırıldı ve devre dışı bırakıldı', + 'pref_two_factor_auth_remove_it' => 'Kimlik hesabı doğrulama uygulaması için çıkış yapmayı unutmayın!', + 'pref_two_factor_auth_code' => 'Doğrulama kodu', + 'pref_two_factor_auth_code_help' => 'QR kodunu, telefonunuzda Authy veya Authenticator uygulamalarında bulun ve oluşturulan kodu girin.', + 'pref_two_factor_auth_reset_code' => 'Doğrulama kodunu sıfırla', + 'pref_two_factor_auth_disable_2fa' => '2FA\'yı kapatın', + '2fa_use_secret_instead' => 'If you cannot scan the QR code, feel free to use the secret instead: :secret.', + '2fa_backup_codes' => 'Store these backup codes for access in case you lose your device.', + '2fa_already_enabled' => '2-step verification is already enabled.', + 'wrong_mfa_code' => 'This MFA code is not valid.', + 'pref_save_settings' => 'Ayarları kaydet', + 'saved_preferences' => 'Tercihler kaydedildi!', + 'preferences_general' => 'Genel', + 'preferences_frontpage' => 'Ana ekran', + 'preferences_security' => 'Güvenlik', + 'preferences_layout' => 'Düzen', + 'pref_home_show_deposits' => 'Ana ekranda mevduat göster', + 'pref_home_show_deposits_info' => 'Ana ekranınızda hesaplarınız zaten görüntülenir. Gelir hesaplarınız da görüntülensin mi?', + 'pref_home_do_show_deposits' => 'Evet, onlara göster', + 'successful_count' => ':count bunlar başarılı oldu', + 'list_page_size_title' => 'Sayfa boyutu', + 'list_page_size_help' => 'Herhangi bir şeyin listesi (hesaplar, işlemler, vb) sayfa başına en fazla bu kadar gösterebilir.', + 'list_page_size_label' => 'Sayfa boyutu', + 'between_dates' => '(:start ​ve :end)', + 'pref_optional_fields_transaction' => 'İşlemler için bağlı alanlar', 'pref_optional_fields_transaction_help' => 'Yeni bir işlem oluşturulurken (dağınıklık nedeniyle) varsayılan olarak tüm alanlar ektinleştirilmez. Aşağıdan, eğer işinize yarayacağını düşünüyorsanız bu alanları ektinleştirebilirsiniz. Tabii ki, devre dışı bırakılmış ama zaten doldurulmuş alanlar ayarlar ne olursa olsun görünecektir.', 'optional_tj_date_fields' => 'Tarih alanları', 'optional_tj_business_fields' => 'İş alanları', @@ -582,24 +565,25 @@ işlemlerin kontrol edildiğini lütfen unutmayın.', 'login_with_new_email' => 'Artık yeni e-posta adresinizle giriş yapabilirsiniz.', 'login_with_old_email' => 'Artık eski e-posta adresinizle yeniden giriş yapabilirsiniz.', 'login_provider_local_only' => 'This action is not available when authenticating through ":login_provider".', - 'delete_local_info_only' => 'Because you authenticate through ":login_provider", this will only delete local Firefly III information.', + 'delete_local_info_only' => 'Because you authenticate through ":login_provider", this will only delete local Firefly III information.', // attachments - 'nr_of_attachments' => 'Bir eklenti |:count eklenti', - 'attachments' => 'Ekler', - 'edit_attachment' => 'Ek ":name" düzenle', - 'update_attachment' => 'Eki güncelle', - 'delete_attachment' => '":name" Eki sil', - 'attachment_deleted' => '":name" eki silindi', - 'attachment_updated' => '":name" Ek güncellendi', - 'upload_max_file_size' => 'Maksimum dosya boyutu: :size', - 'list_all_attachments' => 'List of all attachments', + 'nr_of_attachments' => 'Bir eklenti |:count eklenti', + 'attachments' => 'Ekler', + 'edit_attachment' => 'Ek ":name" düzenle', + 'update_attachment' => 'Eki güncelle', + 'delete_attachment' => '":name" Eki sil', + 'attachment_deleted' => '":name" eki silindi', + 'liabilities_deleted' => 'Deleted liability ":name"', + 'attachment_updated' => '":name" Ek güncellendi', + 'upload_max_file_size' => 'Maksimum dosya boyutu: :size', + 'list_all_attachments' => 'List of all attachments', // transaction index - 'title_expenses' => 'Giderler', - 'title_withdrawal' => 'Giderler', - 'title_revenue' => 'Gelir / Gelir', - 'title_deposit' => 'Gelir / Gelir', + 'title_expenses' => 'Giderler', + 'title_withdrawal' => 'Giderler', + 'title_revenue' => 'Gelir / Gelir', + 'title_deposit' => 'Gelir / Gelir', 'title_transfer' => 'Transferler', 'title_transfers' => 'Transferler', @@ -637,6 +621,9 @@ işlemlerin kontrol edildiğini lütfen unutmayın.', 'converted_to_Transfer' => 'İşlem aktarıma dönüştürüldü', 'invalid_convert_selection' => 'Seçtiğiniz hesap zaten bu işlemde kullanılmış veya mevcut değil.', 'source_or_dest_invalid' => 'Cannot find the correct transaction details. Conversion is not possible.', + 'convert_to_withdrawal' => 'Convert to a withdrawal', + 'convert_to_deposit' => 'Convert to a deposit', + 'convert_to_transfer' => 'Convert to a transfer', // create new stuff: 'create_new_withdrawal' => 'Yeni çekim oluştur', @@ -698,7 +685,9 @@ işlemlerin kontrol edildiğini lütfen unutmayın.', 'available_amount_indication' => 'Toplam bütçenizin ne olabileceğinin bir göstergesi olarak bu tutarı kullanın.', 'suggested' => 'Önerilen', 'average_between' => ':start ve :end arasında Averaj', - 'over_budget_warn' => ' Normally you budget about :amount per day. This is :over_amount per day.', + 'over_budget_warn' => ' Usually you budget about :amount per day. This time it\'s :over_amount per day. Are you sure?', + 'transferred_in' => 'Transferred (in)', + 'transferred_away' => 'Transferred (away)', // bills: 'match_between_amounts' => 'Bill matches transactions between :low and :high.', @@ -782,6 +771,7 @@ işlemlerin kontrol edildiğini lütfen unutmayın.', 'reconcile_options' => 'Mutabakat Seçenekleri', 'reconcile_range' => 'Mutabakat aralığı', 'start_reconcile' => 'Mutabakatı başlat', + 'cash_account_type' => 'Cash', 'cash' => 'nakit', 'account_type' => 'Hesap Türü', 'save_transactions_by_moving' => 'Bu işlemleri başka bir hesaba taşıyarak kaydet:', @@ -806,7 +796,9 @@ işlemlerin kontrol edildiğini lütfen unutmayın.', 'reconcile_go_back' => 'Bir düzeltmeyi daha sonra her zaman düzenleyebilir ya da silebilirsiniz.', 'must_be_asset_account' => 'Sadece varlık hesapları bağdaştırabilirsiniz', 'reconciliation_stored' => 'Depolanmış Mutabakat', - 'reconcilliation_transaction_title' => 'Bağdaştırma (:from to :to)', + 'reconciliation_error' => 'Due to an error the transactions were marked as reconciled but the correction has not been stored: :error.', + 'reconciliation_transaction_title' => 'Reconciliation (:from to :to)', + 'sum_of_reconciliation' => 'Sum of reconciliation', 'reconcile_this_account' => 'Bu hesabı bağdaştırınız', 'confirm_reconciliation' => 'Mutabakatı onayla', 'submitted_start_balance' => 'Gönderilen başlangıç bakiyesi', @@ -818,7 +810,7 @@ işlemlerin kontrol edildiğini lütfen unutmayın.', 'interest_calc_daily' => 'Per day', 'interest_calc_monthly' => 'Per month', 'interest_calc_yearly' => 'Per year', - 'initial_balance_account' => 'Initial balance account of :name', + 'initial_balance_account' => 'Initial balance account of :account', // categories: 'new_category' => 'Yeni Kategori', @@ -850,6 +842,8 @@ işlemlerin kontrol edildiğini lütfen unutmayın.', 'deleted_deposit' => '":description" Para yatırma başarıyla silindi', 'deleted_transfer' => '":description" Aktarım başarıyla silindi', 'stored_journal' => '":description" Yeni işlem başarıyla oluşturuldu', + 'stored_journal_no_descr' => 'Successfully created your new transaction', + 'updated_journal_no_descr' => 'Successfully updated your transaction', 'select_transactions' => 'İşlemleri Seç', 'rule_group_select_transactions' => 'İşlemlere "başlık" uygula', 'rule_select_transactions' => 'İşlemlere "başlık" uygula', @@ -858,26 +852,33 @@ işlemlerin kontrol edildiğini lütfen unutmayın.', 'mass_delete_journals' => 'Bir dizi işlemi sil', 'mass_edit_journals' => 'Bir dizi işlem düzenle', 'mass_bulk_journals' => 'Bulk edit a number of transactions', - 'mass_bulk_journals_explain' => 'If you do not want to change your transactions one-by-one using the mass-edit function, you can update them in one go. Simply select the preferred category, tag(s) or budget in the fields below, and all the transactions in the table will be updated.', + 'mass_bulk_journals_explain' => 'This form allows you to change properties of the transactions listed below in one sweeping update. All the transactions in the table will be updated when you change the parameters you see here.', + 'part_of_split' => 'This transaction is part of a split transaction. If you have not selected all the splits, you may end up with changing only half the transaction.', 'bulk_set_new_values' => 'Use the inputs below to set new values. If you leave them empty, they will be made empty for all. Also, note that only withdrawals will be given a budget.', 'no_bulk_category' => 'Don\'t update category', 'no_bulk_budget' => 'Don\'t update budget', - 'no_bulk_tags' => 'Don\'t update tag(s)', - 'bulk_edit' => 'Toplu düzenle', - 'cannot_edit_other_fields' => 'Gösterecek yer olmadığı için, bu dosya dışındaki dosyaları toplu olarak düzenleyemezsiniz. Eğer o alanları düzenlemeniz gerekliyse lütfen linki takip edin ve onları teker teker düzenleyin.', - 'no_budget' => '(no budget)', - 'no_budget_squared' => '(Bütçe yok)', - 'perm-delete-many' => 'Birden fazla öğeyi tek seferde silmek sıkıntı olabilir. Lütfen temkinli olun.', - 'mass_deleted_transactions_success' => 'Silinen :amount işlem(ler).', - 'mass_edited_transactions_success' => 'Güncellenen :amount işlem(ler)', - 'opt_group_' => '(no account type)', + 'no_bulk_tags' => 'Don\'t update tag(s)', + 'mass_edit' => 'Edit selected individually', + 'bulk_edit' => 'Edit selected in bulk', + 'mass_delete' => 'Delete selected', + 'cannot_edit_other_fields' => 'Gösterecek yer olmadığı için, bu dosya dışındaki dosyaları toplu olarak düzenleyemezsiniz. Eğer o alanları düzenlemeniz gerekliyse lütfen linki takip edin ve onları teker teker düzenleyin.', + 'cannot_change_amount_reconciled' => 'You can\'t change the amount of reconciled transactions.', + 'no_budget' => '(no budget)', + 'no_budget_squared' => '(Bütçe yok)', + 'perm-delete-many' => 'Deleting many items in one go can be very disruptive. Please be cautious. You can delete part of a split transaction from this page, so take care.', + 'mass_deleted_transactions_success' => 'Silinen :amount işlem(ler).', + 'mass_edited_transactions_success' => 'Güncellenen :amount işlem(ler)', + 'opt_group_' => '(no account type)', 'opt_group_no_account_type' => '(hesap türü yok)', 'opt_group_defaultAsset' => 'Varsayılan varlık hesapları', 'opt_group_savingAsset' => 'Tasarruf Hesapları', 'opt_group_sharedAsset' => 'Paylaşılan varlık hesapları', 'opt_group_ccAsset' => 'Kredi Kartı', 'opt_group_cashWalletAsset' => 'Nakit cüzdan', + 'opt_group_expense_account' => 'Expense accounts', + 'opt_group_revenue_account' => 'Revenue accounts', 'opt_group_l_Loan' => 'Liability: Loan', + 'opt_group_cash_account' => 'Cash account', 'opt_group_l_Debt' => 'Liability: Debt', 'opt_group_l_Mortgage' => 'Liability: Mortgage', 'opt_group_l_Credit card' => 'Liability: Credit card', @@ -976,7 +977,7 @@ işlemlerin kontrol edildiğini lütfen unutmayın.', 'errors' => 'Hatalar', 'debt_start_date' => 'Start date of debt', 'debt_start_amount' => 'Start amount of debt', - 'debt_start_amount_help' => 'Please enter the original amount of this liability as a positive number. You may also enter the current amount. Make sure to edit the date below to match.', + 'debt_start_amount_help' => 'If you owe an amount its best to enter a negative amount, because it influences your net worth. If you\'re owed an amount the same applies. Check out the help pages for more information.', 'store_new_liabilities_account' => 'Store new liability', 'edit_liabilities_account' => 'Edit liability ":name"', @@ -1148,6 +1149,7 @@ işlemlerin kontrol edildiğini lütfen unutmayın.', 'deleted_piggy_bank' => '":name" kumbarası silinmiş', 'added_amount_to_piggy' => '":name" e kadar olan miktar eklendi', 'removed_amount_from_piggy' => '":name"\'den :amount" silindi', + 'piggy_events' => 'Related piggy banks', // tags 'delete_tag' => '":tag" etiketi sil', @@ -1157,47 +1159,57 @@ işlemlerin kontrol edildiğini lütfen unutmayın.', 'updated_tag' => 'Güncellenmiş etiket ":tag"', 'created_tag' => 'Tag ":tag" oluşturuldu!', - 'transaction_journal_information' => 'İşlem Bilgileri', - 'transaction_journal_meta' => 'Meta Bilgisi', - 'total_amount' => 'Toplam Tutar', - 'number_of_decimals' => 'Ondalık Sayı', + 'transaction_journal_information' => 'İşlem Bilgileri', + 'transaction_journal_meta' => 'Meta Bilgisi', + 'transaction_journal_more' => 'More information', + 'att_part_of_journal' => 'Stored under ":journal"', + 'total_amount' => 'Toplam Tutar', + 'number_of_decimals' => 'Ondalık Sayı', // administration - 'administration' => 'Yönetim', - 'user_administration' => 'Kullanıcı Yönetimi', - 'list_all_users' => 'Tüm kullanıcılar', - 'all_users' => 'Tüm kullanıcılar', - 'instance_configuration' => 'Yapılandırma', - 'firefly_instance_configuration' => 'Firefly III için yapılandırma seçenekleri', - 'setting_single_user_mode' => 'Tek kullanıcı modu', - 'setting_single_user_mode_explain' => 'Varsayılan olarak Firefly III sadece bir kayıt kabul eder: sizi. Bu başkalarının sizin isteklerinizi kontrol etmemesi için bir güvenlik önlemidir. Gelecekteki kayıtlar engellendi. Bu kutunun işaretini kaldırdığınızda erişebildiklerini var sayarsak (internete bağlı olduğunda) başkaları da sizin isteklerinizi kullanabilir.', - 'store_configuration' => 'Mağaza yapılandırması', - 'single_user_administration' => ':email için kullanıcı yönetimi', - 'edit_user' => ':email kullanıcısını düzenle', - 'hidden_fields_preferences' => 'Şu anda tüm alanlar görülebilir değil. Ayarlardan etkinleştirmeniz gerek.', - 'user_data_information' => 'Kullanıcı bilgisi', - 'user_information' => 'Kullanıcı bilgisi', - 'total_size' => 'toplam boyut', - 'budget_or_budgets' => 'bütçe (ler)', - 'budgets_with_limits' => 'yapılandırılmış miktarlı bütçe(ler)', - 'nr_of_rules_in_total_groups' => ':count_groups kural gruplarındaki :count_rules kuralları', - 'tag_or_tags' => 'etiket (ler)', - 'configuration_updated' => 'Yapılandırma güncellendi', - 'setting_is_demo_site' => 'Tanıtım videosu', - 'setting_is_demo_site_explain' => 'Bu kutuyu işaretlediniz, bu yükleme, demo sitesinin sanki garip yan etkilere sahipmiş gibi davranır.', - 'block_code_bounced' => 'E-posta mesajı geri çevrildi', - 'block_code_expired' => 'Demo hesabı doldu', - 'no_block_code' => 'Engellemek için bir neden yok ya da kullanıcı engellenmemiş', - 'block_code_email_changed' => 'Kullanıcı henüz yeni bir e-posta adresi teyit etmedi', - 'admin_update_email' => 'Profil sayfasının aksine kullanıcı e-posta adresleri değiştirildiğinde bildirim ALMAYACAKLAR!', - 'update_user' => 'Kullanıcıyı güncelle', - 'updated_user' => 'Kullanıcı verileri değiştirildi.', - 'delete_user' => 'Kullanıcıyı sil :email', - 'user_deleted' => 'Kullanıcı silindi', - 'send_test_email' => 'E-posta adresine test mesajı gönder', - 'send_test_email_text' => 'Yüklemenizin e-posta gönderme yetkisine sahip olup olmadığını anlamak için lütfen bu tuşa basınız.Burada bir hata görmeyeceksiniz(varsa), kayıt dosyaları hataları gösterecektir. Bu düğmeye istediğiniz kadar basabilirsiniz. Spam kontrolü yoktur.Bu mesaj :email\'e gönderilecektir ve kısa sürede varmalıdır.', - 'send_message' => 'Mesaj gönder', - 'send_test_triggered' => 'Test tetiklendi. Gelen kutunuzu ve kayıt dosyalarınızı kontrol ediniz.', + 'administration' => 'Yönetim', + 'user_administration' => 'Kullanıcı Yönetimi', + 'list_all_users' => 'Tüm kullanıcılar', + 'all_users' => 'Tüm kullanıcılar', + 'instance_configuration' => 'Yapılandırma', + 'firefly_instance_configuration' => 'Firefly III için yapılandırma seçenekleri', + 'setting_single_user_mode' => 'Tek kullanıcı modu', + 'setting_single_user_mode_explain' => 'Varsayılan olarak Firefly III sadece bir kayıt kabul eder: sizi. Bu başkalarının sizin isteklerinizi kontrol etmemesi için bir güvenlik önlemidir. Gelecekteki kayıtlar engellendi. Bu kutunun işaretini kaldırdığınızda erişebildiklerini var sayarsak (internete bağlı olduğunda) başkaları da sizin isteklerinizi kullanabilir.', + 'store_configuration' => 'Mağaza yapılandırması', + 'single_user_administration' => ':email için kullanıcı yönetimi', + 'edit_user' => ':email kullanıcısını düzenle', + 'hidden_fields_preferences' => 'You can enable more transaction options in your settings.', + 'user_data_information' => 'Kullanıcı bilgisi', + 'user_information' => 'Kullanıcı bilgisi', + 'total_size' => 'toplam boyut', + 'budget_or_budgets' => 'bütçe (ler)', + 'budgets_with_limits' => 'yapılandırılmış miktarlı bütçe(ler)', + 'nr_of_rules_in_total_groups' => ':count_groups kural gruplarındaki :count_rules kuralları', + 'tag_or_tags' => 'etiket (ler)', + 'configuration_updated' => 'Yapılandırma güncellendi', + 'setting_is_demo_site' => 'Tanıtım videosu', + 'setting_is_demo_site_explain' => 'Bu kutuyu işaretlediniz, bu yükleme, demo sitesinin sanki garip yan etkilere sahipmiş gibi davranır.', + 'block_code_bounced' => 'E-posta mesajı geri çevrildi', + 'block_code_expired' => 'Demo hesabı doldu', + 'no_block_code' => 'Engellemek için bir neden yok ya da kullanıcı engellenmemiş', + 'block_code_email_changed' => 'Kullanıcı henüz yeni bir e-posta adresi teyit etmedi', + 'admin_update_email' => 'Profil sayfasının aksine kullanıcı e-posta adresleri değiştirildiğinde bildirim ALMAYACAKLAR!', + 'update_user' => 'Kullanıcıyı güncelle', + 'updated_user' => 'Kullanıcı verileri değiştirildi.', + 'delete_user' => 'Kullanıcıyı sil :email', + 'user_deleted' => 'Kullanıcı silindi', + 'send_test_email' => 'E-posta adresine test mesajı gönder', + 'send_test_email_text' => 'Yüklemenizin e-posta gönderme yetkisine sahip olup olmadığını anlamak için lütfen bu tuşa basınız.Burada bir hata görmeyeceksiniz(varsa), kayıt dosyaları hataları gösterecektir. Bu düğmeye istediğiniz kadar basabilirsiniz. Spam kontrolü yoktur.Bu mesaj :email\'e gönderilecektir ve kısa sürede varmalıdır.', + 'send_message' => 'Mesaj gönder', + 'send_test_triggered' => 'Test tetiklendi. Gelen kutunuzu ve kayıt dosyalarınızı kontrol ediniz.', + + 'split_transaction_title' => 'Description of the split transaction', + 'split_title_help' => 'If you create a split transaction, there must be a global description for all splits of the transaction.', + 'transaction_information' => 'Transaction information', + 'you_create_transfer' => 'You\'re creating a transfer.', + 'you_create_withdrawal' => 'You\'re creating a withdrawal.', + 'you_create_deposit' => 'You\'re creating a deposit.', + // links 'journal_link_configuration' => 'İşlem bağlantıları yapılandırması', @@ -1217,12 +1229,13 @@ işlemlerin kontrol edildiğini lütfen unutmayın.', 'do_not_save_connection' => '(bağlantıyı kaydetme)', 'link_transaction' => 'Bağlantı işlemi', 'link_to_other_transaction' => 'Bu işlemi başka bir işlemle bağlantılandır', - 'select_transaction_to_link' => 'Bu hareketi bağlamak için bir işlem seçin', + 'select_transaction_to_link' => 'Select a transaction to link this transaction to. The links are currently unused in Firefly III (apart from being shown), but I plan to change this in the future. Use the search box to select a transaction either by title or by ID. If you want to add custom link types, check out the administration section.', 'this_transaction' => 'Bu işlem', 'transaction' => 'İşlem', 'comments' => 'Yorumlar', - 'to_link_not_found' => 'Bağlantılamak istediğiniz işlem listelenmemişse, basitçe ID\'sini girin.', + 'link_notes' => 'Any notes you wish to store with the link.', 'invalid_link_selection' => 'Bu işlemler bağlantılanamıyor', + 'selected_transaction' => 'Selected transaction', 'journals_linked' => 'İşlemler bağlantıları oluşturuldu.', 'journals_error_linked' => 'Bu işlemler zaten bağlantılı.', 'journals_link_to_self' => 'Kendisi için bir ilişki ekleyemezsiniz.', @@ -1261,12 +1274,11 @@ işlemlerin kontrol edildiğini lütfen unutmayın.', 'split_this_withdrawal' => 'Bu çekimi böl', 'split_this_deposit' => 'Bu mevduat\'ı böl', 'split_this_transfer' => 'Bu aktarımı böl', - 'cannot_edit_multiple_source' => 'Bölünmüş işlemi #:id ile ":description"açıklamasıyla birlikte düzenleyemezsiniz. çünkü birden fazla kaynak hesabı içeriyor.', - 'cannot_edit_multiple_dest' => 'Bölünmüş işlemi #:id ile ":description"açıklamasıyla birlikte düzenleyemezsiniz. çünkü birden fazla kaynak hesabı içeriyor.', - 'cannot_edit_reconciled' => 'İşlemi #:id ile ":description"açıklamasıyla birlikte düzenleyemezsiniz. çünkü uzlaştırılmış olarak işaretli.', 'cannot_edit_opening_balance' => 'Bir hesabın açılış bakiyesini düzenleyemezsiniz.', 'no_edit_multiple_left' => 'Düzenlemek için geçerli İşlemler seçmediniz.', - 'cannot_convert_split_journal' => 'Bölünmüş bir İşlemi dönüştüremezsiniz', + 'breadcrumb_convert_group' => 'Convert transaction', + 'convert_invalid_source' => 'Source information is invalid for transaction #%d.', + 'convert_invalid_destination' => 'Destination information is invalid for transaction #%d.', // Import page (general strings only) 'import_index_title' => 'Firefly III\'e veri aktarma', @@ -1392,4 +1404,15 @@ işlemlerin kontrol edildiğini lütfen unutmayın.', 'will_jump_monday' => 'Will be created on Monday instead of the weekends.', 'except_weekends' => 'Except weekends', 'recurrence_deleted' => 'Recurring transaction ":title" deleted', + + // new lines for summary controller. + 'box_balance_in_currency' => 'Balance (:currency)', + 'box_spent_in_currency' => 'Spent (:currency)', + 'box_earned_in_currency' => 'Earned (:currency)', + 'box_bill_paid_in_currency' => 'Bills paid (:currency)', + 'box_bill_unpaid_in_currency' => 'Bills unpaid (:currency)', + 'box_left_to_spend_in_currency' => 'Left to spend (:currency)', + 'box_net_worth_in_currency' => 'Net worth (:currency)', + 'box_spend_per_day' => 'Left to spend per day: :amount', + ]; diff --git a/resources/lang/tr_TR/form.php b/resources/lang/tr_TR/form.php index 933205c0e5..e07f4e8621 100644 --- a/resources/lang/tr_TR/form.php +++ b/resources/lang/tr_TR/form.php @@ -57,20 +57,21 @@ return [ 'asset_source_account' => 'Source account', 'journal_description' => 'Tanımlama', 'note' => 'Notlar', + 'store_new_transaction' => 'Store new transaction', 'split_journal' => 'Bu işlemi böl / Taksitlendir', 'split_journal_explanation' => 'Bu işlemi taksitlendirin', 'currency' => 'Para birimi', 'account_id' => 'Varlık hesabı', 'budget_id' => 'Bütçe', - 'openingBalance' => 'Açılış bakiyesi', + 'opening_balance' => 'Opening balance', 'tagMode' => 'Etiket modu', 'tag_position' => 'Etiket konumu', - 'virtualBalance' => 'Sanal bakiye', + 'virtual_balance' => 'Virtual balance', 'targetamount' => 'Hedef tutar', - 'accountRole' => 'Hesap rolü', - 'openingBalanceDate' => 'Açılış bakiyesi tarihi', - 'ccType' => 'Kredi kartı ödeme planı', - 'ccMonthlyPaymentDate' => 'Kredi kartı aylık ödeme tarihi', + 'account_role' => 'Account role', + 'opening_balance_date' => 'Opening balance date', + 'cc_type' => 'Credit card payment plan', + 'cc_monthly_payment_date' => 'Credit card monthly payment date', 'piggy_bank_id' => 'Kumbara', 'returnHere' => 'Dön buraya', 'returnHereExplanation' => 'Sakladıktan sonra, başka bir tane oluşturmak için buraya dön.', @@ -118,7 +119,7 @@ return [ 'symbol' => 'Sembol', 'code' => 'Kod', 'iban' => 'IBAN numarası', - 'accountNumber' => 'Hesap numarası', + 'account_number' => 'Account number', 'creditCardNumber' => 'Kredi Kartı Numarası', 'has_headers' => 'Başlıklar', 'date_format' => 'Tarih formatı', @@ -139,12 +140,8 @@ return [ 'stop_processing' => 'İşlemeyi durdur', 'start_date' => 'Sayfa başlangıcı', 'end_date' => 'Kapsama alanı dışında', - 'export_start_range' => 'İhracatın başlangıcı', - 'export_end_range' => 'İhracatın sonu', - 'export_format' => 'Dosya biçimi', 'include_attachments' => 'Yüklenen ekleri dahil et', 'include_old_uploads' => 'İçe aktarılan verileri dahil et', - 'accounts' => 'Bu hesaptan işlemleri çıkarın', 'delete_account' => '":name" adlı hesabı sil', 'delete_bill' => 'Faturayı sil ":name"', 'delete_budget' => '":name" bütçesini sil', @@ -256,4 +253,7 @@ return [ 'weekend' => 'Weekend', 'client_secret' => 'Client secret', + 'withdrawal_destination_id' => 'Destination account', + 'deposit_source_id' => 'Source account', + ]; diff --git a/resources/lang/tr_TR/import.php b/resources/lang/tr_TR/import.php index ba91bc8c55..3f5c1c53bd 100644 --- a/resources/lang/tr_TR/import.php +++ b/resources/lang/tr_TR/import.php @@ -207,6 +207,10 @@ return [ 'specific_rabo_descr' => 'Fixes potential problems with Rabobank files', 'specific_pres_name' => 'President\'s Choice Financial CA', 'specific_pres_descr' => 'Fixes potential problems with PC files', + 'specific_belfius_name' => 'Belfius BE', + 'specific_belfius_descr' => 'Fixes potential problems with Belfius files', + 'specific_ingbelgium_name' => 'ING BE', + 'specific_ingbelgium_descr' => 'Fixes potential problems with ING Belgium files', // job configuration for file provider (stage: roles) 'job_config_roles_title' => 'Import setup (3/4) - Define each column\'s role', 'job_config_roles_text' => 'Each column in your CSV file contains certain data. Please indicate what kind of data the importer should expect. The option to "map" data means that you will link each entry found in the column to a value in your database. An often mapped column is the column that contains the IBAN of the opposing account. That can be easily matched to IBAN\'s present in your database already.', @@ -291,14 +295,14 @@ return [ 'column_rabo-debit-credit' => 'Rabobank\'a özel borç / kredi göstergesi', 'column_ing-debit-credit' => 'ING\'ye özel borç/kredi göstergesi', 'column_generic-debit-credit' => 'Generic bank debit/credit indicator', - 'column_sepa-ct-id' => 'SEPA end-to-end Identifier', - 'column_sepa-ct-op' => 'SEPA Opposing Account Identifier', - 'column_sepa-db' => 'SEPA Mandate Identifier', - 'column_sepa-cc' => 'SEPA Clearing Code', - 'column_sepa-ci' => 'SEPA Creditor Identifier', - 'column_sepa-ep' => 'SEPA External Purpose', - 'column_sepa-country' => 'SEPA Country Code', - 'column_sepa-batch-id' => 'SEPA Batch ID', + 'column_sepa_ct_id' => 'SEPA end-to-end Identifier', + 'column_sepa_ct_op' => 'SEPA Opposing Account Identifier', + 'column_sepa_db' => 'SEPA Mandate Identifier', + 'column_sepa_cc' => 'SEPA Clearing Code', + 'column_sepa_ci' => 'SEPA Creditor Identifier', + 'column_sepa_ep' => 'SEPA External Purpose', + 'column_sepa_country' => 'SEPA Country Code', + 'column_sepa_batch_id' => 'SEPA Batch ID', 'column_tags-comma' => 'Etiketler (virgülle ayrılmış)', 'column_tags-space' => 'Etiketler (boşlukla ayrılmış)', 'column_account-number' => 'Varlık hesabı (hesap numarası)', @@ -306,4 +310,7 @@ return [ 'column_note' => 'Not(lar)', 'column_internal-reference' => 'Internal reference', + // error message + 'duplicate_row' => 'Row #:row (":description") could not be imported. It already exists.', + ]; diff --git a/resources/lang/tr_TR/intro.php b/resources/lang/tr_TR/intro.php index d11f0e577a..8629b40148 100644 --- a/resources/lang/tr_TR/intro.php +++ b/resources/lang/tr_TR/intro.php @@ -24,39 +24,61 @@ declare(strict_types=1); return [ // index - 'index_intro' => 'Firefly III indeks sayfasına hoşgeldiniz. Firefly III\'nin nasıl çalıştığını öğrenmek için lütfen bu tanıtımı izleyin.', - 'index_accounts-chart' => 'Bu grafik, varlık hesaplarınızın geçerli bakiyesini gösterir. Burada görünen hesapları tercihlerinizde seçebilirsiniz.', - 'index_box_out_holder' => 'Bu küçük kutu ve bunun yanındaki kutular size finansal durumunuza hızlı bir bakış sunar.', - 'index_help' => 'Bir sayfa veya formla ilgili yardıma ihtiyacınız varsa, bu düğmeye basın.', - 'index_outro' => 'Firefly III\'ün çoğu sayfası bunun gibi küçük bir turla başlayacak. Sorularınız ve yorumlarınız olursa lütfen benimle iletişime geçin. Keyfini çıkarın!', - 'index_sidebar-toggle' => 'Yeni işlemler, hesaplar veya başka şeyler oluşturmak için bu simgenin altındaki menüyü kullanın.', + 'index_intro' => 'Firefly III indeks sayfasına hoşgeldiniz. Firefly III\'nin nasıl çalıştığını öğrenmek için lütfen bu tanıtımı izleyin.', + 'index_accounts-chart' => 'Bu grafik, varlık hesaplarınızın geçerli bakiyesini gösterir. Burada görünen hesapları tercihlerinizde seçebilirsiniz.', + 'index_box_out_holder' => 'Bu küçük kutu ve bunun yanındaki kutular size finansal durumunuza hızlı bir bakış sunar.', + 'index_help' => 'Bir sayfa veya formla ilgili yardıma ihtiyacınız varsa, bu düğmeye basın.', + 'index_outro' => 'Firefly III\'ün çoğu sayfası bunun gibi küçük bir turla başlayacak. Sorularınız ve yorumlarınız olursa lütfen benimle iletişime geçin. Keyfini çıkarın!', + 'index_sidebar-toggle' => 'Yeni işlemler, hesaplar veya başka şeyler oluşturmak için bu simgenin altındaki menüyü kullanın.', + 'index_cash_account' => 'These are the accounts created so far. You can use the cash account to track cash expenses but it\'s not mandatory of course.', + + // transactions (withdrawal) + 'transactions_create_withdrawal_source' => 'Select your favorite asset account or liability from this dropdown.', + 'transactions_create_withdrawal_destination' => 'Select an expense account here. Leave it empty if you want to make a cash expense.', + 'transactions_create_withdrawal_foreign_currency' => 'Use this field to set a foreign currency and amount.', + 'transactions_create_withdrawal_more_meta' => 'Plenty of other meta data you set in these fields.', + 'transactions_create_withdrawal_split_add' => 'If you want to split a transaction, add more splits with this button', + + // transactions (deposit) + 'transactions_create_deposit_source' => 'Select or type the payee in this auto-completing dropdown/textbox. Leave it empty if you want to make a cash deposit.', + 'transactions_create_deposit_destination' => 'Select an asset or liability account here.', + 'transactions_create_deposit_foreign_currency' => 'Use this field to set a foreign currency and amount.', + 'transactions_create_deposit_more_meta' => 'Plenty of other meta data you set in these fields.', + 'transactions_create_deposit_split_add' => 'If you want to split a transaction, add more splits with this button', + + // transactions (transfer) + 'transactions_create_transfer_source' => 'Select the source asset account here.', + 'transactions_create_transfer_destination' => 'Select the destination asset account here.', + 'transactions_create_transfer_foreign_currency' => 'Use this field to set a foreign currency and amount.', + 'transactions_create_transfer_more_meta' => 'Plenty of other meta data you set in these fields.', + 'transactions_create_transfer_split_add' => 'If you want to split a transaction, add more splits with this button', // create account: - 'accounts_create_iban' => 'Hesaplarınıza geçerli IBAN girin. Bu, ileride veri aktarma işlemini kolaylaştırabilir.', - 'accounts_create_asset_opening_balance' => 'Aktif hesapların, Firefly\'da bu hesap geçmişinin başlangıcını gösteren bir "açılış bakiyesi" olabilir.', - 'accounts_create_asset_currency' => 'Firefly III, birden fazla para birimini destekliyor. Varlık hesaplarının bir ana para birimi var, burada ayarlamanız gerekir.', - 'accounts_create_asset_virtual' => 'Bazen hesabınıza sanal bir bakiye sağlamanıza yardımcı olabilir: ek bir miktar her zaman gerçek bakiyeye eklenir veya gerçek bakiyeden çıkarılır.', + 'accounts_create_iban' => 'Hesaplarınıza geçerli IBAN girin. Bu, ileride veri aktarma işlemini kolaylaştırabilir.', + 'accounts_create_asset_opening_balance' => 'Aktif hesapların, Firefly\'da bu hesap geçmişinin başlangıcını gösteren bir "açılış bakiyesi" olabilir.', + 'accounts_create_asset_currency' => 'Firefly III, birden fazla para birimini destekliyor. Varlık hesaplarının bir ana para birimi var, burada ayarlamanız gerekir.', + 'accounts_create_asset_virtual' => 'Bazen hesabınıza sanal bir bakiye sağlamanıza yardımcı olabilir: ek bir miktar her zaman gerçek bakiyeye eklenir veya gerçek bakiyeden çıkarılır.', // budgets index - 'budgets_index_intro' => 'Bütçeler, finansmanınızı yönetmek ve Firefly III\'nin temel işlevlerinden birini oluşturmak için kullanılır.', - 'budgets_index_set_budget' => 'Toplam bütçenizi her dönem için belirleyin, böylelikle Firefly size mevcut tüm parayı bütçelendirdiğinizde söyleyebilir.', - 'budgets_index_see_expenses_bar' => 'Para harcamak yavaşça bu çubuğu dolduracaktır.', - 'budgets_index_navigate_periods' => 'Bütçeleri önceden kolayca ayarlamak için dönemleri gezinin.', - 'budgets_index_new_budget' => 'Uygun gördüğünüz yeni bütçeler oluşturun.', - 'budgets_index_list_of_budgets' => 'Her bütçe için tutarları ayarlamak ve ne durumda olduğunuzu görmek için bu tabloyu kullanın.', - 'budgets_index_outro' => 'Bütçeleme hakkında daha fazla bilgi almak için sağ üst köşedeki yardım simgesini kontrol edin.', + 'budgets_index_intro' => 'Bütçeler, finansmanınızı yönetmek ve Firefly III\'nin temel işlevlerinden birini oluşturmak için kullanılır.', + 'budgets_index_set_budget' => 'Toplam bütçenizi her dönem için belirleyin, böylelikle Firefly size mevcut tüm parayı bütçelendirdiğinizde söyleyebilir.', + 'budgets_index_see_expenses_bar' => 'Para harcamak yavaşça bu çubuğu dolduracaktır.', + 'budgets_index_navigate_periods' => 'Bütçeleri önceden kolayca ayarlamak için dönemleri gezinin.', + 'budgets_index_new_budget' => 'Uygun gördüğünüz yeni bütçeler oluşturun.', + 'budgets_index_list_of_budgets' => 'Her bütçe için tutarları ayarlamak ve ne durumda olduğunuzu görmek için bu tabloyu kullanın.', + 'budgets_index_outro' => 'Bütçeleme hakkında daha fazla bilgi almak için sağ üst köşedeki yardım simgesini kontrol edin.', // reports (index) - 'reports_index_intro' => 'Maliyetlerinizde ayrıntılı bilgi edinmek için bu raporları kullanın.', - 'reports_index_inputReportType' => 'Bir rapor türü seçin. Her bir raporun neyi gösterdiğini görmek için yardım sayfalarına göz atın.', - 'reports_index_inputAccountsSelect' => 'Varlık hesaplarını uygun gördüğünüz gibi hariç tutabilir veya ekleyebilirsiniz.', - 'reports_index_inputDateRange' => 'Seçilen tarih aralığı tamamen size kalmış: 1 günden 10 yıla kadar.', - 'reports_index_extra-options-box' => 'Seçtiğiniz rapora bağlı olarak, burada ekstra filtre ve seçenekleri belirleyebilirsiniz. Rapor türlerini değiştirirken bu kutuya dikkat edin.', + 'reports_index_intro' => 'Maliyetlerinizde ayrıntılı bilgi edinmek için bu raporları kullanın.', + 'reports_index_inputReportType' => 'Bir rapor türü seçin. Her bir raporun neyi gösterdiğini görmek için yardım sayfalarına göz atın.', + 'reports_index_inputAccountsSelect' => 'Varlık hesaplarını uygun gördüğünüz gibi hariç tutabilir veya ekleyebilirsiniz.', + 'reports_index_inputDateRange' => 'Seçilen tarih aralığı tamamen size kalmış: 1 günden 10 yıla kadar.', + 'reports_index_extra-options-box' => 'Seçtiğiniz rapora bağlı olarak, burada ekstra filtre ve seçenekleri belirleyebilirsiniz. Rapor türlerini değiştirirken bu kutuya dikkat edin.', // reports (reports) - 'reports_report_default_intro' => 'Bu rapor size mali durumunuz hakkında hızlı ve kapsamlı bir bilgi verecektir. Başka bir şey görmek isterseniz, lütfen benimle iletişime geçmekten çekinmeyin!', - 'reports_report_audit_intro' => 'Bu rapor size aktif hesaplarınızla ilgili ayrıntılı bilgiler verecektir.', - 'reports_report_audit_optionsBox' => 'İlgilendiğiniz sütunları göstermek veya gizlemek için bu onay kutularını kullanın.', + 'reports_report_default_intro' => 'Bu rapor size mali durumunuz hakkında hızlı ve kapsamlı bir bilgi verecektir. Başka bir şey görmek isterseniz, lütfen benimle iletişime geçmekten çekinmeyin!', + 'reports_report_audit_intro' => 'Bu rapor size aktif hesaplarınızla ilgili ayrıntılı bilgiler verecektir.', + 'reports_report_audit_optionsBox' => 'İlgilendiğiniz sütunları göstermek veya gizlemek için bu onay kutularını kullanın.', 'reports_report_category_intro' => 'Bu rapor size bir veya birden fazla kategoride fikir verecektir.', 'reports_report_category_pieCharts' => 'Bu grafikler, size her bir kategori veya hesaptaki gelir ve giderler konusunda fikir verecektir.', @@ -131,7 +153,7 @@ return [ // currencies 'currencies_index_intro' => 'Firefly III, bu sayfada değiştirebileceğiniz birden fazla para birimini destekliyor.', 'currencies_index_default' => 'Firefly III has one default currency.', - 'currencies_index_buttons' => 'Use these buttons to change the default currency or enable other currencies.', + 'currencies_index_buttons' => 'Varsayılan para birimini değiştirmek veya diğer para etkinleştirmek için bu düğmeleri kullanın.', // create currency 'currencies_create_code' => 'Bu kod ISO uyumlu olmalıdır (Yeni para biriminiz için Google\'da arayın).', diff --git a/resources/lang/tr_TR/list.php b/resources/lang/tr_TR/list.php index 799d20662f..0eca9b81ff 100644 --- a/resources/lang/tr_TR/list.php +++ b/resources/lang/tr_TR/list.php @@ -23,72 +23,72 @@ declare(strict_types=1); return [ - 'buttons' => 'Tuşlar', - 'icon' => 'Simge', - 'id' => 'Kimlik', - 'create_date' => 'Tarihinde oluşturuldu', - 'update_date' => 'Tarihinde güncellendi', - 'updated_at' => 'Tarihinde güncellendi', - 'balance_before' => 'Önceki bakiye', - 'balance_after' => 'Sonraki bakiye', - 'name' => 'İsim', - 'role' => 'Rol', - 'currentBalance' => 'Cari bakiye', - 'linked_to_rules' => ' + 'buttons' => 'Tuşlar', + 'icon' => 'Simge', + 'id' => 'Kimlik', + 'create_date' => 'Tarihinde oluşturuldu', + 'update_date' => 'Tarihinde güncellendi', + 'updated_at' => 'Tarihinde güncellendi', + 'balance_before' => 'Önceki bakiye', + 'balance_after' => 'Sonraki bakiye', + 'name' => 'İsim', + 'role' => 'Rol', + 'currentBalance' => 'Cari bakiye', + 'linked_to_rules' => ' İlgili kurallar', - 'active' => 'Aktif mi?', - 'lastActivity' => 'Son Etkinlik', - 'balanceDiff' => 'Bakiye farkı', - 'matchesOn' => 'Eşleşti', - 'account_type' => 'Hesap Türü', - 'created_at' => 'Tarihinde oluşturuldu', - 'account' => 'Hesap', - 'matchingAmount' => 'Miktar', - 'split_number' => 'Ayır #', - 'destination' => 'Hedef', - 'source' => 'Kaynak', - 'next_expected_match' => 'Beklenen sonraki eşleşme', - 'automatch' => 'Otomatik eşleştir?', - 'repeat_freq' => 'Tekrarlar', - 'description' => 'Açıklama', - 'amount' => 'Miktar', - 'internal_reference' => 'Dahili referans', - 'date' => 'Tarih', - 'interest_date' => 'Faiz tarihi', - 'book_date' => 'Kitap tarihi', - 'process_date' => 'İşlem tarihi', - 'due_date' => 'Bitiş tarihi', - 'payment_date' => 'Ödeme tarihi', - 'invoice_date' => 'Fatura tarihi', - 'interal_reference' => 'Dahili referans', - 'notes' => 'Notlar', - 'from' => 'Kimden', - 'piggy_bank' => 'Kumbara', - 'to' => 'Alıcı', - 'budget' => 'Bütçe', - 'category' => 'Kategori', - 'bill' => 'Fatura', - 'withdrawal' => 'Para Çekme', - 'deposit' => 'Yatır', - 'transfer' => 'Transfer', - 'type' => 'Tür', - 'completed' => 'Tamamlandı', - 'iban' => 'IBAN numarası', - 'paid_current_period' => 'Bu dönemde ödenen', - 'email' => 'E-posta', - 'registered_at' => 'Şurada kayıtlı', - 'is_blocked' => 'Engellendi', - 'is_admin' => 'Yönetici mi', - 'has_two_factor' => '2FA var', - 'blocked_code' => 'Blok kodu', - 'source_account' => 'Kaynak Hesap', + 'active' => 'Aktif mi?', + 'transaction_type' => 'Type', + 'lastActivity' => 'Son Etkinlik', + 'balanceDiff' => 'Bakiye farkı', + 'matchesOn' => 'Eşleşti', + 'account_type' => 'Hesap Türü', + 'created_at' => 'Tarihinde oluşturuldu', + 'account' => 'Hesap', + 'matchingAmount' => 'Miktar', + 'split_number' => 'Ayır #', + 'destination' => 'Hedef', + 'source' => 'Kaynak', + 'next_expected_match' => 'Beklenen sonraki eşleşme', + 'automatch' => 'Otomatik eşleştir?', + 'repeat_freq' => 'Tekrarlar', + 'description' => 'Açıklama', + 'amount' => 'Miktar', + 'internal_reference' => 'Dahili referans', + 'date' => 'Tarih', + 'interest_date' => 'Faiz tarihi', + 'book_date' => 'Kitap tarihi', + 'process_date' => 'İşlem tarihi', + 'due_date' => 'Bitiş tarihi', + 'payment_date' => 'Ödeme tarihi', + 'invoice_date' => 'Fatura tarihi', + 'interal_reference' => 'Dahili referans', + 'notes' => 'Notlar', + 'from' => 'Kimden', + 'piggy_bank' => 'Kumbara', + 'to' => 'Alıcı', + 'budget' => 'Bütçe', + 'category' => 'Kategori', + 'bill' => 'Fatura', + 'withdrawal' => 'Para Çekme', + 'deposit' => 'Yatır', + 'transfer' => 'Transfer', + 'type' => 'Tür', + 'completed' => 'Tamamlandı', + 'iban' => 'IBAN numarası', + 'paid_current_period' => 'Bu dönemde ödenen', + 'email' => 'E-posta', + 'registered_at' => 'Şurada kayıtlı', + 'is_blocked' => 'Engellendi', + 'is_admin' => 'Yönetici mi', + 'has_two_factor' => '2FA var', + 'blocked_code' => 'Blok kodu', + 'source_account' => 'Kaynak Hesap', 'destination_account' => 'Hedef Hesap', 'accounts_count' => 'Hesap Sayısı', 'journals_count' => 'İşlem Sayısı', 'attachments_count' => 'Eklerin sayısı', 'bills_count' => 'Fatura sayısı', 'categories_count' => 'Kategori sayısı', - 'export_jobs_count' => 'İhracat sayısı', 'import_jobs_count' => 'İthalat sayısı', 'budget_count' => 'Bütçelerin sayısı', 'rule_and_groups_count' => 'Kuralların ve kural gruplarının sayısı', @@ -107,14 +107,14 @@ return [ 'account_on_spectre' => '(Spectre) Hesabı', 'account_on_ynab' => 'Account (YNAB)', 'do_import' => 'Bu hesaptan içeri aktar', - 'sepa-ct-id' => 'SEPA End to End Identifier', - 'sepa-ct-op' => 'SEPA Opposing Account Identifier', - 'sepa-db' => 'SEPA Mandate Identifier', - 'sepa-country' => 'SEPA Country', - 'sepa-cc' => 'SEPA Clearing Code', - 'sepa-ep' => 'SEPA External Purpose', - 'sepa-ci' => 'SEPA Creditor Identifier', - 'sepa-batch-id' => 'SEPA Batch ID', + 'sepa_ct_id' => 'SEPA End to End Identifier', + 'sepa_ct_op' => 'SEPA Opposing Account Identifier', + 'sepa_db' => 'SEPA Mandate Identifier', + 'sepa_country' => 'SEPA Country', + 'sepa_cc' => 'SEPA Clearing Code', + 'sepa_ep' => 'SEPA External Purpose', + 'sepa_ci' => 'SEPA Creditor Identifier', + 'sepa_batch_id' => 'SEPA Batch ID', 'external_id' => 'External ID', 'account_at_bunq' => 'Account with bunq', 'file_name' => 'Dosya adı', diff --git a/resources/lang/tr_TR/passwords.php b/resources/lang/tr_TR/passwords.php index 3a312f2358..1cac0c383f 100644 --- a/resources/lang/tr_TR/passwords.php +++ b/resources/lang/tr_TR/passwords.php @@ -25,7 +25,7 @@ declare(strict_types=1); return [ 'password' => 'Şifreniz en az altı karakter olmalıdır ve onayla eşleşmelidir.', 'user' => 'Bu e-posta adresine sahip bir kullanıcı bulunamadı.', - 'token' => 'Şifre sıfırlama kodu geçersiz.', + 'token' => 'Bu şifre sıfırlama güvenlik anahtarı geçersiz.', 'sent' => 'Şifre sıfırlama linkini e-posta adresinize gönderdik!', 'reset' => 'Şifreniz sıfırlandı!', 'blocked' => 'Yine de iyi denemeydi.', diff --git a/resources/lang/tr_TR/validation.php b/resources/lang/tr_TR/validation.php index acf3732c8b..89cb343ce7 100644 --- a/resources/lang/tr_TR/validation.php +++ b/resources/lang/tr_TR/validation.php @@ -33,15 +33,19 @@ return [ 'rule_trigger_value' => 'Bu eylem, seçili işlem için geçersizdir.', 'rule_action_value' => 'Bu eylem seçili işlem için geçersizdir.', 'file_already_attached' => 'Yüklenen dosya ":name" zaten bu nesneye bağlı.', - 'file_attached' => '":name" dosyası başarıyla yüklendi.', + 'file_attached' => 'Successfully uploaded file ":name".', 'must_exist' => 'ID alanı :attribute veritabanın içinde yok.', 'all_accounts_equal' => 'Bu alandaki tüm hesapları eşit olmalıdır.', + 'group_title_mandatory' => 'A group title is mandatory when there is more than one transaction.', + 'transaction_types_equal' => 'All splits must be of the same type.', + 'invalid_transaction_type' => 'Invalid transaction type.', 'invalid_selection' => 'Seçiminiz geçersiz.', 'belongs_user' => 'Bu değer bu alan için geçerli değil.', 'at_least_one_transaction' => 'En az bir işlem gerekir.', 'at_least_one_repetition' => 'En az bir tekrarı gerekir.', 'require_repeat_until' => 'Require either a number of repetitions, or an end date (repeat_until). Not both.', 'require_currency_info' => 'Bu alanın içeriği para birimi bilgileri geçersiz.', + 'require_currency_amount' => 'The content of this field is invalid without foreign amount information.', 'equal_description' => 'İşlem açıklaması genel açıklama eşit değildir.', 'file_invalid_mime' => '":name" dosyası ":mime" türünde olup yeni bir yükleme olarak kabul edilemez.', 'file_too_large' => '":name" dosyası çok büyük.', @@ -135,8 +139,8 @@ return [ 'name' => 'adı', 'piggy_bank_id' => 'Kumbara ID', 'targetamount' => 'Hedef tutar', - 'openingBalanceDate' => 'Açılış bakiyesi', - 'openingBalance' => 'Açılış bakiyesi', + 'opening_balance_date' => 'opening balance date', + 'opening_balance' => 'opening balance', 'match' => 'Eşleşme', 'amount_min' => 'Minimum tutar', 'amount_max' => 'Maksimum tutar', @@ -164,4 +168,28 @@ return [ 'rule-trigger.4' => 'kural tetikleyici #4', 'rule-trigger.5' => 'kural tetikleyici #5', ], + + // validation of accounts: + 'withdrawal_source_need_data' => 'Need to get a valid source account ID and/or valid source account name to continue.', + 'withdrawal_source_bad_data' => 'Could not find a valid source account when searching for ID ":id" or name ":name".', + 'withdrawal_dest_need_data' => 'Need to get a valid destination account ID and/or valid destination account name to continue.', + 'withdrawal_dest_bad_data' => 'Could not find a valid destination account when searching for ID ":id" or name ":name".', + + 'deposit_source_need_data' => 'Need to get a valid source account ID and/or valid source account name to continue.', + 'deposit_source_bad_data' => 'Could not find a valid source account when searching for ID ":id" or name ":name".', + 'deposit_dest_need_data' => 'Need to get a valid destination account ID and/or valid destination account name to continue.', + 'deposit_dest_bad_data' => 'Could not find a valid destination account when searching for ID ":id" or name ":name".', + + 'transfer_source_need_data' => 'Need to get a valid source account ID and/or valid source account name to continue.', + 'transfer_source_bad_data' => 'Could not find a valid source account when searching for ID ":id" or name ":name".', + 'transfer_dest_need_data' => 'Need to get a valid destination account ID and/or valid destination account name to continue.', + 'transfer_dest_bad_data' => 'Could not find a valid destination account when searching for ID ":id" or name ":name".', + 'need_id_in_edit' => 'Each split must have transaction_journal_id (either valid ID or 0).', + + 'ob_source_need_data' => 'Need to get a valid source account ID and/or valid source account name to continue.', + 'ob_dest_need_data' => 'Need to get a valid destination account ID and/or valid destination account name to continue.', + 'ob_dest_bad_data' => 'Could not find a valid destination account when searching for ID ":id" or name ":name".', + + 'generic_invalid_source' => 'You can\'t use this account as the source account.', + 'generic_invalid_destination' => 'You can\'t use this account as the destination account.', ]; diff --git a/resources/lang/zh_CN/breadcrumbs.php b/resources/lang/zh_CN/breadcrumbs.php index 94e67a5eb9..705c1092a1 100644 --- a/resources/lang/zh_CN/breadcrumbs.php +++ b/resources/lang/zh_CN/breadcrumbs.php @@ -23,35 +23,37 @@ declare(strict_types=1); return [ - 'home' => '首页', - 'edit_currency' => '编辑货币 ":name"', - 'delete_currency' => '删除货币 ":name"', - 'newPiggyBank' => '创建一个新的小猪存钱罐', - 'edit_piggyBank' => '编辑小猪存钱罐 ":name"', - 'preferences' => '偏好设定', - 'profile' => '个人档案', - 'changePassword' => '更改您的密码', - 'change_email' => '更改您的电子邮件地址', - 'bills' => '帐单', - 'newBill' => '新增帐单', - 'edit_bill' => '编辑帐单 ":name"', - 'delete_bill' => '删除帐单 ":name"', - 'reports' => '报表', - 'search_result' => '":query" 的搜寻结果', - 'withdrawal_list' => '支出', - 'deposit_list' => '收入、所得与存款', - 'transfer_list' => '转帐', - 'transfers_list' => '转帐', - 'reconciliation_list' => '对帐', - 'create_withdrawal' => '新增提款', - 'create_deposit' => '新增存款', - 'create_transfer' => '新增转帐', - 'edit_journal' => '编辑交易 ":description"', - 'edit_reconciliation' => '编辑 ":description"', - 'delete_journal' => '删除交易 ":description"', - 'tags' => '标签', - 'createTag' => '建立新标签', - 'edit_tag' => '编辑标签 ":tag"', - 'delete_tag' => '删除标签 ":tag"', - 'delete_journal_link' => '删除交易记录之间的连结', + 'home' => '首页', + 'edit_currency' => '编辑货币 ":name"', + 'delete_currency' => '删除货币 ":name"', + 'newPiggyBank' => '创建一个新的存钱罐', + 'edit_piggyBank' => '编辑存钱罐 ":name"', + 'preferences' => '偏好设定', + 'profile' => '个人档案', + 'changePassword' => '更改您的密码', + 'change_email' => '更改您的电子邮件地址', + 'bills' => '帐单', + 'newBill' => '新增帐单', + 'edit_bill' => '编辑帐单 ":name"', + 'delete_bill' => '删除帐单 ":name"', + 'reports' => '报表', + 'search_result' => '":query" 的搜寻结果', + 'withdrawal_list' => '支出', + 'Withdrawal_list' => 'Expenses', + 'deposit_list' => '收入、所得与存款', + 'transfer_list' => '转帐', + 'transfers_list' => '转帐', + 'reconciliation_list' => '对帐', + 'create_withdrawal' => '新增提款', + 'create_deposit' => '新增存款', + 'create_transfer' => '新增转帐', + 'create_new_transaction' => '新建交易', + 'edit_journal' => '编辑交易 ":description"', + 'edit_reconciliation' => '编辑 ":description"', + 'delete_journal' => '删除交易 ":description"', + 'tags' => '标签', + 'createTag' => '建立新标签', + 'edit_tag' => '编辑标签 ":tag"', + 'delete_tag' => '删除标签 ":tag"', + 'delete_journal_link' => '删除交易记录之间的连结', ]; diff --git a/resources/lang/zh_CN/config.php b/resources/lang/zh_CN/config.php index 0c120f8184..0d6f54027b 100644 --- a/resources/lang/zh_CN/config.php +++ b/resources/lang/zh_CN/config.php @@ -24,7 +24,7 @@ declare(strict_types=1); return [ 'html_language' => 'zh', - 'locale' => 'zh_CN,简体中文,zh_CN.utf8,zh_CN.UTF-8', + 'locale' => 'zh_CN.utf8,zh_CN.UTF-8', 'month' => '%Y 年 %B', 'month_and_day' => '%Y年 %B %e日', 'month_and_date_day' => '%Y 年 %B %e 日 %A', diff --git a/resources/lang/zh_CN/firefly.php b/resources/lang/zh_CN/firefly.php index b794fb1a07..88bd7b6410 100644 --- a/resources/lang/zh_CN/firefly.php +++ b/resources/lang/zh_CN/firefly.php @@ -33,7 +33,7 @@ return [ 'last_seven_days' => '最近7天', 'last_thirty_days' => '最近30天', 'welcomeBack' => '吃饱没?', - 'welcome_back' => 'What\'s playing?', + 'welcome_back' => 'What\'s playing?', 'everything' => '所有', 'today' => '今天', 'customRange' => '自订范围', @@ -54,13 +54,14 @@ return [ 'create_new_stuff' => '建立新内容', 'new_withdrawal' => '新提款', 'create_new_transaction' => '建立新交易', + 'new_transaction' => '新交易', 'go_to_asset_accounts' => '检视您的资产帐户', 'go_to_budgets' => '前往您的预算', 'go_to_categories' => '前往您的分类', 'go_to_bills' => '前往您的帐单', 'go_to_expense_accounts' => '查看您的支出帐户', 'go_to_revenue_accounts' => '查看您的收入帐户', - 'go_to_piggies' => '前往您的小猪存钱罐', + 'go_to_piggies' => '前往您的存钱罐', 'new_deposit' => '新的存款', 'new_transfer' => '新的转帐', 'new_transfers' => '新的转帐', @@ -82,28 +83,32 @@ return [ 'help_for_this_page' => '本页说明', 'no_help_could_be_found' => '找不到说明文本。', 'no_help_title' => '不好意思,发生一个错误。', - 'two_factor_welcome' => ':user 您好!', - 'two_factor_enter_code' => '若要继续,请输入你的两步骤验证 (two factor authentication) 代码,您的应用程式可为您产生。', - 'two_factor_code_here' => '在此输入代码', - 'two_factor_title' => '两步骤验证', - 'authenticate' => '验证', - 'two_factor_forgot_title' => '遗失两步骤验证', - 'two_factor_forgot' => '往忘记我的两步骤什麽的。', - 'two_factor_lost_header' => '遗失您的两步骤验证吗?', - 'two_factor_lost_intro' => '不幸地,这并非您可以从网页介面重新设置的,您有两个选择。', - 'two_factor_lost_fix_self' => '若您自行营运 Firefly III 伺服器,请检察 storage/logs 内的日志以供指引。', - 'two_factor_lost_fix_owner' => '否则,请致信网站拥有者,:site_owner 并要求他们重置你的两步骤验证。', - 'warning_much_data' => ':days 天份的资料需要一点时间读取。', - 'registered' => '您已成功注册!', - 'Default asset account' => '预设资产帐户', - 'no_budget_pointer' => '您似乎尚无预算,您可至 预算页面来建立预算。预算可协助您追踪支出。', - 'Savings account' => '储蓄帐户', - 'Credit card' => '信用卡', - 'source_accounts' => '来源帐户', - 'destination_accounts' => '目标帐户', - 'user_id_is' => '您的使用者 ID 是 :user', - 'field_supports_markdown' => '此栏位支援 Markdown 语法。', - 'need_more_help' => '如果您需要更多 Firefly III 的协助,请 于 GitHub 建立提问。', + 'two_factor_welcome' => 'Hello!', + 'two_factor_enter_code' => '若要继续,请输入你的两步骤验证 (two factor authentication) 代码,您的应用程式可为您产生。', + 'two_factor_code_here' => '在此输入代码', + 'two_factor_title' => '两步骤验证', + 'authenticate' => '验证', + 'two_factor_forgot_title' => '遗失两步骤验证', + 'two_factor_forgot' => '往忘记我的两步骤什麽的。', + 'two_factor_lost_header' => '遗失您的两步骤验证吗?', + 'two_factor_lost_intro' => 'If you lost your backup codes as well, you have bad luck. This is not something you can fix from the web interface. You have two choices.', + 'two_factor_lost_fix_self' => 'If you run your own instance of Firefly III, check the logs in storage/logs for instructions, or run docker logs <container_id> to see the instructions (refresh this page).', + 'two_factor_lost_fix_owner' => '否则,请致信网站拥有者,:site_owner 并要求他们重置你的两步骤验证。', + 'mfa_backup_code' => 'You have used a backup code to login to Firefly III. It can\'t be used again, so cross it from your list.', + 'pref_two_factor_new_backup_codes' => 'Get new backup codes', + 'pref_two_factor_backup_code_count' => 'You have :count valid backup code(s).', + '2fa_i_have_them' => 'I stored them!', + 'warning_much_data' => ':days 天份的资料需要一点时间读取。', + 'registered' => '您已成功注册!', + 'Default asset account' => '预设资产帐户', + 'no_budget_pointer' => '您似乎尚无预算,您可至 预算页面来建立预算。预算可协助您追踪支出。', + 'Savings account' => '储蓄帐户', + 'Credit card' => '信用卡', + 'source_accounts' => '来源帐户', + 'destination_accounts' => '目标帐户', + 'user_id_is' => '您的使用者 ID 是 :user', + 'field_supports_markdown' => '此栏位支援 Markdown 语法。', + 'need_more_help' => '如果您需要更多 Firefly III 的协助,请 于 GitHub 建立提问。', 'reenable_intro_text' => '您也可以重新启用 说明指导。', 'intro_boxes_after_refresh' => '当您重新整理页面后,介绍框将会重新出现。', 'show_all_no_filter' => '不以日期分组,显示所有交易纪录', @@ -157,7 +162,7 @@ return [ 'title_transfers_between' => '自 :start 至 :end 的所有转帐', 'all_transfer' => '所有转帐', 'all_journals_for_tag' => '标签「:tag」的所有交易', - 'title_transfer_between' => '自 :start 至 :end 的所有转帐', + 'title_transfer_between' => ':start 到 :end 期间的所有转帐', 'all_journals_for_category' => '分类 :name 的所有交易', 'all_journals_for_budget' => '预算 :name 的所有交易', 'chart_all_journals_for_budget' => '预算 :name 的所有交易图表', @@ -167,7 +172,7 @@ return [ 'exchange_rate_instructions' => '资产帐户「@name」仅接受使用 @native_currency 的交易,若您希望使用 @foreign_currency,请确认 @native_currency 内的总额是已知的。', 'transfer_exchange_rate_instructions' => '来源资产帐户「@source_name」仅接受 @source_currency 的交易,目标资产帐户「@dest_name」紧接受 @dest_currency 的交易,您必须就双方货币正确地提供已转总额。', 'transaction_data' => '交易资料', - 'invalid_server_configuration' => '无效伺服器组态', + 'invalid_server_configuration' => '无效服务器设置', 'invalid_locale_settings' => 'Firefly III 无法格式化金额总数,因为您的伺服器缺乏必要套件。此处为 如何处置的指引。', 'quickswitch' => '快速切换', 'sign_in_to_start' => '登入以开始您的连线阶段', @@ -185,14 +190,14 @@ return [ 'reset_pw_page_title' => '重设您 Firefly III 的密码', 'cannot_reset_demo_user' => '您不能重置 演示使用者 的密码。', 'button_register' => '注册帐号', - 'authorization' => '身份认証', + 'authorization' => '认证', 'active_bills_only' => '只显示进行中的工作', 'average_per_bill' => '每张帐单的平均数', 'expected_total' => '期望总数', // API access 'authorization_request' => 'Firefly III :version 版授权请求', 'authorization_request_intro' => ':client 正在要求通行您的财务管理后台的许可,您是否愿意授权 :client 通行这些纪录?', - 'scopes_will_be_able' => '此应用程式可以:', + 'scopes_will_be_able' => '此应用可以:', 'button_authorize' => '授权', 'none_in_select_list' => '(空)', 'name_in_currency' => ':name 于 :currency', @@ -218,27 +223,29 @@ return [ // search 'search' => '搜寻', 'search_query' => '查询', - 'search_found_transactions' => 'Firefly III found :count transaction(s) in :time seconds.', - 'search_for_query' => 'Firefly III is searching for transactions with all of these words in them: :query', - 'search_modifier_amount_is' => 'Amount is exactly :value', - 'search_modifier_amount' => 'Amount is exactly :value', - 'search_modifier_amount_max' => 'Amount is at most :value', - 'search_modifier_amount_min' => 'Amount is at least :value', - 'search_modifier_amount_less' => 'Amount is less than :value', - 'search_modifier_amount_more' => 'Amount is more than :value', - 'search_modifier_source' => 'Source account is :value', - 'search_modifier_destination' => 'Destination account is :value', - 'search_modifier_category' => 'Category is :value', - 'search_modifier_budget' => 'Budget is :value', - 'search_modifier_bill' => 'Bill is :value', - 'search_modifier_type' => 'Transaction type is :value', - 'search_modifier_date' => 'Transaction date is :value', - 'search_modifier_date_before' => 'Transaction date is before :value', - 'search_modifier_date_after' => 'Transaction date is after :value', - 'search_modifier_on' => 'Transaction date is :value', - 'search_modifier_before' => 'Transaction date is before :value', - 'search_modifier_after' => 'Transaction date is after :value', - 'modifiers_applies_are' => 'The following modifiers are applied to the search as well:', + 'search_found_transactions' => 'Firefly III 在 :time 秒内找到 :count 笔交易。', + 'search_for_query' => 'Firefly III 正搜索含有 :query 的交易。', + 'search_modifier_amount_is' => '金额恰为 :value', + 'search_modifier_amount' => '金额恰为 :value', + 'search_modifier_amount_max' => '金额不超过 :value', + 'search_modifier_amount_min' => '金额不少于 :value', + 'search_modifier_amount_less' => '金额小于 :value', + 'search_modifier_amount_more' => '金额大于 :value', + 'search_modifier_source' => '来源帐户为 :value', + 'search_modifier_from' => 'Source account is :value', + 'search_modifier_destination' => '目标帐户为 :value', + 'search_modifier_to' => 'Destination account is :value', + 'search_modifier_category' => '分类为 :value', + 'search_modifier_budget' => '预算为 :value', + 'search_modifier_bill' => '账单是 :value', + 'search_modifier_type' => '交易类型为 :value', + 'search_modifier_date' => '交易日期为 :value', + 'search_modifier_date_before' => '交易日期早于 :value', + 'search_modifier_date_after' => '交易日期晚于 :value', + 'search_modifier_on' => '交易日期为 :value', + 'search_modifier_before' => '交易日期早于 :value', + 'search_modifier_after' => '交易日期晚于 :value', + 'modifiers_applies_are' => '以下修饰也适用于搜索:', 'general_search_error' => '在搜索时发生错误,请前往log files查看更错信息。', 'search_box' => '搜寻', 'search_box_intro' => '欢迎使用 Firefly III 的搜寻功能,请于方框内键入搜寻条件。请确保您以阅读过协助档案,因为此搜寻功能非常先进。', @@ -257,33 +264,6 @@ return [ 'half-year' => '每半年', 'yearly' => '每年', - // export data: - 'import_and_export' => '导入/导出', - 'export_data' => '汇出资料', - 'export_and_backup_data' => '汇出资料', - 'export_data_intro' => '使用汇出资料转移至新的财务应用程式。请注意这些档案并非备份,它们未包含足够的后设资料以供新安装的 Firefly III 作为完全还原所用。如果您希望备份您的资料,请直接备份整个资料库。', - 'export_format' => '汇出格式', - 'export_format_csv' => '以逗号分隔的数值 (CSV档案)', - 'export_format_mt940' => 'MT940 相容格式', - 'include_old_uploads_help' => 'Firefly III 不会扔掉过去已导入的原始 CSV 档,你可以将它们包含在导出的档案中。', - 'do_export' => '汇出', - 'export_status_never_started' => '汇出尚未开始', - 'export_status_make_exporter' => '建立汇出相关物件…', - 'export_status_collecting_journals' => '蒐集您的交易…', - 'export_status_collected_journals' => '已蒐集您的交易!', - 'export_status_converting_to_export_format' => '转换您的交易…', - 'export_status_converted_to_export_format' => '已转换您的交易!', - 'export_status_creating_journal_file' => '建立汇出档…', - 'export_status_created_journal_file' => '已建立汇出档!', - 'export_status_collecting_attachments' => '蒐集所有您的附加档案…', - 'export_status_collected_attachments' => '已蒐集所有您的附加档案!', - 'export_status_collecting_old_uploads' => '蒐集您过往的上传…', - 'export_status_collected_old_uploads' => '已蒐集您过往的上传!', - 'export_status_creating_zip_file' => '建立一个压缩档…', - 'export_status_created_zip_file' => '已建立一个压缩档!', - 'export_status_finished' => '汇出已成功完成!耶!', - 'export_data_please_wait' => '请稍后…', - // rules 'rules' => '规则', 'rule_name' => '规则名称', @@ -502,30 +482,33 @@ return [ 'pref_custom_fiscal_year_help' => '在使用1月1日至12月31日以外作为会计年度的国家,您可开启此功能并指定财政年度的起迄日。', 'pref_fiscal_year_start_label' => '财政年度开始日期', 'pref_two_factor_auth' => '两步骤验证', - 'pref_two_factor_auth_help' => '当您启用两步骤验证 (亦称为双重验证),便为您的帐号增加了一层安全保护。您以已知的方式 (密码) 以及既有物 (认证码) 登入,认证码系由您的手机产生,如 Authy 或 Google 身份验证器。', - 'pref_enable_two_factor_auth' => '启用两步骤验证', - 'pref_two_factor_auth_disabled' => '两步骤验证码已删除且停用', - 'pref_two_factor_auth_remove_it' => '别忘记自您的验证应用程式上删除帐号!', - 'pref_two_factor_auth_code' => '验证码', - 'pref_two_factor_auth_code_help' => '使用手机上的应用程式,如 Authy 或 Google 身分验证器,扫描 QR 码并输入自动产生之代码。', - 'pref_two_factor_auth_reset_code' => '重设认证码', - 'pref_two_factor_auth_disable_2fa' => '停用两步骤验证', - '2fa_use_secret_instead' => '如果您无法扫描 QR 码,请使用密钥: :secret。', - 'pref_save_settings' => '储存设定', - 'saved_preferences' => '偏好设定已储存!', - 'preferences_general' => '一般', - 'preferences_frontpage' => '主画面', - 'preferences_security' => '安全性', - 'preferences_layout' => '版面配置', - 'pref_home_show_deposits' => '在主画面显示存款', - 'pref_home_show_deposits_info' => '主画面已显示您的支出帐户,是否亦显示您的收入帐户?', - 'pref_home_do_show_deposits' => '是,要显示', - 'successful_count' => ':count 项成功', - 'list_page_size_title' => '页面大小', - 'list_page_size_help' => '任何物品 (帐户、交易…等) 清单在每页至多显示此数量', - 'list_page_size_label' => '页面大小', - 'between_dates' => '(:start 与 :end)', - 'pref_optional_fields_transaction' => '交易的选填栏位', + 'pref_two_factor_auth_help' => '当您启用两步骤验证 (亦称为双重验证),便为您的帐号增加了一层安全保护。您以已知的方式 (密码) 以及既有物 (认证码) 登入,认证码系由您的手机产生,如 Authy 或 Google 身份验证器。', + 'pref_enable_two_factor_auth' => '启用两步骤验证', + 'pref_two_factor_auth_disabled' => '两步骤验证码已删除且停用', + 'pref_two_factor_auth_remove_it' => '别忘记自您的验证应用程式上删除帐号!', + 'pref_two_factor_auth_code' => '验证码', + 'pref_two_factor_auth_code_help' => '使用手机上的应用程式,如 Authy 或 Google 身分验证器,扫描 QR 码并输入自动产生之代码。', + 'pref_two_factor_auth_reset_code' => '重设认证码', + 'pref_two_factor_auth_disable_2fa' => '停用两步骤验证', + '2fa_use_secret_instead' => 'If you cannot scan the QR code, feel free to use the secret instead: :secret.', + '2fa_backup_codes' => 'Store these backup codes for access in case you lose your device.', + '2fa_already_enabled' => '2-step verification is already enabled.', + 'wrong_mfa_code' => 'This MFA code is not valid.', + 'pref_save_settings' => '储存设定', + 'saved_preferences' => '偏好设定已储存!', + 'preferences_general' => '一般', + 'preferences_frontpage' => '主画面', + 'preferences_security' => '安全性', + 'preferences_layout' => '版面配置', + 'pref_home_show_deposits' => '在主画面显示存款', + 'pref_home_show_deposits_info' => '主画面已显示您的支出帐户,是否亦显示您的收入帐户?', + 'pref_home_do_show_deposits' => '是,要显示', + 'successful_count' => ':count 项成功', + 'list_page_size_title' => '页面大小', + 'list_page_size_help' => '任何物品 (帐户、交易…等) 清单在每页至多显示此数量', + 'list_page_size_label' => '页面大小', + 'between_dates' => '(:start 与 :end)', + 'pref_optional_fields_transaction' => '交易的选填栏位', 'pref_optional_fields_transaction_help' => '预设状况下,建立一笔新交易 (由于丛集关系) 时,并非所有栏位都是启用的。以下,您可启用您觉得对您有用的栏位。当然,任何已键入却停用的栏位,仍是可见的,与设定无关。', 'optional_tj_date_fields' => '日期栏位', 'optional_tj_business_fields' => '商务栏位', @@ -579,24 +562,25 @@ return [ 'login_with_new_email' => '现在,您可以使用新的电子邮件地址登入。', 'login_with_old_email' => '现在,您可以再次使用旧的电子邮件地址登入。', 'login_provider_local_only' => '当藉由 ":login_provider" 验证时,此动作不可用。', - 'delete_local_info_only' => '由于您以 ":login_provider" 验证,仅会删除本机 Firefly III 资讯。', + 'delete_local_info_only' => '由于您以 ":login_provider" 验证,仅会删除本机 Firefly III 资讯。', // attachments - 'nr_of_attachments' => '1个附加档案|:count 个附加档案', - 'attachments' => '附加档案', - 'edit_attachment' => '编辑附加档案 ":name"', - 'update_attachment' => '更新附加档案', - 'delete_attachment' => '删除附加档案 ":name"', - 'attachment_deleted' => '已删除附加档案 ":name"', - 'attachment_updated' => '已更新附加档案 ":name', - 'upload_max_file_size' => '最大档案尺寸: :size', - 'list_all_attachments' => '全部附加档案清单', + 'nr_of_attachments' => '1个附加档案|:count 个附加档案', + 'attachments' => '附加档案', + 'edit_attachment' => '编辑附加档案 ":name"', + 'update_attachment' => '更新附加档案', + 'delete_attachment' => '删除附加档案 ":name"', + 'attachment_deleted' => '已删除附加档案 ":name"', + 'liabilities_deleted' => 'Deleted liability ":name"', + 'attachment_updated' => '已更新附加档案 ":name', + 'upload_max_file_size' => '最大档案尺寸: :size', + 'list_all_attachments' => '全部附加档案清单', // transaction index - 'title_expenses' => '支出', - 'title_withdrawal' => '支出', - 'title_revenue' => '收入', - 'title_deposit' => '收入', + 'title_expenses' => '支出', + 'title_withdrawal' => '支出', + 'title_revenue' => '收入', + 'title_deposit' => '收入', 'title_transfer' => '转帐', 'title_transfers' => '转帐', @@ -634,6 +618,9 @@ return [ 'converted_to_Transfer' => '此交易已被转换为一笔转帐', 'invalid_convert_selection' => '您选择的帐户已用于此交易或不存在', 'source_or_dest_invalid' => '找不到正确的交易细节,无法转换。', + 'convert_to_withdrawal' => '转换为提款', + 'convert_to_deposit' => '转换为存款', + 'convert_to_transfer' => '转换为转帐', // create new stuff: 'create_new_withdrawal' => '建立新提款', @@ -642,7 +629,7 @@ return [ 'create_new_asset' => '建立新资产帐户', 'create_new_expense' => '建立新支出帐户', 'create_new_revenue' => '建立新收入帐户', - 'create_new_piggy_bank' => '建立新小猪存钱罐', + 'create_new_piggy_bank' => '建立新存钱罐', 'create_new_bill' => '建立新帐单', // currencies: @@ -695,7 +682,9 @@ return [ 'available_amount_indication' => '使用这些金额以获得您总预算可能为何的指标', 'suggested' => '建议', 'average_between' => '自 :start 至 :end 的平均', - 'over_budget_warn' => ' 通常您的每日预算为 :amount,此为每日 :over_amount。', + 'over_budget_warn' => ' Usually you budget about :amount per day. This time it\'s :over_amount per day. Are you sure?', + 'transferred_in' => 'Transferred (in)', + 'transferred_away' => 'Transferred (away)', // bills: 'match_between_amounts' => '帐单配合自 :low 至 :high 的交易。', @@ -779,6 +768,7 @@ return [ 'reconcile_options' => '对帐选项', 'reconcile_range' => '对帐范围', 'start_reconcile' => '开始对帐', + 'cash_account_type' => 'Cash', 'cash' => '现金', 'account_type' => '帐户类型', 'save_transactions_by_moving' => '藉由移动至其他帐户以储存这些交易', @@ -786,7 +776,7 @@ return [ 'updated_account' => '帐户 ":name" 已更新', 'credit_card_options' => '信用卡选项', 'no_transactions_account' => '资产帐户 ":name" 没有交易 (在此区间)。', - 'no_transactions_period' => 'There are no transactions (in this period).', + 'no_transactions_period' => '没有交易(在此期间)。', 'no_data_for_chart' => '目前 (尚) 没有足够资讯以产生此图表。', 'select_at_least_one_account' => '选择至少一个资产帐户', 'select_at_least_one_category' => '选择至少一个分类', @@ -803,7 +793,9 @@ return [ 'reconcile_go_back' => '您可之后再编辑或删除较正。', 'must_be_asset_account' => '您只可以对帐资产帐户。', 'reconciliation_stored' => '已储存对帐', - 'reconcilliation_transaction_title' => '对帐 (:from 至 :to)', + 'reconciliation_error' => 'Due to an error the transactions were marked as reconciled but the correction has not been stored: :error.', + 'reconciliation_transaction_title' => 'Reconciliation (:from to :to)', + 'sum_of_reconciliation' => 'Sum of reconciliation', 'reconcile_this_account' => '对帐此帐户', 'confirm_reconciliation' => '确认对帐', 'submitted_start_balance' => '初始余额已送出', @@ -815,7 +807,7 @@ return [ 'interest_calc_daily' => '每日', 'interest_calc_monthly' => '每月', 'interest_calc_yearly' => '每年', - 'initial_balance_account' => ':name 帐户的初始余额', + 'initial_balance_account' => 'Initial balance account of :account', // categories: 'new_category' => '新分类', @@ -847,6 +839,8 @@ return [ 'deleted_deposit' => '已成功删除存款 ”:description“', 'deleted_transfer' => '已成功删除转帐 ”:description“', 'stored_journal' => '已成功建立新交易 ”:description“', + 'stored_journal_no_descr' => '成功创建您的新交易', + 'updated_journal_no_descr' => 'Successfully updated your transaction', 'select_transactions' => '选择交易', 'rule_group_select_transactions' => '套用 ”:title“ 至交易', 'rule_select_transactions' => '套用 ”:title“ 至交易', @@ -855,26 +849,33 @@ return [ 'mass_delete_journals' => '删除数个交易', 'mass_edit_journals' => '编辑数个交易', 'mass_bulk_journals' => '批次编辑数个交易', - 'mass_bulk_journals_explain' => '使用大量编辑功能时,若您不想一步一步变更您的交易,您可一次性地进行更新。只要在下方的栏位选择想要的分类、标签或预算,所有表格内的交易都会被更新。', + 'mass_bulk_journals_explain' => 'This form allows you to change properties of the transactions listed below in one sweeping update. All the transactions in the table will be updated when you change the parameters you see here.', + 'part_of_split' => 'This transaction is part of a split transaction. If you have not selected all the splits, you may end up with changing only half the transaction.', 'bulk_set_new_values' => '用下方的输入栏位设定新的值。若您留空,则全部皆会为空值。此外,请注意仅有提款会被赋予预算。', 'no_bulk_category' => '不更新分类', 'no_bulk_budget' => '不更新预算', - 'no_bulk_tags' => '不更新标签', - 'bulk_edit' => '批次编辑', - 'cannot_edit_other_fields' => '由于画面空间限制,除了此处所示的栏位以外,您无法大量编辑其他栏位。若您需要编辑其他栏位,请依连结并按部就班编辑之。', - 'no_budget' => '(无预算)', - 'no_budget_squared' => '(无预算)', - 'perm-delete-many' => '一次删除多个项目系非常危险的,请审慎考虑。', - 'mass_deleted_transactions_success' => '已删除 :amount 笔交易。', - 'mass_edited_transactions_success' => '已更新 :amount 笔交易', - 'opt_group_' => '(没有帐户类型)', + 'no_bulk_tags' => '不更新标签', + 'mass_edit' => 'Edit selected individually', + 'bulk_edit' => 'Edit selected in bulk', + 'mass_delete' => 'Delete selected', + 'cannot_edit_other_fields' => '由于画面空间限制,除了此处所示的栏位以外,您无法大量编辑其他栏位。若您需要编辑其他栏位,请依连结并按部就班编辑之。', + 'cannot_change_amount_reconciled' => 'You can\'t change the amount of reconciled transactions.', + 'no_budget' => '(无预算)', + 'no_budget_squared' => '(无预算)', + 'perm-delete-many' => 'Deleting many items in one go can be very disruptive. Please be cautious. You can delete part of a split transaction from this page, so take care.', + 'mass_deleted_transactions_success' => '已删除 :amount 笔交易。', + 'mass_edited_transactions_success' => '已更新 :amount 笔交易', + 'opt_group_' => '(没有帐户类型)', 'opt_group_no_account_type' => '(没有帐户类型)', 'opt_group_defaultAsset' => '预设资产帐户', 'opt_group_savingAsset' => '储蓄帐户', 'opt_group_sharedAsset' => '共用资产帐户', 'opt_group_ccAsset' => '信用卡', 'opt_group_cashWalletAsset' => '现金皮夹', + 'opt_group_expense_account' => 'Expense accounts', + 'opt_group_revenue_account' => 'Revenue accounts', 'opt_group_l_Loan' => '债务: 贷款', + 'opt_group_cash_account' => 'Cash account', 'opt_group_l_Debt' => '债务: 欠款', 'opt_group_l_Mortgage' => '债务: 抵押', 'opt_group_l_Credit card' => '债务: 信用卡', @@ -900,13 +901,13 @@ return [ // home page: 'yourAccounts' => '您的帐户', - 'your_accounts' => 'Your account overview', - 'category_overview' => 'Category overview', - 'expense_overview' => 'Expense account overview', - 'revenue_overview' => 'Revenue account overview', + 'your_accounts' => '账户概览', + 'category_overview' => '类别概述', + 'expense_overview' => '支出账户概览', + 'revenue_overview' => '收入账户概览', 'budgetsAndSpending' => '预算与花费', - 'budgets_and_spending' => 'Budgets and spending', - 'go_to_budget' => 'Go to budget "{budget}"', + 'budgets_and_spending' => '预算与花费', + 'go_to_budget' => '转到预算 "{budget}"', 'savings' => '储蓄', 'newWithdrawal' => '新支出', 'newDeposit' => '新存款', @@ -924,10 +925,10 @@ return [ 'searchPlaceholder' => '搜寻中…', 'version' => '版本', 'dashboard' => '监控面板', - 'available_budget' => 'Available budget ({currency})', + 'available_budget' => '可用预算 ({currency})', 'currencies' => '货币', - 'activity' => 'Activity', - 'usage' => 'Usage', + 'activity' => '活动', + 'usage' => '使用情况', 'accounts' => '帐户', 'Asset account' => '资产帐户', 'Default account' => '资产帐户', @@ -948,9 +949,9 @@ return [ 'moneyManagement' => '金钱管理', 'money_management' => '金钱管理', 'tools' => '工具', - 'piggyBanks' => '小猪存钱罐', - 'piggy_banks' => '小猪存钱罐', - 'amount_x_of_y' => '{current} of {total}', + 'piggyBanks' => '存钱罐', + 'piggy_banks' => '存钱罐', + 'amount_x_of_y' => '{total} 中的 {current}', 'bills' => '帐单', 'withdrawal' => '提款', 'opening_balance' => '开户余额', @@ -973,7 +974,7 @@ return [ 'errors' => '错误', 'debt_start_date' => '负债开始日期', 'debt_start_amount' => '负债开始金额', - 'debt_start_amount_help' => '请以正数输入此债务的原始金额,您也可以输入目前的金额。请确定修改下方的日期以符合需求。', + 'debt_start_amount_help' => 'If you owe an amount its best to enter a negative amount, because it influences your net worth. If you\'re owed an amount the same applies. Check out the help pages for more information.', 'store_new_liabilities_account' => '储存新债务', 'edit_liabilities_account' => '编辑债务 “:name”', @@ -1101,36 +1102,36 @@ return [ 'period' => '区间', 'balance' => '余额', 'sum' => '总计', - 'summary' => 'Summary', + 'summary' => '概要', 'average' => '平均', 'balanceFor' => ':name 的余额', - 'no_tags_for_cloud' => 'No tags to generate cloud', - 'tag_cloud' => 'Tag cloud', + 'no_tags_for_cloud' => '没有生成标签云的标签', + 'tag_cloud' => '标签云', // piggy banks: - 'add_money_to_piggy' => '新增金钱至小猪存钱罐 “:name”', - 'piggy_bank' => '小猪存钱罐', + 'add_money_to_piggy' => '存入存钱罐 “:name”', + 'piggy_bank' => '存钱罐', 'new_piggy_bank' => '新建存钱罐', - 'store_piggy_bank' => '储存新的小猪存钱罐', - 'stored_piggy_bank' => '储存新的小猪存钱罐 “:name”', + 'store_piggy_bank' => '保存新的存钱罐', + 'stored_piggy_bank' => '保存新的存钱罐 “:name”', 'account_status' => '帐户状态', - 'left_for_piggy_banks' => '留给小猪存钱罐', - 'sum_of_piggy_banks' => '小猪存钱罐总和', + 'left_for_piggy_banks' => '留给存钱罐', + 'sum_of_piggy_banks' => '存钱罐总和', 'saved_so_far' => '保存到目前为止', 'left_to_save' => '剩余可供储蓄', 'suggested_amount' => '建议每月储蓄金额', - 'add_money_to_piggy_title' => '新增金钱至小猪存钱罐 “:name”', - 'remove_money_from_piggy_title' => '自小猪存钱罐 “:name” 中移走金钱', + 'add_money_to_piggy_title' => '存至存钱罐 “:name”', + 'remove_money_from_piggy_title' => '自存钱罐 “:name” 中取出', 'add' => '新增', - 'no_money_for_piggy' => '您已没有钱可放至小猪存钱罐', + 'no_money_for_piggy' => '您已没有钱可放至存钱罐', 'suggested_savings_per_month' => '每月建议', 'remove' => '移除', 'max_amount_add' => '所能增加之最大金额为', 'max_amount_remove' => '所能移除之最大金额为', - 'update_piggy_button' => '更新小猪存钱罐', - 'update_piggy_title' => '更新小猪存钱罐 ":name"', - 'updated_piggy_bank' => '更新小猪存钱罐 ":name"', + 'update_piggy_button' => '更新存钱罐', + 'update_piggy_title' => '更新存钱罐 ":name"', + 'updated_piggy_bank' => '更新存钱罐 ":name"', 'details' => '明细', 'events' => '事件', 'target_amount' => '目标金额', @@ -1139,12 +1140,13 @@ return [ 'target_date' => '目标日期', 'no_target_date' => '无目标日期', 'table' => '表格', - 'delete_piggy_bank' => '删除小猪存钱罐 ":name"', + 'delete_piggy_bank' => '删除存钱罐 ":name"', 'cannot_add_amount_piggy' => '无法增加 :amount 至 “:name”。', 'cannot_remove_from_piggy' => '无法自 “:name” 移除 :amount。', - 'deleted_piggy_bank' => '删除小猪存钱罐 ":name"', + 'deleted_piggy_bank' => '删除存钱罐 ":name"', 'added_amount_to_piggy' => '已新增 :amount 至 “:name”', 'removed_amount_from_piggy' => '已自 “:name” 移除 :amount', + 'piggy_events' => '相关的存钱罐', // tags 'delete_tag' => '删除标签 ":tag"', @@ -1154,47 +1156,57 @@ return [ 'updated_tag' => '已更新标签 “:tag”', 'created_tag' => '标签 “:tag” 已被建立!', - 'transaction_journal_information' => '交易资讯', - 'transaction_journal_meta' => '后设资讯', - 'total_amount' => '总金额', - 'number_of_decimals' => '小数位数:', + 'transaction_journal_information' => '交易资讯', + 'transaction_journal_meta' => '后设资讯', + 'transaction_journal_more' => '更多信息', + 'att_part_of_journal' => '在“:journal”中存储', + 'total_amount' => '总金额', + 'number_of_decimals' => '小数位数:', // administration - 'administration' => '管理', - 'user_administration' => '使用者管理', - 'list_all_users' => '所有使用者', - 'all_users' => '所有使用者', - 'instance_configuration' => '组态设定', - 'firefly_instance_configuration' => 'Firefly III 的组态设定选项', - 'setting_single_user_mode' => '单使用者模式', - 'setting_single_user_mode_explain' => '预设情况下, 萤火虫 iii 只接受一 (1) 个注册: 您。这是一项安全措施, 可防止其他人使用您的实例, 除非您允许他们使用。将来的注册将被阻止。当您取消选中此核取方块时, 其他人也可以使用您的实例, 假设他们可以到达它 (当它连接到互联网时)。', - 'store_configuration' => '储存设定', - 'single_user_administration' => ':email 的使用者管理后台', - 'edit_user' => '编辑使用者 :email', - 'hidden_fields_preferences' => '目前并非所有栏位都是可见的,您必须在 设定 启用它们。', - 'user_data_information' => '使用者资料', - 'user_information' => '使用者资讯', - 'total_size' => '总大小', - 'budget_or_budgets' => '预算', - 'budgets_with_limits' => '已设定金额的预算', - 'nr_of_rules_in_total_groups' => ':count_groups规则组中,包含:count_rules条规则', - 'tag_or_tags' => '标签', - 'configuration_updated' => '已更新组态设定', - 'setting_is_demo_site' => '演示网站', - 'setting_is_demo_site_explain' => '若您勾选此选项,此安装将会以展示网站方式运作,会有奇怪的副作用。', - 'block_code_bounced' => '电子邮件被退回', - 'block_code_expired' => '演示帐户已过期', - 'no_block_code' => '无封锁原因或使用者未被封锁', - 'block_code_email_changed' => '使用者尚未确认新的电子邮件地址', - 'admin_update_email' => '与个人资料页面相反,使用者不会被通知他们的电子邮件地址已变更!', - 'update_user' => '更新使用者', - 'updated_user' => '使用者资料已更改。', - 'delete_user' => '删除使用者 :email', - 'user_deleted' => '使用者已被删除', - 'send_test_email' => '寄送测试邮件讯息', - 'send_test_email_text' => '要检查您的安装是否有能力发送电子邮件,请按此按钮。您不会在此看到错误 (如果有的话),日志档才会反应一切错误。您可依照自己意愿点选此按钮,程式无管控垃圾邮件,测试讯息将会被寄发至 :email 并在短时间内送达。', - 'send_message' => '发送消息', - 'send_test_triggered' => '测试已触发,请检视您的收件匣与日志档。', + 'administration' => '管理', + 'user_administration' => '使用者管理', + 'list_all_users' => '所有使用者', + 'all_users' => '所有使用者', + 'instance_configuration' => '设置', + 'firefly_instance_configuration' => 'Firefly III 的设置', + 'setting_single_user_mode' => '单使用者模式', + 'setting_single_user_mode_explain' => '预设情况下, 萤火虫 iii 只接受一 (1) 个注册: 您。这是一项安全措施, 可防止其他人使用您的实例, 除非您允许他们使用。将来的注册将被阻止。当您取消选中此核取方块时, 其他人也可以使用您的实例, 假设他们可以到达它 (当它连接到互联网时)。', + 'store_configuration' => '储存设定', + 'single_user_administration' => ':email 的使用者管理后台', + 'edit_user' => '编辑使用者 :email', + 'hidden_fields_preferences' => '您可以在 设置中启用更多的交易选项。', + 'user_data_information' => '使用者资料', + 'user_information' => '使用者资讯', + 'total_size' => '总大小', + 'budget_or_budgets' => '预算', + 'budgets_with_limits' => '已设定金额的预算', + 'nr_of_rules_in_total_groups' => ':count_groups规则组中,包含:count_rules条规则', + 'tag_or_tags' => '标签', + 'configuration_updated' => '已更新设置', + 'setting_is_demo_site' => '演示网站', + 'setting_is_demo_site_explain' => '若您勾选此选项,此安装将会以展示网站方式运作,会有奇怪的副作用。', + 'block_code_bounced' => '电子邮件被退回', + 'block_code_expired' => '演示帐户已过期', + 'no_block_code' => '无封锁原因或使用者未被封锁', + 'block_code_email_changed' => '使用者尚未确认新的电子邮件地址', + 'admin_update_email' => '与个人资料页面相反,使用者不会被通知他们的电子邮件地址已变更!', + 'update_user' => '更新使用者', + 'updated_user' => '使用者资料已更改。', + 'delete_user' => '删除使用者 :email', + 'user_deleted' => '使用者已被删除', + 'send_test_email' => '寄送测试邮件讯息', + 'send_test_email_text' => '要检查您的安装是否有能力发送电子邮件,请按此按钮。您不会在此看到错误 (如果有的话),日志档才会反应一切错误。您可依照自己意愿点选此按钮,程式无管控垃圾邮件,测试讯息将会被寄发至 :email 并在短时间内送达。', + 'send_message' => '发送消息', + 'send_test_triggered' => '测试已触发,请检视您的收件匣与日志档。', + + 'split_transaction_title' => '拆分交易的描述', + 'split_title_help' => '如果您创建一个拆分交易,必须有一个全局的交易描述。', + 'transaction_information' => '交易信息', + 'you_create_transfer' => '您正在创建一笔转账。', + 'you_create_withdrawal' => '您正在创建一笔提款。', + 'you_create_deposit' => '您正在创建一笔存款。', + // links 'journal_link_configuration' => '交易链结设定', @@ -1214,12 +1226,13 @@ return [ 'do_not_save_connection' => '(不保存连接)', 'link_transaction' => '连结交易', 'link_to_other_transaction' => '链结此交易至另一笔', - 'select_transaction_to_link' => '选择一笔交易关联到这笔交易', + 'select_transaction_to_link' => 'Select a transaction to link this transaction to. The links are currently unused in Firefly III (apart from being shown), but I plan to change this in the future. Use the search box to select a transaction either by title or by ID. If you want to add custom link types, check out the administration section.', 'this_transaction' => '这笔交易', 'transaction' => '交易', 'comments' => '评论', - 'to_link_not_found' => '若您想要链结的交易不在清单上,只需要输入它的 ID 即可。', + 'link_notes' => 'Any notes you wish to store with the link.', 'invalid_link_selection' => '无法连结这些交易', + 'selected_transaction' => 'Selected transaction', 'journals_linked' => '交易是连结的。', 'journals_error_linked' => '这些交易已互相链结', 'journals_link_to_self' => '您无法将一笔交易链结至该交易本身', @@ -1258,12 +1271,11 @@ return [ 'split_this_withdrawal' => '拆分这笔提款', 'split_this_deposit' => '拆分此存款', 'split_this_transfer' => '拆分此转帐', - 'cannot_edit_multiple_source' => '不能用:description来拆分:id交易记录', - 'cannot_edit_multiple_dest' => '不能用:description来拆分:id交易记录', - 'cannot_edit_reconciled' => '你不能这样编辑#:id交易,因为:description已经被用来关联', 'cannot_edit_opening_balance' => '您无法编辑一个帐户的开户余额。', 'no_edit_multiple_left' => '您没有选择有效的交易纪录以供编辑。', - 'cannot_convert_split_journal' => '无法转换拆分交易记录', + 'breadcrumb_convert_group' => 'Convert transaction', + 'convert_invalid_source' => 'Source information is invalid for transaction #%d.', + 'convert_invalid_destination' => 'Destination information is invalid for transaction #%d.', // Import page (general strings only) 'import_index_title' => '将交易记录导入', @@ -1287,7 +1299,7 @@ return [ 'no_accounts_imperative_revenue' => '当您建立交易时收入帐户会自动被建立,但您也可以手动建立,现在就新增一个:', 'no_accounts_create_revenue' => '建立新收入帐户', 'no_accounts_title_liabilities' => '一起建立一笔债务!', - 'no_accounts_intro_liabilities' => 'You have no liabilities yet. Liabilities are the accounts that register your (student) loans and other debts.', + 'no_accounts_intro_liabilities' => '您还没有债务,债务是您注册信用卡、(学生)贷款或其他负债的帐户。', 'no_accounts_imperative_liabilities' => '您不需使用此功能,但若您要追踪这些东西,此功能可是很有用的。', 'no_accounts_create_liabilities' => '建立新债务', 'no_budgets_title_default' => '一起建立一笔预算', @@ -1314,10 +1326,10 @@ return [ 'no_transactions_intro_transfers' => '您还没有转帐。当您自资产帐户之前移动金钱,会自动记录为一笔转帐。', 'no_transactions_imperative_transfers' => '你已动用了一些钱吗?那麽把它写下来:', 'no_transactions_create_transfers' => '新增转帐', - 'no_piggies_title_default' => '我们来建立一个小猪存钱罐!', - 'no_piggies_intro_default' => '您目前没有小猪存钱罐。您可以建立小猪存钱罐来分别您的储蓄,并追踪您储蓄的目标。', - 'no_piggies_imperative_default' => '您有想要储蓄的东西吗?建立一个小猪存钱罐然后开始追踪:', - 'no_piggies_create_default' => '创建一个新的小猪存钱罐', + 'no_piggies_title_default' => '我们来建立一个存钱罐!', + 'no_piggies_intro_default' => '您目前没有存钱罐。您可以建立存钱罐来划分您的储蓄,并追踪您储蓄的目标。', + 'no_piggies_imperative_default' => '您有想要为之攒钱的东西吗?建立一个存钱罐然后并跟踪:', + 'no_piggies_create_default' => '创建一个新的存钱罐', 'no_bills_title_default' => '我们来建立一笔帐单!', 'no_bills_intro_default' => '您目前没有帐单。您可以建立帐单以追踪您的日常支出,如房租与保险。', 'no_bills_imperative_default' => '你们有这样的普通帐单吗?创建帐单并跟踪您的付款:', @@ -1352,7 +1364,7 @@ return [ 'recurring_meta_field_tags' => '标签', 'recurring_meta_field_notes' => '备注', 'recurring_meta_field_bill_id' => '帐单', - 'recurring_meta_field_piggy_bank_id' => '小猪存钱罐', + 'recurring_meta_field_piggy_bank_id' => '存钱罐', 'create_new_recurrence' => '建立新的周期性交易', 'help_first_date' => '表示第一笔预期的周期性交易,应于未来发生。', 'help_first_date_no_past' => '表示第一笔预期的周期交易,Firefly III 不会建立过去的交易。', @@ -1389,4 +1401,15 @@ return [ 'will_jump_monday' => '将推迟到星期一创建交易记录而不是周末创建。', 'except_weekends' => '例外的周末', 'recurrence_deleted' => '定期重复交易 ":title" 已删除', + + // new lines for summary controller. + 'box_balance_in_currency' => 'Balance (:currency)', + 'box_spent_in_currency' => 'Spent (:currency)', + 'box_earned_in_currency' => 'Earned (:currency)', + 'box_bill_paid_in_currency' => 'Bills paid (:currency)', + 'box_bill_unpaid_in_currency' => 'Bills unpaid (:currency)', + 'box_left_to_spend_in_currency' => 'Left to spend (:currency)', + 'box_net_worth_in_currency' => 'Net worth (:currency)', + 'box_spend_per_day' => 'Left to spend per day: :amount', + ]; diff --git a/resources/lang/zh_CN/form.php b/resources/lang/zh_CN/form.php index 9bb9eaabfa..3c551038cc 100644 --- a/resources/lang/zh_CN/form.php +++ b/resources/lang/zh_CN/form.php @@ -57,20 +57,21 @@ return [ 'asset_source_account' => '来源帐户', 'journal_description' => '说明', 'note' => '备注', + 'store_new_transaction' => '储存新交易', 'split_journal' => '分割此交易', 'split_journal_explanation' => '分割这个交易为几个部分', 'currency' => '货币', 'account_id' => '资产帐户', 'budget_id' => '预算', - 'openingBalance' => '初始余额', + 'opening_balance' => 'Opening balance', 'tagMode' => '标签模式', 'tag_position' => '标签位置', - 'virtualBalance' => '虚拟余额', + 'virtual_balance' => 'Virtual balance', 'targetamount' => '目标金额', - 'accountRole' => '帐户角色', - 'openingBalanceDate' => '初始余额日期', - 'ccType' => '信用卡付款计画', - 'ccMonthlyPaymentDate' => '信用卡每月付款日期', + 'account_role' => 'Account role', + 'opening_balance_date' => 'Opening balance date', + 'cc_type' => 'Credit card payment plan', + 'cc_monthly_payment_date' => 'Credit card monthly payment date', 'piggy_bank_id' => '小猪扑满', 'returnHere' => '返回此处', 'returnHereExplanation' => '储存后,返回这里建立另一笔记录。', @@ -118,7 +119,7 @@ return [ 'symbol' => '符号', 'code' => '代码', 'iban' => '国际银行帐户号码 (IBAN)', - 'accountNumber' => '帐户号码', + 'account_number' => 'Account number', 'creditCardNumber' => '信用卡卡号', 'has_headers' => '标题', 'date_format' => '日期格式', @@ -139,12 +140,8 @@ return [ 'stop_processing' => '停止处理', 'start_date' => '范围起点', 'end_date' => '范围终点', - 'export_start_range' => '输出范围起点', - 'export_end_range' => '输出范围终点', - 'export_format' => '档案格式', 'include_attachments' => '包括上传的附件', 'include_old_uploads' => '包含导入的数据', - 'accounts' => '从这些帐户汇出交易记录', 'delete_account' => '删除帐户 ":name"', 'delete_bill' => '删除帐单 ":name"', 'delete_budget' => '删除预算 ":name"', @@ -209,7 +206,7 @@ return [ // import 'import_file' => '导入文件', - 'configuration_file' => '组态档案', + 'configuration_file' => '设置档案', 'import_file_type' => '导入文件类型', 'csv_comma' => '逗号 (,)', 'csv_semicolon' => '分号 (;)', @@ -256,4 +253,7 @@ return [ 'weekend' => '周末', 'client_secret' => '客户端密钥', + 'withdrawal_destination_id' => 'Destination account', + 'deposit_source_id' => 'Source account', + ]; diff --git a/resources/lang/zh_CN/import.php b/resources/lang/zh_CN/import.php index 2fe05f88a4..61a36dfcbe 100644 --- a/resources/lang/zh_CN/import.php +++ b/resources/lang/zh_CN/import.php @@ -207,6 +207,10 @@ return [ 'specific_rabo_descr' => '修正 Rabobank 档案中的潜在问题', 'specific_pres_name' => 'President\'s Choice Financial CA', 'specific_pres_descr' => '修正 PC 档案中的潜在问题', + 'specific_belfius_name' => 'Belfius BE', + 'specific_belfius_descr' => 'Fixes potential problems with Belfius files', + 'specific_ingbelgium_name' => 'ING BE', + 'specific_ingbelgium_descr' => 'Fixes potential problems with ING Belgium files', // job configuration for file provider (stage: roles) 'job_config_roles_title' => '导入设定 (3/4) - 定义每个栏的角色', 'job_config_roles_text' => '在您 CSV 档案中的每个栏均含某些资料,请说明系核种资料供导入器参照。用以「映射」资料的选项,即您将连结每个栏中的资料至您资料库的一个值。一个常见的「已映射」的栏,是包含 IBAN 相对帐户的栏,这便可轻易地媒合至您资料库既存的 IBAN 帐户。', @@ -291,14 +295,14 @@ return [ 'column_rabo-debit-credit' => 'Rabobank 独有现金/信用卡指标', 'column_ing-debit-credit' => 'ING 独有 现金/信用卡 指标', 'column_generic-debit-credit' => 'Generic bank debit/credit indicator', - 'column_sepa-ct-id' => 'SEPA 端到端标识符', - 'column_sepa-ct-op' => 'SEPA Opposing 账户标识符', - 'column_sepa-db' => 'SEPA 授权标识符', - 'column_sepa-cc' => 'SEPA 清算代码', - 'column_sepa-ci' => 'SEPA 债权人标识符', - 'column_sepa-ep' => 'SEPA 外部用途', - 'column_sepa-country' => 'SEPA Country', - 'column_sepa-batch-id' => 'SEPA 批次 ID', + 'column_sepa_ct_id' => 'SEPA end-to-end Identifier', + 'column_sepa_ct_op' => 'SEPA Opposing Account Identifier', + 'column_sepa_db' => 'SEPA Mandate Identifier', + 'column_sepa_cc' => 'SEPA Clearing Code', + 'column_sepa_ci' => 'SEPA Creditor Identifier', + 'column_sepa_ep' => 'SEPA External Purpose', + 'column_sepa_country' => 'SEPA Country Code', + 'column_sepa_batch_id' => 'SEPA Batch ID', 'column_tags-comma' => '标签 (以逗号分隔)', 'column_tags-space' => '标签 (以空白键分隔)', 'column_account-number' => '资产帐户 (帐户号码)', @@ -306,4 +310,7 @@ return [ 'column_note' => '备注', 'column_internal-reference' => '内部参考', + // error message + 'duplicate_row' => 'Row #:row (":description") could not be imported. It already exists.', + ]; diff --git a/resources/lang/zh_CN/intro.php b/resources/lang/zh_CN/intro.php index 44bb747207..466b66c08a 100644 --- a/resources/lang/zh_CN/intro.php +++ b/resources/lang/zh_CN/intro.php @@ -24,39 +24,61 @@ declare(strict_types=1); return [ // index - 'index_intro' => '欢迎来到 Firefly III 的首页。请花时间参观一下这个介绍,瞭解 Firefly III 是如何运作的。', - 'index_accounts-chart' => '此图表显示您的资产帐户的目前馀额,您可以在偏好设定中选择此处可见的帐户。', - 'index_box_out_holder' => '这个小盒子和这个旁边的盒子会提供您财务状况的快速概览。', - 'index_help' => '如果您需要有关页面或表单的说明,请按此按钮。', - 'index_outro' => 'Firefly III 的大多数页面将从像这样的小介绍开始,如果您有任何问题或意见,请与我联繫。请享受!', - 'index_sidebar-toggle' => '若要建立新的交易记录、帐户或其他内容,请使用此图示下的选单。', + 'index_intro' => '欢迎来到 Firefly III 的首页。请花时间参观一下这个介绍,瞭解 Firefly III 是如何运作的。', + 'index_accounts-chart' => '此图表显示您的资产帐户的目前馀额,您可以在偏好设定中选择此处可见的帐户。', + 'index_box_out_holder' => '这个小盒子和这个旁边的盒子会提供您财务状况的快速概览。', + 'index_help' => '如果您需要有关页面或表单的说明,请按此按钮。', + 'index_outro' => 'Firefly III 的大多数页面将从像这样的小介绍开始,如果您有任何问题或意见,请与我联繫。请享受!', + 'index_sidebar-toggle' => '若要建立新的交易记录、帐户或其他内容,请使用此图示下的选单。', + 'index_cash_account' => 'These are the accounts created so far. You can use the cash account to track cash expenses but it\'s not mandatory of course.', + + // transactions (withdrawal) + 'transactions_create_withdrawal_source' => 'Select your favorite asset account or liability from this dropdown.', + 'transactions_create_withdrawal_destination' => 'Select an expense account here. Leave it empty if you want to make a cash expense.', + 'transactions_create_withdrawal_foreign_currency' => 'Use this field to set a foreign currency and amount.', + 'transactions_create_withdrawal_more_meta' => 'Plenty of other meta data you set in these fields.', + 'transactions_create_withdrawal_split_add' => 'If you want to split a transaction, add more splits with this button', + + // transactions (deposit) + 'transactions_create_deposit_source' => 'Select or type the payee in this auto-completing dropdown/textbox. Leave it empty if you want to make a cash deposit.', + 'transactions_create_deposit_destination' => 'Select an asset or liability account here.', + 'transactions_create_deposit_foreign_currency' => 'Use this field to set a foreign currency and amount.', + 'transactions_create_deposit_more_meta' => 'Plenty of other meta data you set in these fields.', + 'transactions_create_deposit_split_add' => 'If you want to split a transaction, add more splits with this button', + + // transactions (transfer) + 'transactions_create_transfer_source' => 'Select the source asset account here.', + 'transactions_create_transfer_destination' => 'Select the destination asset account here.', + 'transactions_create_transfer_foreign_currency' => 'Use this field to set a foreign currency and amount.', + 'transactions_create_transfer_more_meta' => 'Plenty of other meta data you set in these fields.', + 'transactions_create_transfer_split_add' => 'If you want to split a transaction, add more splits with this button', // create account: - 'accounts_create_iban' => '给您的帐户一个有效的 IBAN,可使未来资料导入变得更容易。', - 'accounts_create_asset_opening_balance' => '资产帐户可能有一个 "初始馀额",表示此帐户在 Firefly III 中的纪录开始。', - 'accounts_create_asset_currency' => 'Fireflly III 支持多种货币。资产帐户有一种主要货币,您必须在此处设定。', - 'accounts_create_asset_virtual' => '有时,它可以协助赋予你的帐户一个虚拟额度:一个总是增加至实际馀额中,或自其中删减的固定金额。', + 'accounts_create_iban' => '给您的帐户一个有效的 IBAN,可使未来资料导入变得更容易。', + 'accounts_create_asset_opening_balance' => '资产帐户可能有一个 "初始馀额",表示此帐户在 Firefly III 中的纪录开始。', + 'accounts_create_asset_currency' => 'Fireflly III 支持多种货币。资产帐户有一种主要货币,您必须在此处设定。', + 'accounts_create_asset_virtual' => '有时,它可以协助赋予你的帐户一个虚拟额度:一个总是增加至实际馀额中,或自其中删减的固定金额。', // budgets index - 'budgets_index_intro' => '预算是用来管理你的财务,也是 Firefly III 的核心功能之一。', - 'budgets_index_set_budget' => '设定每个期间的总预算,这样 Firefly III 就可以告诉你,是否已经将所有可用的钱设定预算。', - 'budgets_index_see_expenses_bar' => '消费金额会慢慢地填满这个横条。', - 'budgets_index_navigate_periods' => '前往区间,以便提前轻鬆设定预算。', - 'budgets_index_new_budget' => '根据需要建立新预算。', - 'budgets_index_list_of_budgets' => '使用此表可以设定每个预算的金额,并查看您的情况。', - 'budgets_index_outro' => '要瞭解有关预算的详细资讯,请查看右上角的说明图示。', + 'budgets_index_intro' => '预算是用来管理你的财务,也是 Firefly III 的核心功能之一。', + 'budgets_index_set_budget' => '设定每个期间的总预算,这样 Firefly III 就可以告诉你,是否已经将所有可用的钱设定预算。', + 'budgets_index_see_expenses_bar' => '消费金额会慢慢地填满这个横条。', + 'budgets_index_navigate_periods' => '前往区间,以便提前轻鬆设定预算。', + 'budgets_index_new_budget' => '根据需要建立新预算。', + 'budgets_index_list_of_budgets' => '使用此表可以设定每个预算的金额,并查看您的情况。', + 'budgets_index_outro' => '要瞭解有关预算的详细资讯,请查看右上角的说明图示。', // reports (index) - 'reports_index_intro' => '使用这些报表可以获得有关您财务状况的详细洞察报告。', - 'reports_index_inputReportType' => '选择报表类型。查看说明页面以瞭解每个报表向您显示的内容。', - 'reports_index_inputAccountsSelect' => '您可以根据需要排除或包括资产帐户。', - 'reports_index_inputDateRange' => '所选日期范围完全由您决定:从1天到10年不等。', - 'reports_index_extra-options-box' => '根据您选择的报表,您可以在此处选择额外的筛选标准和选项。更改报表类型时,请查看此区块。', + 'reports_index_intro' => '使用这些报表可以获得有关您财务状况的详细洞察报告。', + 'reports_index_inputReportType' => '选择报表类型。查看说明页面以瞭解每个报表向您显示的内容。', + 'reports_index_inputAccountsSelect' => '您可以根据需要排除或包括资产帐户。', + 'reports_index_inputDateRange' => '所选日期范围完全由您决定:从1天到10年不等。', + 'reports_index_extra-options-box' => '根据您选择的报表,您可以在此处选择额外的筛选标准和选项。更改报表类型时,请查看此区块。', // reports (reports) - 'reports_report_default_intro' => '这份报表将为您提供一个快速和全面的个人财务概览。如果你想看其他的东西,请不要犹豫并联繫我!', - 'reports_report_audit_intro' => '此报表将为您提供有关资产帐户的详细洞察报告。', - 'reports_report_audit_optionsBox' => '使用这些选取方块可以显示或隐藏您感兴趣的栏。', + 'reports_report_default_intro' => '这份报表将为您提供一个快速和全面的个人财务概览。如果你想看其他的东西,请不要犹豫并联繫我!', + 'reports_report_audit_intro' => '此报表将为您提供有关资产帐户的详细洞察报告。', + 'reports_report_audit_optionsBox' => '使用这些选取方块可以显示或隐藏您感兴趣的栏。', 'reports_report_category_intro' => '此报表将提供您一个或多个类别洞察报告。', 'reports_report_category_pieCharts' => '这些图表将提供您每个类别或每个帐户中,支出和所得的洞察报告。', @@ -76,20 +98,20 @@ return [ 'transactions_create_withdrawal_ffInput_budget' => '将您的提款连结至预算,以利财务管控。', 'transactions_create_withdrawal_currency_dropdown_amount' => '当您的提款使用另一种货币时, 请使用此下拉清单。', 'transactions_create_deposit_currency_dropdown_amount' => '当您的存款使用另一种货币时, 请使用此下拉清单。', - 'transactions_create_transfer_ffInput_piggy_bank_id' => '选择一个小猪存钱罐,并将此转帐连结到您的储蓄。', + 'transactions_create_transfer_ffInput_piggy_bank_id' => '选择一个存钱罐,并将此转帐连结到您的储蓄。', // piggy banks index: - 'piggy-banks_index_saved' => '此栏位显示您在每个小猪存钱罐中保存了多少。', - 'piggy-banks_index_button' => '此进度条旁边有两个按钮 (+ 和-),用于从每个小猪存钱罐中增加或删除资金。', - 'piggy-banks_index_accountStatus' => '此表中列出了每一个至少有一个小猪存钱罐的资产帐户的状态。', + 'piggy-banks_index_saved' => '此栏位显示您在每个存钱罐中存了多少。', + 'piggy-banks_index_button' => '此进度条旁边有两个按钮 ( + 和 - ),用于从每个存钱罐中投入或取出资金。', + 'piggy-banks_index_accountStatus' => '此表中列出了所有有存钱罐的资产帐户的状态。', // create piggy 'piggy-banks_create_name' => '你的目标是什麽?一个新沙发、一个相机、急难用金?', - 'piggy-banks_create_date' => '您可以为小猪存钱罐设定目标日期或截止日期。', + 'piggy-banks_create_date' => '您可以为存钱罐设定目标日期或截止日期。', // show piggy - 'piggy-banks_show_piggyChart' => '这张图表将显示这个小猪存钱罐的历史。', - 'piggy-banks_show_piggyDetails' => '关于你的小猪存钱罐的一些细节', + 'piggy-banks_show_piggyChart' => '这张图表将显示这个存钱罐的历史。', + 'piggy-banks_show_piggyDetails' => '关于你的存钱罐的一些细节', 'piggy-banks_show_piggyEvents' => '此处还列出了任何增加或删除。', // bill index diff --git a/resources/lang/zh_CN/list.php b/resources/lang/zh_CN/list.php index 5a7af755f1..9522017e29 100644 --- a/resources/lang/zh_CN/list.php +++ b/resources/lang/zh_CN/list.php @@ -23,71 +23,71 @@ declare(strict_types=1); return [ - 'buttons' => '按钮', - 'icon' => '图示', - 'id' => 'ID', - 'create_date' => '建立于', - 'update_date' => '更新于', - 'updated_at' => '更新于', - 'balance_before' => '交易前馀额', - 'balance_after' => '交易后馀额', - 'name' => '名称', - 'role' => '角色', - 'currentBalance' => '目前馀额', - 'linked_to_rules' => '相关规则', - 'active' => '是否启用?', - 'lastActivity' => '上次活动', - 'balanceDiff' => '馀额差', - 'matchesOn' => '配对于', - 'account_type' => '帐户类型', - 'created_at' => '建立于', - 'account' => '帐户', - 'matchingAmount' => '金额', - 'split_number' => '分割编号 #', - 'destination' => '目标', - 'source' => '来源', - 'next_expected_match' => '下一个预期的配对', - 'automatch' => '自动配对?', - 'repeat_freq' => '重复', - 'description' => '描述', - 'amount' => '金额', - 'internal_reference' => '内部参考', - 'date' => '日期', - 'interest_date' => '利率日期', - 'book_date' => '登记日期', - 'process_date' => '处理日期', - 'due_date' => '到期日', - 'payment_date' => '付款日期', - 'invoice_date' => '发票日期', - 'interal_reference' => '内部参考', - 'notes' => '备注', - 'from' => '自', - 'piggy_bank' => '小猪存钱罐', - 'to' => '至', - 'budget' => '预算', - 'category' => '分类', - 'bill' => '帐单', - 'withdrawal' => '提款', - 'deposit' => '存款', - 'transfer' => '转帐', - 'type' => '类型', - 'completed' => '已完成', - 'iban' => '国际银行帐户号码 (IBAN)', - 'paid_current_period' => '已付此区间', - 'email' => '电子邮件', - 'registered_at' => '注册于', - 'is_blocked' => '被封锁', - 'is_admin' => '是管理员', - 'has_two_factor' => '有双重身份验证 (2FA)', - 'blocked_code' => '区块代码', - 'source_account' => '来源帐户', + 'buttons' => '按钮', + 'icon' => '图示', + 'id' => 'ID', + 'create_date' => '建立于', + 'update_date' => '更新于', + 'updated_at' => '更新于', + 'balance_before' => '交易前馀额', + 'balance_after' => '交易后馀额', + 'name' => '名称', + 'role' => '角色', + 'currentBalance' => '目前馀额', + 'linked_to_rules' => '相关规则', + 'active' => '是否启用?', + 'transaction_type' => 'Type', + 'lastActivity' => '上次活动', + 'balanceDiff' => '馀额差', + 'matchesOn' => '配对于', + 'account_type' => '帐户类型', + 'created_at' => '建立于', + 'account' => '帐户', + 'matchingAmount' => '金额', + 'split_number' => '分割编号 #', + 'destination' => '目标', + 'source' => '来源', + 'next_expected_match' => '下一个预期的配对', + 'automatch' => '自动配对?', + 'repeat_freq' => '重复', + 'description' => '描述', + 'amount' => '金额', + 'internal_reference' => '内部参考', + 'date' => '日期', + 'interest_date' => '利率日期', + 'book_date' => '登记日期', + 'process_date' => '处理日期', + 'due_date' => '到期日', + 'payment_date' => '付款日期', + 'invoice_date' => '发票日期', + 'interal_reference' => '内部参考', + 'notes' => '备注', + 'from' => '自', + 'piggy_bank' => '存钱罐', + 'to' => '至', + 'budget' => '预算', + 'category' => '分类', + 'bill' => '帐单', + 'withdrawal' => '提款', + 'deposit' => '存款', + 'transfer' => '转帐', + 'type' => '类型', + 'completed' => '已完成', + 'iban' => '国际银行帐户号码 (IBAN)', + 'paid_current_period' => '已付此区间', + 'email' => '电子邮件', + 'registered_at' => '注册于', + 'is_blocked' => '被封锁', + 'is_admin' => '是管理员', + 'has_two_factor' => '有双重身份验证 (2FA)', + 'blocked_code' => '区块代码', + 'source_account' => '来源帐户', 'destination_account' => '目标帐户', 'accounts_count' => '帐户数量', 'journals_count' => '交易数量', 'attachments_count' => '附加档案数量', 'bills_count' => '帐单数量', 'categories_count' => '分类数量', - 'export_jobs_count' => '汇出工作数量', 'import_jobs_count' => '导入工作数量', 'budget_count' => '预算数量', 'rule_and_groups_count' => '规则及规则群组数量', @@ -106,14 +106,14 @@ return [ 'account_on_spectre' => '帐户 (Spectre)', 'account_on_ynab' => '帐户 (YNAB)', 'do_import' => '自此帐户导入', - 'sepa-ct-id' => 'SEPA 端到端标识符', - 'sepa-ct-op' => 'SEPA 反对账户标识符', - 'sepa-db' => 'SEPA 授权标识符', - 'sepa-country' => 'SEPA 国家', - 'sepa-cc' => 'SEPA 清关代码', - 'sepa-ep' => 'SEPA 外部用途', - 'sepa-ci' => 'SEPA 债权人标识符', - 'sepa-batch-id' => 'SEPA 批次 ID', + 'sepa_ct_id' => 'SEPA 端到端标识符', + 'sepa_ct_op' => 'SEPA Opposing Account Identifier', + 'sepa_db' => 'SEPA Mandate Identifier', + 'sepa_country' => 'SEPA Country', + 'sepa_cc' => 'SEPA Clearing Code', + 'sepa_ep' => 'SEPA External Purpose', + 'sepa_ci' => 'SEPA Creditor Identifier', + 'sepa_batch_id' => 'SEPA Batch ID', 'external_id' => '外部 ID', 'account_at_bunq' => 'bunq 帐户', 'file_name' => '档案名称', diff --git a/resources/lang/zh_CN/validation.php b/resources/lang/zh_CN/validation.php index 7f72722c2a..0cf1535bfc 100644 --- a/resources/lang/zh_CN/validation.php +++ b/resources/lang/zh_CN/validation.php @@ -25,7 +25,7 @@ declare(strict_types=1); return [ 'iban' => '这不是有效的 IBAN。', 'zero_or_more' => '此数值不能为负数', - 'date_or_time' => 'The value must be a valid date or time value (ISO 8601).', + 'date_or_time' => '必须是有效的日期或时间(ISO 8601)。', 'source_equals_destination' => '来源帐户与目标帐户相同。', 'unique_account_number_for_user' => '看起来此帐户号码已在使用中。', 'unique_iban_for_user' => '看起来此IBAN已在使用中。', @@ -33,15 +33,19 @@ return [ 'rule_trigger_value' => '此值不能用于所选择的触发器。', 'rule_action_value' => '此值不能用于所选择的动作。', 'file_already_attached' => '上传的档案 ":name" 已附加到该物件上。', - 'file_attached' => '已成功上传档案 ":name"。', + 'file_attached' => '文件成功上传 ":name".', 'must_exist' => '栏位 :attribute 的 ID 不存在于资料库。', 'all_accounts_equal' => '此栏位中的所有帐户必须相等。', + 'group_title_mandatory' => '在有超过一笔交易时,必须有分组标题。', + 'transaction_types_equal' => '所有拆分的类型必须相同。', + 'invalid_transaction_type' => '无效交易类型。', 'invalid_selection' => '您的选择无效。', 'belongs_user' => '此值对此栏位无效。', 'at_least_one_transaction' => '至少需要一个交易。', 'at_least_one_repetition' => '至少需要一次重复。', 'require_repeat_until' => '仅需重复次数或结束日期 (repeat_until) 即可,不需两者均备。', 'require_currency_info' => '无货币资讯,此栏位内容无效。', + 'require_currency_amount' => 'The content of this field is invalid without foreign amount information.', 'equal_description' => '交易描述不应等同全域描述。', 'file_invalid_mime' => '档案 ":name" 的类型为 ":mime",系不被允许上载的类型。', 'file_too_large' => '档案 ":name" 过大。', @@ -123,7 +127,7 @@ return [ 'in_array' => ':attribute 栏位不存在于 :other。', 'present' => ':attribute 栏位必须存在。', 'amount_zero' => '总金额不能为零。', - 'unique_piggy_bank_for_user' => '小猪存钱罐的名称必须是独一无二的。', + 'unique_piggy_bank_for_user' => '存钱罐的名称必须是独一无二的。', 'secure_password' => '这不是一个安全的密码,请重试一次。如需更多讯息,请访问 https://bit.ly/FF3-password-security', 'valid_recurrence_rep_type' => '对定期重复交易是无效的重复类型。', 'valid_recurrence_rep_moment' => '对此重复类型是无效的重复时刻。', @@ -133,10 +137,10 @@ return [ 'description' => '描述', 'amount' => '金额', 'name' => '名称', - 'piggy_bank_id' => '小猪存钱罐 ID', + 'piggy_bank_id' => '存钱罐 ID', 'targetamount' => '目标金额', - 'openingBalanceDate' => '初始余额日期', - 'openingBalance' => '初始余额', + 'opening_balance_date' => 'opening balance date', + 'opening_balance' => 'opening balance', 'match' => '符合', 'amount_min' => '最小金额', 'amount_max' => '最大金额', @@ -164,4 +168,28 @@ return [ 'rule-trigger.4' => '规则触发器 #4', 'rule-trigger.5' => '规则触发器 #5', ], + + // validation of accounts: + 'withdrawal_source_need_data' => '需要一个有效的来源账户ID和/或来源账户名称才能继续。', + 'withdrawal_source_bad_data' => '搜索 ID":id"或名称":name"时找不到有效的来源帐户。', + 'withdrawal_dest_need_data' => '需要一个有效的目标账户ID和/或目标账户名称才能继续。', + 'withdrawal_dest_bad_data' => 'Could not find a valid destination account when searching for ID ":id" or name ":name".', + + 'deposit_source_need_data' => '需要一个有效的来源账户ID和/或来源账户名称才能继续。', + 'deposit_source_bad_data' => '搜索 ID":id"或名称":name"时找不到有效的来源帐户。', + 'deposit_dest_need_data' => '需要一个有效的目标账户ID和/或目标账户名称才能继续。', + 'deposit_dest_bad_data' => '搜索 ID":id"或名称":name"时找不到有效的目标帐户。', + + 'transfer_source_need_data' => '需要一个有效的来源账户ID和/或来源账户名称才能继续。', + 'transfer_source_bad_data' => '搜索 ID":id"或名称":name"时找不到有效的来源帐户。', + 'transfer_dest_need_data' => '需要一个有效的目标账户ID和/或目标账户名称才能继续。', + 'transfer_dest_bad_data' => '搜索 ID":id"或名称":name"时找不到有效的目标帐户。', + 'need_id_in_edit' => 'Each split must have transaction_journal_id (either valid ID or 0).', + + 'ob_source_need_data' => 'Need to get a valid source account ID and/or valid source account name to continue.', + 'ob_dest_need_data' => 'Need to get a valid destination account ID and/or valid destination account name to continue.', + 'ob_dest_bad_data' => 'Could not find a valid destination account when searching for ID ":id" or name ":name".', + + 'generic_invalid_source' => 'You can\'t use this account as the source account.', + 'generic_invalid_destination' => 'You can\'t use this account as the destination account.', ]; diff --git a/resources/lang/zh_TW/breadcrumbs.php b/resources/lang/zh_TW/breadcrumbs.php index dda1aa80eb..0ef21490fd 100644 --- a/resources/lang/zh_TW/breadcrumbs.php +++ b/resources/lang/zh_TW/breadcrumbs.php @@ -23,35 +23,37 @@ declare(strict_types=1); return [ - 'home' => '首頁', - 'edit_currency' => '編輯貨幣 ":name"', - 'delete_currency' => '刪除貨幣 ":name"', - 'newPiggyBank' => '創建一個新的小豬撲滿', - 'edit_piggyBank' => '編輯小豬撲滿 ":name"', - 'preferences' => '偏好設定', - 'profile' => '個人檔案', - 'changePassword' => '更改您的密碼', - 'change_email' => '更改您的電子郵件地址', - 'bills' => '帳單', - 'newBill' => '新增帳單', - 'edit_bill' => '編輯帳單 ":name"', - 'delete_bill' => '刪除帳單 ":name"', - 'reports' => '報表', - 'search_result' => '":query" 的搜尋結果', - 'withdrawal_list' => '支出', - 'deposit_list' => '收入、所得與存款', - 'transfer_list' => '轉帳', - 'transfers_list' => '轉帳', - 'reconciliation_list' => '對帳', - 'create_withdrawal' => '新增提款', - 'create_deposit' => '新增存款', - 'create_transfer' => '新增轉帳', - 'edit_journal' => '編輯交易 ":description"', - 'edit_reconciliation' => '編輯 ":description"', - 'delete_journal' => '刪除交易 ":description"', - 'tags' => '標籤', - 'createTag' => '建立新標籤', - 'edit_tag' => '編輯標籤 ":tag"', - 'delete_tag' => '刪除標籤 ":tag"', - 'delete_journal_link' => '刪除交易記錄之間的連結', + 'home' => '首頁', + 'edit_currency' => '編輯貨幣 ":name"', + 'delete_currency' => '刪除貨幣 ":name"', + 'newPiggyBank' => '創建一個新的小豬撲滿', + 'edit_piggyBank' => '編輯小豬撲滿 ":name"', + 'preferences' => '偏好設定', + 'profile' => '個人檔案', + 'changePassword' => '更改您的密碼', + 'change_email' => '更改您的電子郵件地址', + 'bills' => '帳單', + 'newBill' => '新增帳單', + 'edit_bill' => '編輯帳單 ":name"', + 'delete_bill' => '刪除帳單 ":name"', + 'reports' => '報表', + 'search_result' => '":query" 的搜尋結果', + 'withdrawal_list' => '支出', + 'Withdrawal_list' => 'Expenses', + 'deposit_list' => '收入、所得與存款', + 'transfer_list' => '轉帳', + 'transfers_list' => '轉帳', + 'reconciliation_list' => '對帳', + 'create_withdrawal' => '新增提款', + 'create_deposit' => '新增存款', + 'create_transfer' => '新增轉帳', + 'create_new_transaction' => '建立新交易', + 'edit_journal' => '編輯交易 ":description"', + 'edit_reconciliation' => '編輯 ":description"', + 'delete_journal' => '刪除交易 ":description"', + 'tags' => '標籤', + 'createTag' => '建立新標籤', + 'edit_tag' => '編輯標籤 ":tag"', + 'delete_tag' => '刪除標籤 ":tag"', + 'delete_journal_link' => '刪除交易記錄之間的連結', ]; diff --git a/resources/lang/zh_TW/firefly.php b/resources/lang/zh_TW/firefly.php index 6417fa3dba..cefcddaf27 100644 --- a/resources/lang/zh_TW/firefly.php +++ b/resources/lang/zh_TW/firefly.php @@ -33,7 +33,7 @@ return [ 'last_seven_days' => '最近7天', 'last_thirty_days' => '最近30天', 'welcomeBack' => '吃飽沒?', - 'welcome_back' => '吃飽沒?', + 'welcome_back' => '吃飽沒?', 'everything' => '所有', 'today' => '今天', 'customRange' => '自訂範圍', @@ -54,6 +54,7 @@ return [ 'create_new_stuff' => '建立新內容', 'new_withdrawal' => '新提款', 'create_new_transaction' => '建立新交易', + 'new_transaction' => '新交易', 'go_to_asset_accounts' => '檢視您的資產帳戶', 'go_to_budgets' => '前往您的預算', 'go_to_categories' => '前往您的分類', @@ -82,28 +83,32 @@ return [ 'help_for_this_page' => '本頁說明', 'no_help_could_be_found' => '找不到說明文本。', 'no_help_title' => '不好意思,發生一個錯誤。', - 'two_factor_welcome' => ':user 您好!', - 'two_factor_enter_code' => '如要繼續,請輸入您的兩步驟驗證 (two factor authentication) 代碼,您的應用程式可為您產生。', - 'two_factor_code_here' => '在此輸入代碼', - 'two_factor_title' => '兩步驟驗證', - 'authenticate' => '驗證', - 'two_factor_forgot_title' => '遺失兩步驟驗證', - 'two_factor_forgot' => '啥兩步驟我忘了。', - 'two_factor_lost_header' => '遺失您的兩步驟驗證嗎?', - 'two_factor_lost_intro' => '可惜,網頁介面並不提供重設。以下方法請二擇其一。', - 'two_factor_lost_fix_self' => '若您自行架構 Firefly III,請查閱 storage/logs 中日誌檔內的指示。', - 'two_factor_lost_fix_owner' => '否則,請寄送電子郵件至網站擁有者 :site_owner 要求重設您的兩步驟驗證。', - 'warning_much_data' => '載入 :days 天的資料或會相當耗時。', - 'registered' => '您已成功註冊!', - 'Default asset account' => '預設資產帳戶', - 'no_budget_pointer' => '您似乎尚無預算,您可至 預算頁面來建立預算。預算可協助您追蹤支出。', - 'Savings account' => '儲蓄帳戶', - 'Credit card' => '信用卡', - 'source_accounts' => '來源帳戶', - 'destination_accounts' => '目標帳戶', - 'user_id_is' => '您的使用者 ID 是 :user', - 'field_supports_markdown' => '此欄位支援 Markdown 語法。', - 'need_more_help' => '如果您需要更多 Firefly III 的協助,請 於 GitHub 建立提問。', + 'two_factor_welcome' => 'Hello!', + 'two_factor_enter_code' => '如要繼續,請輸入您的兩步驟驗證 (two factor authentication) 代碼,您的應用程式可為您產生。', + 'two_factor_code_here' => '在此輸入代碼', + 'two_factor_title' => '兩步驟驗證', + 'authenticate' => '驗證', + 'two_factor_forgot_title' => '遺失兩步驟驗證', + 'two_factor_forgot' => '啥兩步驟我忘了。', + 'two_factor_lost_header' => '遺失您的兩步驟驗證嗎?', + 'two_factor_lost_intro' => 'If you lost your backup codes as well, you have bad luck. This is not something you can fix from the web interface. You have two choices.', + 'two_factor_lost_fix_self' => 'If you run your own instance of Firefly III, check the logs in storage/logs for instructions, or run docker logs <container_id> to see the instructions (refresh this page).', + 'two_factor_lost_fix_owner' => '否則,請寄送電子郵件至網站擁有者 :site_owner 要求重設您的兩步驟驗證。', + 'mfa_backup_code' => 'You have used a backup code to login to Firefly III. It can\'t be used again, so cross it from your list.', + 'pref_two_factor_new_backup_codes' => 'Get new backup codes', + 'pref_two_factor_backup_code_count' => 'You have :count valid backup code(s).', + '2fa_i_have_them' => 'I stored them!', + 'warning_much_data' => '載入 :days 天的資料或會相當耗時。', + 'registered' => '您已成功註冊!', + 'Default asset account' => '預設資產帳戶', + 'no_budget_pointer' => '您似乎尚無預算,您可至 預算頁面來建立預算。預算可協助您追蹤支出。', + 'Savings account' => '儲蓄帳戶', + 'Credit card' => '信用卡', + 'source_accounts' => '來源帳戶', + 'destination_accounts' => '目標帳戶', + 'user_id_is' => '您的使用者 ID 是 :user', + 'field_supports_markdown' => '此欄位支援 Markdown 語法。', + 'need_more_help' => '如果您需要更多 Firefly III 的協助,請 於 GitHub 建立提問。', 'reenable_intro_text' => '您也可以重新啟用 說明指導。', 'intro_boxes_after_refresh' => '當您重新整理頁面後,介紹框將會重新出現。', 'show_all_no_filter' => '不以日期分組,顯示所有交易紀錄', @@ -220,21 +225,23 @@ return [ 'search_query' => '查詢', 'search_found_transactions' => 'Firefly III 找到 :count 筆交易,花了 :time 秒。', 'search_for_query' => 'Firefly III 正搜尋包含所有這些字詞的交易::query', - 'search_modifier_amount_is' => '總數正是 :value', - 'search_modifier_amount' => '總數正是 :value', - 'search_modifier_amount_max' => '總數不多於 :value', - 'search_modifier_amount_min' => '總數不少於 :value', - 'search_modifier_amount_less' => '總數少於 :value', - 'search_modifier_amount_more' => '總數多於 :value', - 'search_modifier_source' => '來源帳戶為 :value', - 'search_modifier_destination' => '目標帳戶為 :value', - 'search_modifier_category' => '分類為 :value', - 'search_modifier_budget' => '預算為 :value', - 'search_modifier_bill' => '帳單為 :value', - 'search_modifier_type' => '交易類型為 :value', - 'search_modifier_date' => '交易日期為 :value', - 'search_modifier_date_before' => '交易日期前於 :value', - 'search_modifier_date_after' => '交易日期後於 :value', + 'search_modifier_amount_is' => '總數正是 :value', + 'search_modifier_amount' => '總數正是 :value', + 'search_modifier_amount_max' => '總數不多於 :value', + 'search_modifier_amount_min' => '總數不少於 :value', + 'search_modifier_amount_less' => '總數少於 :value', + 'search_modifier_amount_more' => '總數多於 :value', + 'search_modifier_source' => '來源帳戶為 :value', + 'search_modifier_from' => 'Source account is :value', + 'search_modifier_destination' => '目標帳戶為 :value', + 'search_modifier_to' => 'Destination account is :value', + 'search_modifier_category' => '分類為 :value', + 'search_modifier_budget' => '預算為 :value', + 'search_modifier_bill' => '帳單為 :value', + 'search_modifier_type' => '交易類型為 :value', + 'search_modifier_date' => '交易日期為 :value', + 'search_modifier_date_before' => '交易日期前於 :value', + 'search_modifier_date_after' => '交易日期後於 :value', 'search_modifier_on' => '交易日期為 :value', 'search_modifier_before' => '交易日期前於 :value', 'search_modifier_after' => '交易日期後於 :value', @@ -257,33 +264,6 @@ return [ 'half-year' => '每半年', 'yearly' => '每年', - // export data: - 'import_and_export' => '匯入/匯出', - 'export_data' => '匯出資料', - 'export_and_backup_data' => '匯出資料', - 'export_data_intro' => '使用匯出資料轉移至新的財務應用程式。請注意這些檔案並非備份,它們未包含足夠的中繼資料在全新的 Firefly III 重新還原。如果您希望備份資料,請直接備份整個資料庫。', - 'export_format' => '匯出格式', - 'export_format_csv' => '逗號分隔數值 (CSV 檔案)', - 'export_format_mt940' => 'MT940 相容格式', - 'include_old_uploads_help' => 'Firefly III 不會扔掉過去已匯入的原始 CSV 檔,你可以將它們包含在匯出的檔案中。', - 'do_export' => '匯出', - 'export_status_never_started' => '匯出尚未開始', - 'export_status_make_exporter' => '建立匯出相關物件…', - 'export_status_collecting_journals' => '蒐集您的交易…', - 'export_status_collected_journals' => '已蒐集您的交易!', - 'export_status_converting_to_export_format' => '轉換您的交易…', - 'export_status_converted_to_export_format' => '已轉換您的交易!', - 'export_status_creating_journal_file' => '建立匯出檔…', - 'export_status_created_journal_file' => '已建立匯出檔!', - 'export_status_collecting_attachments' => '蒐集所有您的附加檔案…', - 'export_status_collected_attachments' => '已蒐集所有您的附加檔案!', - 'export_status_collecting_old_uploads' => '蒐集您過往的上傳…', - 'export_status_collected_old_uploads' => '已蒐集您過往的上傳!', - 'export_status_creating_zip_file' => '建立一個壓縮檔…', - 'export_status_created_zip_file' => '已建立一個壓縮檔!', - 'export_status_finished' => '匯出已成功完成!耶!', - 'export_data_please_wait' => '請稍候…', - // rules 'rules' => '規則', 'rule_name' => '規則名稱', @@ -502,30 +482,33 @@ return [ 'pref_custom_fiscal_year_help' => '有些國家/地區採用的會計年度有別於每年 1 月 1 日至 12 月 31 日,您可開啟此功能並指定財政年度的起迄日。', 'pref_fiscal_year_start_label' => '財政年度開始日期', 'pref_two_factor_auth' => '兩步驟驗證', - 'pref_two_factor_auth_help' => '啟用兩步驟驗證 (亦稱為雙重驗證) 可為您的帳號增添一重安全保障,登入時需憑您腦海的記憶 (密碼) 加上一個手持的憑證 (認證碼),認證碼由手機應用程式產生,例如 Authy 或 Google Authenticator。', - 'pref_enable_two_factor_auth' => '啟用兩步驟驗證', - 'pref_two_factor_auth_disabled' => '兩步驟驗證碼已移除並停用', - 'pref_two_factor_auth_remove_it' => '別忘記在您的驗證應用程式上刪除此帳號!', - 'pref_two_factor_auth_code' => '驗證碼', - 'pref_two_factor_auth_code_help' => '使用您手機上的應用程式 (如 Authy 或 Google Authenticator) 掃描 QR 碼並輸入自動產生之代碼。', - 'pref_two_factor_auth_reset_code' => '重設認證碼', - 'pref_two_factor_auth_disable_2fa' => '停用兩步驟驗證', - '2fa_use_secret_instead' => '如果您無法掃描 QR 碼,請使用密鑰: :secret。', - 'pref_save_settings' => '儲存設定', - 'saved_preferences' => '偏好設定已儲存!', - 'preferences_general' => '一般', - 'preferences_frontpage' => '主畫面', - 'preferences_security' => '安全性', - 'preferences_layout' => '版面配置', - 'pref_home_show_deposits' => '在主畫面顯示存款', - 'pref_home_show_deposits_info' => '主畫面已顯示您的支出帳戶,是否亦顯示您的收入帳戶?', - 'pref_home_do_show_deposits' => '是,要顯示', - 'successful_count' => ':count 項成功', - 'list_page_size_title' => '頁面大小', - 'list_page_size_help' => '任何物品 (帳戶、交易…等) 清單在每頁至多顯示此數量', - 'list_page_size_label' => '頁面大小', - 'between_dates' => '(:start 與 :end)', - 'pref_optional_fields_transaction' => '交易的選填欄位', + 'pref_two_factor_auth_help' => '啟用兩步驟驗證 (亦稱為雙重驗證) 可為您的帳號增添一重安全保障,登入時需憑您腦海的記憶 (密碼) 加上一個手持的憑證 (認證碼),認證碼由手機應用程式產生,例如 Authy 或 Google Authenticator。', + 'pref_enable_two_factor_auth' => '啟用兩步驟驗證', + 'pref_two_factor_auth_disabled' => '兩步驟驗證碼已移除並停用', + 'pref_two_factor_auth_remove_it' => '別忘記在您的驗證應用程式上刪除此帳號!', + 'pref_two_factor_auth_code' => '驗證碼', + 'pref_two_factor_auth_code_help' => '使用您手機上的應用程式 (如 Authy 或 Google Authenticator) 掃描 QR 碼並輸入自動產生之代碼。', + 'pref_two_factor_auth_reset_code' => '重設認證碼', + 'pref_two_factor_auth_disable_2fa' => '停用兩步驟驗證', + '2fa_use_secret_instead' => 'If you cannot scan the QR code, feel free to use the secret instead: :secret.', + '2fa_backup_codes' => 'Store these backup codes for access in case you lose your device.', + '2fa_already_enabled' => '2-step verification is already enabled.', + 'wrong_mfa_code' => 'This MFA code is not valid.', + 'pref_save_settings' => '儲存設定', + 'saved_preferences' => '偏好設定已儲存!', + 'preferences_general' => '一般', + 'preferences_frontpage' => '主畫面', + 'preferences_security' => '安全性', + 'preferences_layout' => '版面配置', + 'pref_home_show_deposits' => '在主畫面顯示存款', + 'pref_home_show_deposits_info' => '主畫面已顯示您的支出帳戶,是否亦顯示您的收入帳戶?', + 'pref_home_do_show_deposits' => '是,要顯示', + 'successful_count' => ':count 項成功', + 'list_page_size_title' => '頁面大小', + 'list_page_size_help' => '任何物品 (帳戶、交易…等) 清單在每頁至多顯示此數量', + 'list_page_size_label' => '頁面大小', + 'between_dates' => '(:start 與 :end)', + 'pref_optional_fields_transaction' => '交易的選填欄位', 'pref_optional_fields_transaction_help' => '建立新交易時,預設不會啟用全部欄位 (以免版面空間不敷應用)。您可在下方啟用您覺得有用的欄位。當然,若欄位本身停用卻已填入資料,則不論設定如何均會顯示。', 'optional_tj_date_fields' => '日期欄位', 'optional_tj_business_fields' => '商務欄位', @@ -579,24 +562,25 @@ return [ 'login_with_new_email' => '現在,您可以使用新的電子郵件地址登入。', 'login_with_old_email' => '現在,您可以再次使用舊的電子郵件地址登入。', 'login_provider_local_only' => '當藉由 ":login_provider" 驗證時,此動作不可用。', - 'delete_local_info_only' => '由於您以 ":login_provider" 驗證,僅會刪除本機 Firefly III 資訊。', + 'delete_local_info_only' => '由於您以 ":login_provider" 驗證,僅會刪除本機 Firefly III 資訊。', // attachments - 'nr_of_attachments' => '̇1 個附加檔案|:count 個附加檔案', - 'attachments' => '附加檔案', - 'edit_attachment' => '編輯附加檔案 ":name"', - 'update_attachment' => '更新附加檔案', - 'delete_attachment' => '刪除附加檔案 ":name"', - 'attachment_deleted' => '已刪除附加檔案 ":name"', - 'attachment_updated' => '已更新附加檔案 ":name', - 'upload_max_file_size' => '最大檔案大小: :size', - 'list_all_attachments' => '全部附加檔案清單', + 'nr_of_attachments' => '̇1 個附加檔案|:count 個附加檔案', + 'attachments' => '附加檔案', + 'edit_attachment' => '編輯附加檔案 ":name"', + 'update_attachment' => '更新附加檔案', + 'delete_attachment' => '刪除附加檔案 ":name"', + 'attachment_deleted' => '已刪除附加檔案 ":name"', + 'liabilities_deleted' => 'Deleted liability ":name"', + 'attachment_updated' => '已更新附加檔案 ":name', + 'upload_max_file_size' => '最大檔案大小: :size', + 'list_all_attachments' => '全部附加檔案清單', // transaction index - 'title_expenses' => '支出', - 'title_withdrawal' => '支出', - 'title_revenue' => '收入', - 'title_deposit' => '收入', + 'title_expenses' => '支出', + 'title_withdrawal' => '支出', + 'title_revenue' => '收入', + 'title_deposit' => '收入', 'title_transfer' => '轉帳', 'title_transfers' => '轉帳', @@ -634,6 +618,9 @@ return [ 'converted_to_Transfer' => '此交易已被轉換為一筆轉帳', 'invalid_convert_selection' => '您選擇的帳戶已用於此交易或不存在', 'source_or_dest_invalid' => '找不到正確的交易細節,無法轉換。', + 'convert_to_withdrawal' => '轉換成提款', + 'convert_to_deposit' => '轉換成存款', + 'convert_to_transfer' => '轉換成轉帳', // create new stuff: 'create_new_withdrawal' => '建立新提款', @@ -695,7 +682,9 @@ return [ 'available_amount_indication' => '使用這些金額以獲得您總預算可能為何的指標', 'suggested' => '建議', 'average_between' => '自 :start 至 :end 的平均', - 'over_budget_warn' => ' 您每日通常預算約 :amount,此為每日 :over_amount。', + 'over_budget_warn' => ' 您通常每日預算 :amount。這回卻是每日 :over_amount。您確定嗎?', + 'transferred_in' => '轉帳 (轉入)', + 'transferred_away' => '轉帳 (轉出)', // bills: 'match_between_amounts' => '帳單配合自 :low 至 :high 的交易。', @@ -779,6 +768,7 @@ return [ 'reconcile_options' => '對帳選項', 'reconcile_range' => '對帳範圍', 'start_reconcile' => '開始對帳', + 'cash_account_type' => 'Cash', 'cash' => '現金', 'account_type' => '帳戶類型', 'save_transactions_by_moving' => '藉由移動至其他帳戶以儲存這些交易', @@ -803,7 +793,9 @@ return [ 'reconcile_go_back' => '您可稍後再編輯或刪除校正。', 'must_be_asset_account' => '您只可以對帳資產帳戶。', 'reconciliation_stored' => '已儲存對帳', - 'reconcilliation_transaction_title' => '對帳 (:from 至 :to)', + 'reconciliation_error' => '發生錯誤,交易已標記為已對帳,惟修正未能儲存: :error。', + 'reconciliation_transaction_title' => '對帳 (:from 至 :to)', + 'sum_of_reconciliation' => '對帳加總', 'reconcile_this_account' => '對帳此帳戶', 'confirm_reconciliation' => '確認對帳', 'submitted_start_balance' => '初始餘額已送出', @@ -815,7 +807,7 @@ return [ 'interest_calc_daily' => '每日', 'interest_calc_monthly' => '每月', 'interest_calc_yearly' => '每年', - 'initial_balance_account' => ':name 帳戶的初始餘額', + 'initial_balance_account' => ':account 初始餘額帳戶', // categories: 'new_category' => '新分類', @@ -847,6 +839,8 @@ return [ 'deleted_deposit' => '已成功刪除存款 “:description”', 'deleted_transfer' => '已成功刪除轉帳 “:description”', 'stored_journal' => '已成功建立新交易 “:description”', + 'stored_journal_no_descr' => '已成功建立新交易', + 'updated_journal_no_descr' => '已成功更新您的交易', 'select_transactions' => '選擇交易', 'rule_group_select_transactions' => '套用 ”:title“ 至交易', 'rule_select_transactions' => '套用 ”:title“ 至交易', @@ -855,26 +849,33 @@ return [ 'mass_delete_journals' => '刪除數個交易', 'mass_edit_journals' => '編輯數個交易', 'mass_bulk_journals' => '批次編輯數個交易', - 'mass_bulk_journals_explain' => '使用大量編輯功能時,若您不想逐一變更您的交易,您可一次過更新所有交易。只要在以下欄位選擇要更新的分類、標籤或預算,表格內所有交易都將會更新。', + 'mass_bulk_journals_explain' => 'This form allows you to change properties of the transactions listed below in one sweeping update. All the transactions in the table will be updated when you change the parameters you see here.', + 'part_of_split' => 'This transaction is part of a split transaction. If you have not selected all the splits, you may end up with changing only half the transaction.', 'bulk_set_new_values' => '在下方的輸入欄位設定新值。若您留空,則全部皆會為空白值。此外,請注意僅有提款會被賦予預算。', 'no_bulk_category' => '不更新分類', 'no_bulk_budget' => '不更新預算', - 'no_bulk_tags' => '不更新標籤', - 'bulk_edit' => '批次編輯', - 'cannot_edit_other_fields' => '受版面空間所限,您僅能大量編輯此處顯示的欄位。若您需要編輯其他欄位,請按一下連結並逐一編輯。', - 'no_budget' => '(無預算)', - 'no_budget_squared' => '(無預算)', - 'perm-delete-many' => '一次刪除多個項目係非常危險的,請審慎考慮。', - 'mass_deleted_transactions_success' => '已刪除 :amount 筆交易。', - 'mass_edited_transactions_success' => '已更新 :amount 筆交易', - 'opt_group_' => '(沒有帳戶類型)', + 'no_bulk_tags' => '不更新標籤', + 'mass_edit' => 'Edit selected individually', + 'bulk_edit' => 'Edit selected in bulk', + 'mass_delete' => 'Delete selected', + 'cannot_edit_other_fields' => '受版面空間所限,您僅能大量編輯此處顯示的欄位。若您需要編輯其他欄位,請按一下連結並逐一編輯。', + 'cannot_change_amount_reconciled' => 'You can\'t change the amount of reconciled transactions.', + 'no_budget' => '(無預算)', + 'no_budget_squared' => '(無預算)', + 'perm-delete-many' => 'Deleting many items in one go can be very disruptive. Please be cautious. You can delete part of a split transaction from this page, so take care.', + 'mass_deleted_transactions_success' => '已刪除 :amount 筆交易。', + 'mass_edited_transactions_success' => '已更新 :amount 筆交易', + 'opt_group_' => '(沒有帳戶類型)', 'opt_group_no_account_type' => '(沒有帳戶類型)', 'opt_group_defaultAsset' => '預設資產帳戶', 'opt_group_savingAsset' => '儲蓄帳戶', 'opt_group_sharedAsset' => '共用資產帳戶', 'opt_group_ccAsset' => '信用卡', 'opt_group_cashWalletAsset' => '現金皮夾', + 'opt_group_expense_account' => 'Expense accounts', + 'opt_group_revenue_account' => 'Revenue accounts', 'opt_group_l_Loan' => '債務: 貸款', + 'opt_group_cash_account' => 'Cash account', 'opt_group_l_Debt' => '債務: 欠款', 'opt_group_l_Mortgage' => '債務: 抵押', 'opt_group_l_Credit card' => '債務: 信用卡', @@ -973,7 +974,7 @@ return [ 'errors' => '錯誤', 'debt_start_date' => '負債開始日期', 'debt_start_amount' => '負債開始金額', - 'debt_start_amount_help' => '請以正數輸入此債務的原始金額,您也可以輸入目前的金額。請確定修改下方的日期以符合需求。', + 'debt_start_amount_help' => '如欠債未還,建議您輸入負數,以便在您的資產淨值反映。如有別人的欠款,反之亦然。請參見說明頁面中的詳解。', 'store_new_liabilities_account' => '儲存新債務', 'edit_liabilities_account' => '編輯債務 “:name”', @@ -1145,6 +1146,7 @@ return [ 'deleted_piggy_bank' => '删除小豬撲滿 ":name"', 'added_amount_to_piggy' => '已新增 :amount 至 “:name”', 'removed_amount_from_piggy' => '已自 “:name” 移除 :amount', + 'piggy_events' => '相關的小豬撲滿', // tags 'delete_tag' => '刪除標籤 ":tag"', @@ -1154,47 +1156,57 @@ return [ 'updated_tag' => '已更新標籤 “:tag”', 'created_tag' => '標籤 “:tag” 已被建立!', - 'transaction_journal_information' => '交易資訊', - 'transaction_journal_meta' => '後設資訊', - 'total_amount' => '總金額', - 'number_of_decimals' => '小數位數:', + 'transaction_journal_information' => '交易資訊', + 'transaction_journal_meta' => '後設資訊', + 'transaction_journal_more' => '更多資訊', + 'att_part_of_journal' => '儲存在 ":journal"', + 'total_amount' => '總金額', + 'number_of_decimals' => '小數位數:', // administration - 'administration' => '管理', - 'user_administration' => '使用者管理', - 'list_all_users' => '所有使用者', - 'all_users' => '所有使用者', - 'instance_configuration' => '組態設定', - 'firefly_instance_configuration' => 'Firefly III 的組態設定選項', - 'setting_single_user_mode' => '單使用者模式', - 'setting_single_user_mode_explain' => '預設下,Firefly III 只接受一個 (1) 註冊,即您本人。此為安全措施,在除經你同意外,防止其他人使用您的空間,目前註冊功能已關閉。當您取消此核選方塊後,若他們能連接到此空間 (在已聯網狀態),其他人也可以使用。', - 'store_configuration' => '儲存設定', - 'single_user_administration' => ':email 的使用者管理後臺', - 'edit_user' => '編輯使用者 :email', - 'hidden_fields_preferences' => '目前並非所有欄位都是可見的,您必須在 設定 啟用它們。', - 'user_data_information' => '使用者資料', - 'user_information' => '使用者資訊', - 'total_size' => '總大小', - 'budget_or_budgets' => '預算', - 'budgets_with_limits' => '已設定金額的預算', - 'nr_of_rules_in_total_groups' => ':count_groups规则组中,包含:count_rules条规则', - 'tag_or_tags' => '標籤', - 'configuration_updated' => '已更新組態設定', - 'setting_is_demo_site' => '演示網站', - 'setting_is_demo_site_explain' => '若您勾選此選項,此安裝將會以展示網站方式運作,會有奇怪的副作用。', - 'block_code_bounced' => '電子郵件被退回', - 'block_code_expired' => '演示帳戶已過期', - 'no_block_code' => '無封鎖原因或使用者未被封鎖', - 'block_code_email_changed' => '使用者尚未確認新的電子郵件地址', - 'admin_update_email' => '與個人資料頁面相反,使用者不會被通知他們的電子郵件地址已變更!', - 'update_user' => '更新使用者', - 'updated_user' => '使用者資料已更改。', - 'delete_user' => '刪除使用者 :email', - 'user_deleted' => '使用者已被刪除', - 'send_test_email' => '寄送測試郵件訊息', - 'send_test_email_text' => '要檢查您的安裝是否有能力發送電子郵件,請按此按鈕。您不會在此看到錯誤 (如果有的話),日誌檔才會反應一切錯誤。您可依照自己意願點選此按鈕,程式無管控垃圾郵件,測試訊息將會被寄發至 :email 並在短時間內送達。', - 'send_message' => '發送消息', - 'send_test_triggered' => '測試已觸發,請檢視您的收件匣與日誌檔。', + 'administration' => '管理', + 'user_administration' => '使用者管理', + 'list_all_users' => '所有使用者', + 'all_users' => '所有使用者', + 'instance_configuration' => '組態設定', + 'firefly_instance_configuration' => 'Firefly III 的組態設定選項', + 'setting_single_user_mode' => '單使用者模式', + 'setting_single_user_mode_explain' => '預設下,Firefly III 只接受一個 (1) 註冊,即您本人。此為安全措施,在除經你同意外,防止其他人使用您的空間,目前註冊功能已關閉。當您取消此核選方塊後,若他們能連接到此空間 (在已聯網狀態),其他人也可以使用。', + 'store_configuration' => '儲存設定', + 'single_user_administration' => ':email 的使用者管理後臺', + 'edit_user' => '編輯使用者 :email', + 'hidden_fields_preferences' => '您可在 設定 啟用更多交易選項。', + 'user_data_information' => '使用者資料', + 'user_information' => '使用者資訊', + 'total_size' => '總大小', + 'budget_or_budgets' => '預算', + 'budgets_with_limits' => '已設定金額的預算', + 'nr_of_rules_in_total_groups' => ':count_groups规则组中,包含:count_rules条规则', + 'tag_or_tags' => '標籤', + 'configuration_updated' => '已更新組態設定', + 'setting_is_demo_site' => '演示網站', + 'setting_is_demo_site_explain' => '若您勾選此選項,此安裝將會以展示網站方式運作,會有奇怪的副作用。', + 'block_code_bounced' => '電子郵件被退回', + 'block_code_expired' => '演示帳戶已過期', + 'no_block_code' => '無封鎖原因或使用者未被封鎖', + 'block_code_email_changed' => '使用者尚未確認新的電子郵件地址', + 'admin_update_email' => '與個人資料頁面相反,使用者不會被通知他們的電子郵件地址已變更!', + 'update_user' => '更新使用者', + 'updated_user' => '使用者資料已更改。', + 'delete_user' => '刪除使用者 :email', + 'user_deleted' => '使用者已被刪除', + 'send_test_email' => '寄送測試郵件訊息', + 'send_test_email_text' => '要檢查您的安裝是否有能力發送電子郵件,請按此按鈕。您不會在此看到錯誤 (如果有的話),日誌檔才會反應一切錯誤。您可依照自己意願點選此按鈕,程式無管控垃圾郵件,測試訊息將會被寄發至 :email 並在短時間內送達。', + 'send_message' => '發送消息', + 'send_test_triggered' => '測試已觸發,請檢視您的收件匣與日誌檔。', + + 'split_transaction_title' => '拆分交易的描述', + 'split_title_help' => '若您建立一筆拆分交易,須有一個有關交易所有拆分的整體描述。', + 'transaction_information' => '交易資訊', + 'you_create_transfer' => '您正在建立一筆 轉帳。', + 'you_create_withdrawal' => '您正在建立一筆 提款。', + 'you_create_deposit' => '您正在建立一筆 存款。', + // links 'journal_link_configuration' => '交易鏈結設定', @@ -1214,12 +1226,13 @@ return [ 'do_not_save_connection' => '(不保存連接)', 'link_transaction' => '連結交易', 'link_to_other_transaction' => '鏈結此交易至另一筆', - 'select_transaction_to_link' => '选择一笔交易关联到这笔交易', + 'select_transaction_to_link' => 'Select a transaction to link this transaction to. The links are currently unused in Firefly III (apart from being shown), but I plan to change this in the future. Use the search box to select a transaction either by title or by ID. If you want to add custom link types, check out the administration section.', 'this_transaction' => '这笔交易', 'transaction' => '交易', 'comments' => '評論', - 'to_link_not_found' => '若您想要鏈結的交易不在清單上,只需要輸入它的 ID 即可。', + 'link_notes' => 'Any notes you wish to store with the link.', 'invalid_link_selection' => '無法連結這些交易', + 'selected_transaction' => 'Selected transaction', 'journals_linked' => '交易是連結的。', 'journals_error_linked' => '這些交易已互相鏈結', 'journals_link_to_self' => '您無法將一筆交易鏈結至該交易本身', @@ -1258,12 +1271,11 @@ return [ 'split_this_withdrawal' => '拆分這筆提款', 'split_this_deposit' => '拆分這筆存款', 'split_this_transfer' => '拆分這筆轉帳', - 'cannot_edit_multiple_source' => '您不能使用描述 ":description" 來編輯已拆分的交易 #:id,因為該交易包含數筆來源帳戶。', - 'cannot_edit_multiple_dest' => '您不能使用描述 ":description" 來編輯已拆分的交易 #:id,因為該交易包含數筆目標帳戶。', - 'cannot_edit_reconciled' => '您無法編輯編號 #:id 、簡介為 “:description” 的交易,因為它已被標記為已對帳。', 'cannot_edit_opening_balance' => '您無法編輯一個帳戶的開戶餘額。', 'no_edit_multiple_left' => '您沒有選擇有效的交易紀錄以供編輯。', - 'cannot_convert_split_journal' => '無法轉換拆分交易記錄', + 'breadcrumb_convert_group' => 'Convert transaction', + 'convert_invalid_source' => 'Source information is invalid for transaction #%d.', + 'convert_invalid_destination' => 'Destination information is invalid for transaction #%d.', // Import page (general strings only) 'import_index_title' => '將交易記錄導入', @@ -1389,4 +1401,15 @@ return [ 'will_jump_monday' => '將於週一建立交易,而非週末。', 'except_weekends' => '例外的周末', 'recurrence_deleted' => '週期性交易 ":title" 已刪除', + + // new lines for summary controller. + 'box_balance_in_currency' => '餘額 (:currency)', + 'box_spent_in_currency' => '已花費 (:currency)', + 'box_earned_in_currency' => '盈利 (:currency)', + 'box_bill_paid_in_currency' => '已付帳單 (:currency)', + 'box_bill_unpaid_in_currency' => '未付帳單 (:currency)', + 'box_left_to_spend_in_currency' => '可供花費 (:currency)', + 'box_net_worth_in_currency' => '淨值 (:currency)', + 'box_spend_per_day' => '每日可供花費: :amount', + ]; diff --git a/resources/lang/zh_TW/form.php b/resources/lang/zh_TW/form.php index ac5dcc09d8..113d8965fe 100644 --- a/resources/lang/zh_TW/form.php +++ b/resources/lang/zh_TW/form.php @@ -57,20 +57,21 @@ return [ 'asset_source_account' => '來源帳戶', 'journal_description' => '說明', 'note' => '備註', + 'store_new_transaction' => '儲存新交易', 'split_journal' => '分割此交易', 'split_journal_explanation' => '分割這個交易為幾個部分', 'currency' => '貨幣', 'account_id' => '資產帳戶', 'budget_id' => '預算', - 'openingBalance' => '初始餘額', + 'opening_balance' => '初始餘額', 'tagMode' => '標籤模式', 'tag_position' => '標籤位置', - 'virtualBalance' => '虛擬餘額', + 'virtual_balance' => '虛擬餘額', 'targetamount' => '目標金額', - 'accountRole' => '帳戶角色', - 'openingBalanceDate' => '初始餘額日期', - 'ccType' => '信用卡付款計畫', - 'ccMonthlyPaymentDate' => '信用卡每月付款日期', + 'account_role' => '帳戶角色', + 'opening_balance_date' => '初始餘額日期', + 'cc_type' => '信用卡付款計劃', + 'cc_monthly_payment_date' => '信用卡每月付款日期', 'piggy_bank_id' => '小豬撲滿', 'returnHere' => '返回此處', 'returnHereExplanation' => '儲存後,返回這裡建立另一筆記錄。', @@ -118,7 +119,7 @@ return [ 'symbol' => '符號', 'code' => '代碼', 'iban' => '國際銀行帳戶號碼 (IBAN)', - 'accountNumber' => '帳戶號碼', + 'account_number' => '帳戶號碼', 'creditCardNumber' => '信用卡卡號', 'has_headers' => '標題', 'date_format' => '日期格式', @@ -139,12 +140,8 @@ return [ 'stop_processing' => '停止處理', 'start_date' => '範圍起點', 'end_date' => '範圍終點', - 'export_start_range' => '輸出範圍起點', - 'export_end_range' => '輸出範圍終點', - 'export_format' => '檔案格式', 'include_attachments' => '包括上傳的附件', 'include_old_uploads' => '包含匯入的資料', - 'accounts' => '從這些帳戶匯出交易記錄', 'delete_account' => '刪除帳戶 ":name"', 'delete_bill' => '刪除帳單 ":name"', 'delete_budget' => '刪除預算 ":name"', @@ -256,4 +253,7 @@ return [ 'weekend' => '週末', 'client_secret' => '客戶端金鑰', + 'withdrawal_destination_id' => 'Destination account', + 'deposit_source_id' => 'Source account', + ]; diff --git a/resources/lang/zh_TW/import.php b/resources/lang/zh_TW/import.php index 438e7143c8..70f18aa450 100644 --- a/resources/lang/zh_TW/import.php +++ b/resources/lang/zh_TW/import.php @@ -207,6 +207,10 @@ return [ 'specific_rabo_descr' => '修正 Rabobank 檔案中的潛在問題', 'specific_pres_name' => 'President\'s Choice Financial CA', 'specific_pres_descr' => '修正 PC 檔案中的潛在問題', + 'specific_belfius_name' => 'Belfius BE', + 'specific_belfius_descr' => '修正 Belfius 檔案中的潛在問題', + 'specific_ingbelgium_name' => 'ING BE', + 'specific_ingbelgium_descr' => 'Fixes potential problems with ING Belgium files', // job configuration for file provider (stage: roles) 'job_config_roles_title' => '匯入設定 (3/4) - 定義每個欄的角色', 'job_config_roles_text' => '在您 CSV 檔案中的每個欄均含某些資料,請說明係核種資料供匯入器參照。用以「映射」資料的選項,即您將連結每個欄中的資料至您資料庫的一個值。一個常見的「已映射」的欄,是包含 IBAN 相對帳戶的欄,這便可輕易地媒合至您資料庫既存的 IBAN 帳戶。', @@ -291,14 +295,14 @@ return [ 'column_rabo-debit-credit' => 'Rabobank 獨有現金/信用卡指標', 'column_ing-debit-credit' => 'ING 獨有 現金/信用卡 指標', 'column_generic-debit-credit' => '通用銀行債務/信用指標', - 'column_sepa-ct-id' => 'SEPA end-to-end Identifier', - 'column_sepa-ct-op' => 'SEPA Opposing Account Identifier', - 'column_sepa-db' => 'SEPA Mandate Identifier', - 'column_sepa-cc' => 'SEPA Clearing Code', - 'column_sepa-ci' => 'SEPA Creditor Identifier', - 'column_sepa-ep' => 'SEPA External Purpose', - 'column_sepa-country' => 'SEPA Country', - 'column_sepa-batch-id' => 'SEPA Batch ID', + 'column_sepa_ct_id' => 'SEPA end-to-end Identifier', + 'column_sepa_ct_op' => 'SEPA Opposing Account Identifier', + 'column_sepa_db' => 'SEPA Mandate Identifier', + 'column_sepa_cc' => 'SEPA Clearing Code', + 'column_sepa_ci' => 'SEPA Creditor Identifier', + 'column_sepa_ep' => 'SEPA External Purpose', + 'column_sepa_country' => 'SEPA Country Code', + 'column_sepa_batch_id' => 'SEPA Batch ID', 'column_tags-comma' => '標籤 (以逗號分隔)', 'column_tags-space' => '標籤 (以空白鍵分隔)', 'column_account-number' => '資產帳戶 (帳戶號碼)', @@ -306,4 +310,7 @@ return [ 'column_note' => '備註', 'column_internal-reference' => '內部參照', + // error message + 'duplicate_row' => 'Row #:row (":description") could not be imported. It already exists.', + ]; diff --git a/resources/lang/zh_TW/intro.php b/resources/lang/zh_TW/intro.php index 6471977417..ce10d1ee4e 100644 --- a/resources/lang/zh_TW/intro.php +++ b/resources/lang/zh_TW/intro.php @@ -24,40 +24,62 @@ declare(strict_types=1); return [ // index - 'index_intro' => '歡迎來到 Firefly III 首頁。誠邀您花一點時間,看看這介紹,瞭解一下 Firefly III 如何運作。', - 'index_accounts-chart' => '此圖表顯示資產帳戶的目前餘額,哪些帳戶在此顯示,可在偏好設定中選擇。', - 'index_box_out_holder' => '這小方塊 (以及旁邊的) 給您一個財務狀況的快速概覽。', - 'index_help' => '如果您需要有關頁面或表單的説明,請按此按鈕。', - 'index_outro' => 'Firefly III 大部分頁面會以這樣的小介紹開始,如有問題或意見,不妨與我聯繫。祝您使用得心應手!', - 'index_sidebar-toggle' => '若要建立新的交易記錄、帳戶或其他內容,請使用此圖示下的選單。', + 'index_intro' => '歡迎來到 Firefly III 首頁。誠邀您花一點時間,看看這介紹,瞭解一下 Firefly III 如何運作。', + 'index_accounts-chart' => '此圖表顯示資產帳戶的目前餘額,哪些帳戶在此顯示,可在偏好設定中選擇。', + 'index_box_out_holder' => '這小方塊 (以及旁邊的) 給您一個財務狀況的快速概覽。', + 'index_help' => '如果您需要有關頁面或表單的説明,請按此按鈕。', + 'index_outro' => 'Firefly III 大部分頁面會以這樣的小介紹開始,如有問題或意見,不妨與我聯繫。祝您使用得心應手!', + 'index_sidebar-toggle' => '若要建立新的交易記錄、帳戶或其他內容,請使用此圖示下的選單。', + 'index_cash_account' => 'These are the accounts created so far. You can use the cash account to track cash expenses but it\'s not mandatory of course.', + + // transactions (withdrawal) + 'transactions_create_withdrawal_source' => 'Select your favorite asset account or liability from this dropdown.', + 'transactions_create_withdrawal_destination' => 'Select an expense account here. Leave it empty if you want to make a cash expense.', + 'transactions_create_withdrawal_foreign_currency' => 'Use this field to set a foreign currency and amount.', + 'transactions_create_withdrawal_more_meta' => 'Plenty of other meta data you set in these fields.', + 'transactions_create_withdrawal_split_add' => 'If you want to split a transaction, add more splits with this button', + + // transactions (deposit) + 'transactions_create_deposit_source' => 'Select or type the payee in this auto-completing dropdown/textbox. Leave it empty if you want to make a cash deposit.', + 'transactions_create_deposit_destination' => 'Select an asset or liability account here.', + 'transactions_create_deposit_foreign_currency' => 'Use this field to set a foreign currency and amount.', + 'transactions_create_deposit_more_meta' => 'Plenty of other meta data you set in these fields.', + 'transactions_create_deposit_split_add' => 'If you want to split a transaction, add more splits with this button', + + // transactions (transfer) + 'transactions_create_transfer_source' => 'Select the source asset account here.', + 'transactions_create_transfer_destination' => 'Select the destination asset account here.', + 'transactions_create_transfer_foreign_currency' => 'Use this field to set a foreign currency and amount.', + 'transactions_create_transfer_more_meta' => 'Plenty of other meta data you set in these fields.', + 'transactions_create_transfer_split_add' => 'If you want to split a transaction, add more splits with this button', // create account: - 'accounts_create_iban' => '帳戶若設定有效的 IBAN,有助日後匯入資料。', - 'accounts_create_asset_opening_balance' => '資產帳戶可以設定一個 "初始餘額",表示此帳戶在 Firefly III 中開始時的紀錄。', - 'accounts_create_asset_currency' => 'Fireflly III 支援多種貨幣。資產帳戶有一種主要貨幣,須在此設定。', - 'accounts_create_asset_virtual' => '有時,您或會想給予帳戶一個虛擬額度:即在實際餘額之上加減一個定額。', + 'accounts_create_iban' => '帳戶若設定有效的 IBAN,有助日後匯入資料。', + 'accounts_create_asset_opening_balance' => '資產帳戶可以設定一個 "初始餘額",表示此帳戶在 Firefly III 中開始時的紀錄。', + 'accounts_create_asset_currency' => 'Fireflly III 支援多種貨幣。資產帳戶有一種主要貨幣,須在此設定。', + 'accounts_create_asset_virtual' => '有時,您或會想給予帳戶一個虛擬額度:即在實際餘額之上加減一個定額。', // budgets index - 'budgets_index_intro' => '預算用來管理您的財務,是 Firefly III 的 + 'budgets_index_intro' => '預算用來管理您的財務,是 Firefly III 的 核心功能之一。', - 'budgets_index_set_budget' => '設定每個時期的總預算,讓 Firefly III 能告訴您,所有可用的錢是否都已設定預算。', - 'budgets_index_see_expenses_bar' => '消費金額會慢慢地填滿這個橫條。', - 'budgets_index_navigate_periods' => '往前後不同時期,輕鬆預先設定預算。', - 'budgets_index_new_budget' => '隨意建立新預算。', - 'budgets_index_list_of_budgets' => '在此表上設定每個預算的金額,查看表現如何。', - 'budgets_index_outro' => '要瞭解有關預算的詳細資訊,請查看右上角的説明圖示。', + 'budgets_index_set_budget' => '設定每個時期的總預算,讓 Firefly III 能告訴您,所有可用的錢是否都已設定預算。', + 'budgets_index_see_expenses_bar' => '消費金額會慢慢地填滿這個橫條。', + 'budgets_index_navigate_periods' => '往前後不同時期,輕鬆預先設定預算。', + 'budgets_index_new_budget' => '隨意建立新預算。', + 'budgets_index_list_of_budgets' => '在此表上設定每個預算的金額,查看表現如何。', + 'budgets_index_outro' => '要瞭解有關預算的詳細資訊,請查看右上角的説明圖示。', // reports (index) - 'reports_index_intro' => '從這些報表洞察您的詳細財務狀況。', - 'reports_index_inputReportType' => '挑一種報表。查看說明頁面瞭解各報表展示的內容。', - 'reports_index_inputAccountsSelect' => '您可以根據需要排除或包括資產帳戶。', - 'reports_index_inputDateRange' => '所選日期範圍完全由您決定:從1天到10年不等。', - 'reports_index_extra-options-box' => '根據您選擇的報表,您可以在此處選擇額外的篩選標準和選項。更改報表類型時,請查看此區塊。', + 'reports_index_intro' => '從這些報表洞察您的詳細財務狀況。', + 'reports_index_inputReportType' => '挑一種報表。查看說明頁面瞭解各報表展示的內容。', + 'reports_index_inputAccountsSelect' => '您可以根據需要排除或包括資產帳戶。', + 'reports_index_inputDateRange' => '所選日期範圍完全由您決定:從1天到10年不等。', + 'reports_index_extra-options-box' => '根據您選擇的報表,您可以在此處選擇額外的篩選標準和選項。更改報表類型時,請查看此區塊。', // reports (reports) - 'reports_report_default_intro' => '這份報表可快速全面概覽您的個人財務狀況。如有未羅列的項目,歡迎與我聯繫!', - 'reports_report_audit_intro' => '這份報表可讓您洞悉資產帳戶的詳細狀況。', - 'reports_report_audit_optionsBox' => '在這些選取方塊勾選您感興趣想顯示的欄。', + 'reports_report_default_intro' => '這份報表可快速全面概覽您的個人財務狀況。如有未羅列的項目,歡迎與我聯繫!', + 'reports_report_audit_intro' => '這份報表可讓您洞悉資產帳戶的詳細狀況。', + 'reports_report_audit_optionsBox' => '在這些選取方塊勾選您感興趣想顯示的欄。', 'reports_report_category_intro' => '此報表可讓您洞悉一或多個分類的詳細狀況。', 'reports_report_category_pieCharts' => '這些圖表可讓您洞悉按分類或帳戶的收支詳細狀況。', diff --git a/resources/lang/zh_TW/list.php b/resources/lang/zh_TW/list.php index b0fb6184da..1fc033500e 100644 --- a/resources/lang/zh_TW/list.php +++ b/resources/lang/zh_TW/list.php @@ -23,71 +23,71 @@ declare(strict_types=1); return [ - 'buttons' => '按鈕', - 'icon' => '圖示', - 'id' => 'ID', - 'create_date' => '建立於', - 'update_date' => '更新於', - 'updated_at' => '更新於', - 'balance_before' => '交易前餘額', - 'balance_after' => '交易後餘額', - 'name' => '名稱', - 'role' => '角色', - 'currentBalance' => '目前餘額', - 'linked_to_rules' => '相關規則', - 'active' => '是否啟用?', - 'lastActivity' => '上次活動', - 'balanceDiff' => '餘額差', - 'matchesOn' => '配對於', - 'account_type' => '帳戶類型', - 'created_at' => '建立於', - 'account' => '帳戶', - 'matchingAmount' => '金額', - 'split_number' => '分割編號 #', - 'destination' => '目標', - 'source' => '來源', - 'next_expected_match' => '下一個預期的配對', - 'automatch' => '自動配對?', - 'repeat_freq' => '重複', - 'description' => '描述', - 'amount' => '金額', - 'internal_reference' => '內部參照', - 'date' => '日期', - 'interest_date' => '利率日期', - 'book_date' => '登記日期', - 'process_date' => '處理日期', - 'due_date' => '到期日', - 'payment_date' => '付款日期', - 'invoice_date' => '發票日期', - 'interal_reference' => '內部參照', - 'notes' => '備註', - 'from' => '自', - 'piggy_bank' => '小豬撲滿', - 'to' => '至', - 'budget' => '預算', - 'category' => '分類', - 'bill' => '帳單', - 'withdrawal' => '提款', - 'deposit' => '存款', - 'transfer' => '轉帳', - 'type' => '類型', - 'completed' => '已完成', - 'iban' => '國際銀行帳戶號碼 (IBAN)', - 'paid_current_period' => '已付此區間', - 'email' => '電子郵件', - 'registered_at' => '註冊於', - 'is_blocked' => '被封鎖', - 'is_admin' => '是管理員', - 'has_two_factor' => '有兩步驟驗證 (2FA)', - 'blocked_code' => '封鎖代碼', - 'source_account' => '來源帳戶', + 'buttons' => '按鈕', + 'icon' => '圖示', + 'id' => 'ID', + 'create_date' => '建立於', + 'update_date' => '更新於', + 'updated_at' => '更新於', + 'balance_before' => '交易前餘額', + 'balance_after' => '交易後餘額', + 'name' => '名稱', + 'role' => '角色', + 'currentBalance' => '目前餘額', + 'linked_to_rules' => '相關規則', + 'active' => '是否啟用?', + 'transaction_type' => 'Type', + 'lastActivity' => '上次活動', + 'balanceDiff' => '餘額差', + 'matchesOn' => '配對於', + 'account_type' => '帳戶類型', + 'created_at' => '建立於', + 'account' => '帳戶', + 'matchingAmount' => '金額', + 'split_number' => '分割編號 #', + 'destination' => '目標', + 'source' => '來源', + 'next_expected_match' => '下一個預期的配對', + 'automatch' => '自動配對?', + 'repeat_freq' => '重複', + 'description' => '描述', + 'amount' => '金額', + 'internal_reference' => '內部參照', + 'date' => '日期', + 'interest_date' => '利率日期', + 'book_date' => '登記日期', + 'process_date' => '處理日期', + 'due_date' => '到期日', + 'payment_date' => '付款日期', + 'invoice_date' => '發票日期', + 'interal_reference' => '內部參照', + 'notes' => '備註', + 'from' => '自', + 'piggy_bank' => '小豬撲滿', + 'to' => '至', + 'budget' => '預算', + 'category' => '分類', + 'bill' => '帳單', + 'withdrawal' => '提款', + 'deposit' => '存款', + 'transfer' => '轉帳', + 'type' => '類型', + 'completed' => '已完成', + 'iban' => '國際銀行帳戶號碼 (IBAN)', + 'paid_current_period' => '已付此區間', + 'email' => '電子郵件', + 'registered_at' => '註冊於', + 'is_blocked' => '被封鎖', + 'is_admin' => '是管理員', + 'has_two_factor' => '有兩步驟驗證 (2FA)', + 'blocked_code' => '封鎖代碼', + 'source_account' => '來源帳戶', 'destination_account' => '目標帳戶', 'accounts_count' => '帳戶數量', 'journals_count' => '交易數量', 'attachments_count' => '附加檔案數量', 'bills_count' => '帳單數量', 'categories_count' => '分類數量', - 'export_jobs_count' => '匯出工作數量', 'import_jobs_count' => '匯入工作數量', 'budget_count' => '預算數量', 'rule_and_groups_count' => '規則及規則群組數量', @@ -106,14 +106,14 @@ return [ 'account_on_spectre' => '帳戶 (Spectre)', 'account_on_ynab' => '帳戶 (YNAB)', 'do_import' => '自此帳戶匯入', - 'sepa-ct-id' => 'SEPA End to End Identifier', - 'sepa-ct-op' => 'SEPA Opposing Account Identifier', - 'sepa-db' => 'SEPA Mandate Identifier', - 'sepa-country' => 'SEPA Country', - 'sepa-cc' => 'SEPA Clearing Code', - 'sepa-ep' => 'SEPA External Purpose', - 'sepa-ci' => 'SEPA Creditor Identifier', - 'sepa-batch-id' => 'SEPA Batch ID', + 'sepa_ct_id' => 'SEPA End to End Identifier', + 'sepa_ct_op' => 'SEPA Opposing Account Identifier', + 'sepa_db' => 'SEPA Mandate Identifier', + 'sepa_country' => 'SEPA Country', + 'sepa_cc' => 'SEPA Clearing Code', + 'sepa_ep' => 'SEPA External Purpose', + 'sepa_ci' => 'SEPA Creditor Identifier', + 'sepa_batch_id' => 'SEPA Batch ID', 'external_id' => '外部 ID', 'account_at_bunq' => 'bunq 帳戶', 'file_name' => '檔案名稱', diff --git a/resources/lang/zh_TW/validation.php b/resources/lang/zh_TW/validation.php index 7b73c4508e..a7d3ec7a36 100644 --- a/resources/lang/zh_TW/validation.php +++ b/resources/lang/zh_TW/validation.php @@ -36,12 +36,16 @@ return [ 'file_attached' => '已成功上傳檔案 ":name"。', 'must_exist' => '欄位 :attribute 的 ID 不存在於資料庫。', 'all_accounts_equal' => '此欄位中的所有帳戶必須相等。', + 'group_title_mandatory' => '多於一筆交易時,須要群組標題。', + 'transaction_types_equal' => '所有拆分須為同一類型。', + 'invalid_transaction_type' => '交易類型無效。', 'invalid_selection' => '您的選擇無效。', 'belongs_user' => '此欄位不接受此值。', 'at_least_one_transaction' => '至少需要一個交易。', 'at_least_one_repetition' => '至少需要一次重複。', 'require_repeat_until' => '要嘛重複次數,要嘛結束日期 (repeat_until),須二擇其一。', 'require_currency_info' => '此欄位內容須要貨幣資訊。', + 'require_currency_amount' => '此欄位內容須要外幣資訊。', 'equal_description' => '交易描述不應等同全域描述。', 'file_invalid_mime' => '檔案 ":name" 類型為 ":mime",不允許上載。', 'file_too_large' => '檔案 ":name" 過大。', @@ -135,8 +139,8 @@ return [ 'name' => '名稱', 'piggy_bank_id' => '小豬撲滿 ID', 'targetamount' => '目標金額', - 'openingBalanceDate' => '初始餘額日期', - 'openingBalance' => '初始餘額', + 'opening_balance_date' => '初始餘額日期', + 'opening_balance' => '初始餘額', 'match' => '符合', 'amount_min' => '最小金額', 'amount_max' => '最大金額', @@ -164,4 +168,28 @@ return [ 'rule-trigger.4' => '規則觸發器 #4', 'rule-trigger.5' => '規則觸發器 #5', ], + + // validation of accounts: + 'withdrawal_source_need_data' => '需要有效的來源帳戶 ID 及/或有效的來源帳戶名稱才能繼續。', + 'withdrawal_source_bad_data' => '搜尋 ID ":id" 或名稱 ":name" 都找不到有效的來源帳戶。', + 'withdrawal_dest_need_data' => '需要有效的目標帳戶 ID 及/或有效的目標帳戶名稱才能繼續。', + 'withdrawal_dest_bad_data' => '搜尋 ID ":id" 或名稱 ":name" 都找不到有效的目標帳戶。', + + 'deposit_source_need_data' => '需要有效的來源帳戶 ID 及/或有效的來源帳戶名稱才能繼續。', + 'deposit_source_bad_data' => '搜尋 ID ":id" 或名稱 ":name" 都找不到有效的來源帳戶。', + 'deposit_dest_need_data' => '需要有效的目標帳戶 ID 及/或有效的目標帳戶名稱才能繼續。', + 'deposit_dest_bad_data' => '搜尋 ID ":id" 或名稱 ":name" 都找不到有效的目標帳戶。', + + 'transfer_source_need_data' => '需要有效的來源帳戶 ID 及/或有效的來源帳戶名稱才能繼續。', + 'transfer_source_bad_data' => '搜尋 ID ":id" 或名稱 ":name" 都找不到有效的來源帳戶。', + 'transfer_dest_need_data' => '需要有效的目標帳戶 ID 及/或有效的目標帳戶名稱才能繼續。', + 'transfer_dest_bad_data' => '搜尋 ID ":id" 或名稱 ":name" 都找不到有效的目標帳戶。', + 'need_id_in_edit' => '每筆拆分須有 transaction_journal_id (為有效的 ID 或是 0)。', + + 'ob_source_need_data' => '需要有效的來源帳戶 ID 及/或有效的來源帳戶名稱才能繼續。', + 'ob_dest_need_data' => '需要有效的目標帳戶 ID 及/或有效的目標帳戶名稱才能繼續。', + 'ob_dest_bad_data' => '搜尋 ID ":id" 或名稱 ":name" 都找不到有效的目標帳戶。', + + 'generic_invalid_source' => 'You can\'t use this account as the source account.', + 'generic_invalid_destination' => 'You can\'t use this account as the destination account.', ]; diff --git a/resources/views/v1/accounts/create.twig b/resources/views/v1/accounts/create.twig index b691ea9048..7c24565854 100644 --- a/resources/views/v1/accounts/create.twig +++ b/resources/views/v1/accounts/create.twig @@ -1,12 +1,12 @@ {% extends "./layout/default" %} {% block breadcrumbs %} - {{ Breadcrumbs.render(Route.getCurrentRoute.getName, what) }} + {{ Breadcrumbs.render(Route.getCurrentRoute.getName, objectType) }} {% endblock %} {% block content %}
- +
@@ -17,13 +17,13 @@
{{ ExpandedForm.text('name') }} - {% if what == 'asset' or what == 'liabilities' %} + {% if objectType == 'asset' or objectType == 'liabilities' %} {{ ExpandedForm.currencyList('currency_id', null, {helpText:'account_default_currency'|_}) }} {% endif %} - {% if what == 'liabilities' %} + {% if objectType == 'liabilities' %} {{ ExpandedForm.select('liability_type_id', liabilityTypes) }} - {{ ExpandedForm.amountNoCurrency('openingBalance', null, {label:'debt_start_amount'|_, helpText: 'debt_start_amount_help'|_}) }} - {{ ExpandedForm.date('openingBalanceDate', null, {label:'debt_start_date'|_}) }} + {{ ExpandedForm.amountNoCurrency('opening_balance', null, {label:'debt_start_amount'|_, helpText: 'debt_start_amount_help'|_}) }} + {{ ExpandedForm.date('opening_balance_date', null, {label:'debt_start_date'|_}) }} {{ ExpandedForm.percentage('interest') }} {{ ExpandedForm.select('interest_period', interestPeriods) }} {% endif %} @@ -41,14 +41,14 @@ {{ ExpandedForm.text('iban') }} {{ ExpandedForm.text('BIC', null, {maxlength: 11}) }} - {{ ExpandedForm.text('accountNumber') }} + {{ ExpandedForm.text('account_number') }} - {% if what == 'asset' %} + {% if objectType == 'asset' %} - {{ ExpandedForm.amountNoCurrency('openingBalance') }} - {{ ExpandedForm.date('openingBalanceDate') }} - {{ ExpandedForm.select('accountRole', roles,null,{helpText : 'asset_account_role_help'|_}) }} - {{ ExpandedForm.amountNoCurrency('virtualBalance') }} + {{ ExpandedForm.amountNoCurrency('opening_balance') }} + {{ ExpandedForm.date('opening_balance_date') }} + {{ ExpandedForm.select('account_role', roles,null,{helpText : 'asset_account_role_help'|_}) }} + {{ ExpandedForm.amountNoCurrency('virtual_balance') }} {% endif %} {# only correct way to do active checkbox #} {{ ExpandedForm.checkbox('include_net_worth', 1) }} @@ -67,7 +67,7 @@
diff --git a/resources/views/v1/accounts/edit.twig b/resources/views/v1/accounts/edit.twig index 6d90594758..97f24244eb 100644 --- a/resources/views/v1/accounts/edit.twig +++ b/resources/views/v1/accounts/edit.twig @@ -8,7 +8,7 @@ {{ Form.model(account, {'class' : 'form-horizontal','id' : 'update','url' : route('accounts.update',account.id) } ) }} - +
@@ -18,14 +18,14 @@
{{ ExpandedForm.text('name') }} - {% if account.accountType.type == 'Default account' or account.accountType.type == 'Asset account' or what == 'liabilities' %} + {% if account.accountType.type == 'Default account' or account.accountType.type == 'Asset account' or objectType == 'liabilities' %} {{ ExpandedForm.currencyList('currency_id', null, {helpText:'account_default_currency'|_}) }} {% endif %} - {% if what == 'liabilities' %} + {% if objectType == 'liabilities' %} {{ ExpandedForm.select('liability_type_id', liabilityTypes) }} - {{ ExpandedForm.amountNoCurrency('openingBalance', null, {label:'debt_start_amount'|_, helpText: 'debt_start_amount_help'|_}) }} - {{ ExpandedForm.date('openingBalanceDate', null, {label:'debt_start_date'|_}) }} + {{ ExpandedForm.amountNoCurrency('opening_balance', null, {label:'debt_start_amount'|_, helpText: 'debt_start_amount_help'|_}) }} + {{ ExpandedForm.date('opening_balance_date', null, {label:'debt_start_date'|_}) }} {{ ExpandedForm.percentage('interest') }} {{ ExpandedForm.select('interest_period', interestPeriods) }} {% endif %} @@ -41,19 +41,19 @@
{{ ExpandedForm.text('iban') }} {{ ExpandedForm.text('BIC', null, {maxlength: 11}) }} - {% if preFilled.accountRole == 'ccAsset' %} - {{ ExpandedForm.text('accountNumber', null , {label:trans('form.creditCardNumber')}) }} + {% if preFilled.account_role == 'ccAsset' %} + {{ ExpandedForm.text('account_number', null , {label:trans('form.creditCardNumber')}) }} {% else %} - {{ ExpandedForm.text('accountNumber') }} + {{ ExpandedForm.text('account_number') }} {% endif %} {% if account.accounttype.type == 'Default account' or account.accounttype.type == 'Asset account' %} {# get opening balance entry for this thing! #} - {{ ExpandedForm.amountNoCurrency('openingBalance',null) }} - {{ ExpandedForm.date('openingBalanceDate') }} - {{ ExpandedForm.select('accountRole', roles) }} - {{ ExpandedForm.amountNoCurrency('virtualBalance',null) }} + {{ ExpandedForm.amountNoCurrency('opening_balance',null) }} + {{ ExpandedForm.date('opening_balance_date') }} + {{ ExpandedForm.select('account_role', roles) }} + {{ ExpandedForm.amountNoCurrency('virtual_balance',null) }} {% endif %} @@ -68,14 +68,14 @@
{# panel for credit card options #} - {% if preFilled.accountRole == 'ccAsset' %} + {% if preFilled.account_role == 'ccAsset' %}

{{ 'credit_card_options'|_ }}

- {{ ExpandedForm.select('ccType',Config.get('firefly.ccTypes')) }} - {{ ExpandedForm.date('ccMonthlyPaymentDate',null,{'helpText' : 'Select any year and any month, it will be ignored anway. Only the day of the month is relevant.'}) }} + {{ ExpandedForm.select('cc_type',Config.get('firefly.ccTypes')) }} + {{ ExpandedForm.date('cc_monthly_payment_date',null,{'helpText' : 'Select any year and any month, it will be ignored anway. Only the day of the month is relevant.'}) }}
{% endif %} @@ -90,7 +90,7 @@
diff --git a/resources/views/v1/accounts/index.twig b/resources/views/v1/accounts/index.twig index 38e9c30618..2a133a2553 100644 --- a/resources/views/v1/accounts/index.twig +++ b/resources/views/v1/accounts/index.twig @@ -1,14 +1,14 @@ {% extends "./layout/default" %} {% block breadcrumbs %} - {{ Breadcrumbs.render(Route.getCurrentRoute.getName, what) }} + {{ Breadcrumbs.render(Route.getCurrentRoute.getName, objectType) }} {% endblock %} {% block content %} {% if accounts.count > 0 %}
- {% endif %} {% if accounts.count == 0 and page == 1 %} - {% include 'partials.empty' with {what: what, type: 'accounts',route: route('accounts.create', [what])} %} + {% include 'partials.empty' with {objectType: objectType, type: 'accounts',route: route('accounts.create', [objectType])} %} {% endif %} {% endblock %} @@ -52,7 +52,7 @@ {% block scripts %} {% endblock %} diff --git a/resources/views/v1/accounts/reconcile/overview.twig b/resources/views/v1/accounts/reconcile/overview.twig index a7c9287d36..7116cf6ed4 100644 --- a/resources/views/v1/accounts/reconcile/overview.twig +++ b/resources/views/v1/accounts/reconcile/overview.twig @@ -15,8 +15,8 @@ - {% for id in transactionIds %} - + {% for id in selectedIds %} + {% endfor %} @@ -25,7 +25,7 @@ - + @@ -36,6 +36,10 @@ + + + + diff --git a/resources/views/v1/accounts/reconcile/transactions.twig b/resources/views/v1/accounts/reconcile/transactions.twig index 8a9e4b51fe..999d3d16d2 100644 --- a/resources/views/v1/accounts/reconcile/transactions.twig +++ b/resources/views/v1/accounts/reconcile/transactions.twig @@ -8,7 +8,6 @@ - @@ -17,9 +16,9 @@ {# data for previous/next markers #} {% set endSet = false %} {% set startSet = false %} - {% for transaction in transactions %} + {% for journal in journals %} {# start marker #} - {% if transaction.date < start and startSet == false %} + {% if journal.date < start and startSet == false %} {% set endSet = true %} {% endif %} - - + - {# icon #} + + - - {# description #} - - - + + + + + + + + + + + {# + #} + {% endfor %} {# if the start marker has not been generated yet, do it now, at the end of the loop. #} diff --git a/resources/views/v1/accounts/show.twig b/resources/views/v1/accounts/show.twig index 1f7afad59b..d40fcb0ce1 100644 --- a/resources/views/v1/accounts/show.twig +++ b/resources/views/v1/accounts/show.twig @@ -101,7 +101,7 @@ {% endif %}
-
+

{{ 'transactions'|_ }}

@@ -112,14 +112,11 @@ {% else %} {% set showReconcile = false %} {% endif %} - {% if periods.count > 0 %} - {% include 'list.transactions' with {sorting:true, showReconcile: showReconcile} %} - {% else %} - {% include 'list.transactions' with {sorting:true, showReconcile: showReconcile, showCategories: true, showBudgets: true, showBill:true} %} - {% endif %} + + {% include 'list.groups' %}

- {% if periods.count > 0 %} + {% if periods|length > 0 %} {{ 'show_all_no_filter'|_ }} @@ -132,7 +129,7 @@

- {% if periods.count > 0 %} + {% if periods|length > 0 %}
{% include 'list.periods' %}
@@ -173,5 +170,6 @@ - + {# required for groups.twig #} + {% endblock %} diff --git a/resources/views/v1/admin/link/show.twig b/resources/views/v1/admin/link/show.twig index 8a06d53d37..d4c9e80e90 100644 --- a/resources/views/v1/admin/link/show.twig +++ b/resources/views/v1/admin/link/show.twig @@ -35,14 +35,15 @@
- + {% endfor %} diff --git a/resources/views/v1/admin/users/show.twig b/resources/views/v1/admin/users/show.twig index 0479a8cc0e..16da72c998 100644 --- a/resources/views/v1/admin/users/show.twig +++ b/resources/views/v1/admin/users/show.twig @@ -97,10 +97,6 @@ - - - - diff --git a/resources/views/v1/auth/mfa.twig b/resources/views/v1/auth/mfa.twig new file mode 100644 index 0000000000..4addb40104 --- /dev/null +++ b/resources/views/v1/auth/mfa.twig @@ -0,0 +1,40 @@ +{% extends "./layout/guest" %} + +{% block content %} + {% if session_has('error') %} + + {% endif %} + + + + + + +{% endblock %} \ No newline at end of file diff --git a/resources/views/v1/bills/index.twig b/resources/views/v1/bills/index.twig index d29bd0585d..e50fb8950a 100644 --- a/resources/views/v1/bills/index.twig +++ b/resources/views/v1/bills/index.twig @@ -6,7 +6,7 @@ {% block content %} {% if bills.count == 0 %} - {% include 'partials.empty' with {what: 'default', type: 'bills',route: route('bills.create')} %} + {% include 'partials.empty' with {objectType: 'default', type: 'bills',route: route('bills.create')} %} {% else %}
diff --git a/resources/views/v1/bills/show.twig b/resources/views/v1/bills/show.twig index 4665d9c7fd..8b23eecbc7 100644 --- a/resources/views/v1/bills/show.twig +++ b/resources/views/v1/bills/show.twig @@ -163,7 +163,7 @@

{{ 'connected_journals'|_ }}

- {% include 'list.transactions' with {showCategories: true, showBudgets: true} %} + {% include 'list.groups' %}
@@ -180,5 +180,6 @@ - + {# required for groups.twig #} + {% endblock %} diff --git a/resources/views/v1/budgets/income.twig b/resources/views/v1/budgets/income.twig index 09a919b3da..8f173205b4 100644 --- a/resources/views/v1/budgets/income.twig +++ b/resources/views/v1/budgets/income.twig @@ -17,7 +17,7 @@
-
{{ getCurrencySymbol()|raw }}
+
{{ defaultCurrency.symbol|raw }}
diff --git a/resources/views/v1/budgets/index.twig b/resources/views/v1/budgets/index.twig index b14d9e3a71..678fd394f4 100644 --- a/resources/views/v1/budgets/index.twig +++ b/resources/views/v1/budgets/index.twig @@ -21,7 +21,6 @@ : {{ available|formatAmountPlain }} - @@ -86,7 +85,7 @@ {% if paginator.count == 0 and inactive.count == 0 and page == 1 %} - {% include 'partials.empty' with {what: 'default', type: 'budgets',route: route('budgets.create')} %} + {% include 'partials.empty' with {objectType: 'default', type: 'budgets',route: route('budgets.create')} %} {# make FF ignore demo for now. #} {% set shownDemo = true %} {% endif %} @@ -252,7 +251,6 @@ var budgetIndexUri = "{{ route('budgets.index','REPLACE') }}"; var budgetAmountUri = "{{ route('budgets.amount','REPLACE') }}"; var updateIncomeUri = "{{ route('budgets.income',[start.format('Y-m-d'),end.format('Y-m-d')]) }}?page={{ page }}"; - var infoIncomeUri = "{{ route('budgets.income.info',[start.format('Y-m-d'),end.format('Y-m-d')]) }}"; var periodStart = "{{ start.format('Y-m-d') }}"; var periodEnd = "{{ end.format('Y-m-d') }}"; diff --git a/resources/views/v1/budgets/no-budget.twig b/resources/views/v1/budgets/no-budget.twig index 3769567a47..4d0dcaec47 100644 --- a/resources/views/v1/budgets/no-budget.twig +++ b/resources/views/v1/budgets/no-budget.twig @@ -7,7 +7,7 @@ {% block content %} {# upper show-all instruction #} - {% if periods.count > 0 %} + {% if periods|length > 0 %}

{{ 'showEverything'|_ }}

@@ -16,21 +16,21 @@ {% endif %}
-
+

{{ subTitle }}

- {% if periods.count > 0 %} - {% include 'list.transactions' %} + {% if periods|length > 0 %} + {% include 'list.groups' %}

{{ 'show_all_no_filter'|_ }}

{% else %} - {% include 'list.transactions' with {showCategories:true, showBill:true} %} + {% include 'list.groups' %}

{{ 'show_the_current_period_and_overview'|_ }} @@ -40,7 +40,7 @@

- {% if periods.count > 0 %} + {% if periods|length > 0 %}
{% include 'list.periods' %}
@@ -49,7 +49,7 @@
{# lower show-all instruction #} - {% if periods.count > 0 %} + {% if periods|length > 0 %}

{{ 'showEverything'|_ }}

@@ -59,5 +59,6 @@ {% endblock %} {% block scripts %} - + {# required for groups.twig #} + {% endblock %} diff --git a/resources/views/v1/budgets/show.twig b/resources/views/v1/budgets/show.twig index 145e69fcb5..abe743d46a 100644 --- a/resources/views/v1/budgets/show.twig +++ b/resources/views/v1/budgets/show.twig @@ -90,9 +90,9 @@
{% if budgetLimit %} - {% include 'list.transactions' %} + {% include 'list.groups' %} {% else %} - {% include 'list.transactions' with {showCategories: true} %} + {% include 'list.groups' %} {% endif %} {% if budgetLimit %}

@@ -183,5 +183,6 @@ - + {# required for groups.twig #} + {% endblock %} diff --git a/resources/views/v1/categories/index.twig b/resources/views/v1/categories/index.twig index cf2b49840f..b740f78062 100644 --- a/resources/views/v1/categories/index.twig +++ b/resources/views/v1/categories/index.twig @@ -35,7 +35,7 @@

{% else %} - {% include 'partials.empty' with {what: 'default', type: 'categories',route: route('categories.create')} %} + {% include 'partials.empty' with {objectType: 'default', type: 'categories',route: route('categories.create')} %} {% endif %} {% endblock %} diff --git a/resources/views/v1/categories/no-category.twig b/resources/views/v1/categories/no-category.twig index e1f069343e..c43b254ddf 100644 --- a/resources/views/v1/categories/no-category.twig +++ b/resources/views/v1/categories/no-category.twig @@ -7,7 +7,7 @@ {% block content %} {# upper show-all instruction #} - {% if periods.count > 0 %} + {% if periods|length > 0 %}

{{ 'showEverything'|_ }}

@@ -16,21 +16,21 @@ {% endif %}
-
+

{{ subTitle }}

- {% if periods.count > 0 %} - {% include 'list.transactions' %} + {% if periods|length > 0 %} + {% include 'list.groups' %}

{{ 'show_all_no_filter'|_ }}

{% else %} - {% include 'list.transactions' with {showBudgets:true, showBill:true} %} + {% include 'list.groups' %}

{{ 'show_the_current_period_and_overview'|_ }} @@ -40,51 +40,16 @@

- {% if periods.count > 0 %} -
- {% for period in periods %} - {% if period.count > 0 %} -
- -
-
{{ formatAmountByAccount(account, startBalance) }}
{{ trans('firefly.selected_transactions', {count: transactionIds|length}) }}{{ trans('firefly.selected_transactions', {count: selectedIds|length}) }} {{ formatAmountByAccount(account, amount) }}
{{ 'submitted_end_balance'|_ }} ({{ end.formatLocalized(monthAndDayFormat) }}) {{ formatAmountByAccount(account, endBalance) }}
{{ 'sum_of_reconciliation'|_ }}{{ formatAmountByAccount(account, reconSum) }}
{{ 'difference'|_ }}
  @@ -37,7 +36,7 @@ {% endif %} {# end marker #} - {% if transaction.date <= end and endSet == false %} + {% if journal.date <= end and endSet == false %}
  @@ -53,71 +52,101 @@
- - {{ transaction|transactionDescription }} - - {# is a split journal #} - {{ transaction|transactionIsSplit }} - - {# count attachments #} - {{ transaction|transactionHasAtt }} - - - {{ transaction|transactionAmount }} - {% if currency.id == transaction.transaction_currency_id %} - {% set transactionAmount = transaction.transaction_amount %} - {% else %} - {% set transactionAmount = transaction.transaction_foreign_amount %} + {% if journal.transaction_type_type == 'Withdrawal' %} + {% endif %} - {% if transaction.reconciled %} - {{ transaction|transactionReconciled }} - - {% else %} - + {% if journal.transaction_type_type == 'Deposit' %} + + {% endif %} + + {% if journal.transaction_type_type == 'Transfer' %} + + {% endif %} + + {% if journal.transaction_type_type == 'Reconciliation' %} + + {% endif %} + {% if journal.transaction_type_type == 'Opening balance' %} + {% endif %} + + {% if journal.group_title %} + {{ journal.group_title }}: + {% endif %} + {{ journal.description }} + + + {{ formatAmountBySymbol(journal.amount, journal.currency_symbol, journal.currency_symbol_decimal_places) }} + {% if null != journal.foreign_amount %} + ({{ formatAmountBySymbol(journal.foreign_amount, journal.foreign_currency_symbol, journal.foreign_currency_symbol_decimal_places) }}) + {% endif %} + + + {% if journal.date >= start and journal.date <= end %} + {% if journal.reconciled %} + + + {% else %} + + {% endif %} + {% else %} + + {% if journal.reconciled %} + + {% else %} + + {% endif %} + + {% endif %} + +
{{ link.source.description }} {{ link.source|journalTotalAmount }} + {{ journalObjectAmount(link.source) }} + {{ journalLinkTranslation('outward', linkType.outward) }} {{ link.destination.description }} - {{ link.destination|journalTotalAmount }} - + {{ journalObjectAmount(link.destination) }}
{{ trans('list.categories_count') }} {{ information.categories }}
{{ trans('list.export_jobs_count') }}{{ information.export_jobs }}, {{ trans('firefly.successful_count', {count: information.export_jobs_success}) }}
{{ trans('list.import_jobs_count') }} {{ information.import_jobs }}, {{ trans('firefly.successful_count', {count: information.import_jobs_success}) }}
- - - - - {% if period.spent != 0 %} - - - - - {% endif %} - {% if period.earned != 0 %} - - - - - {% endif %} - {% if period.transferred != 0 %} - - - - - {% endif %} -
{{ 'transactions'|_ }}{{ period.count }}
{{ 'spent'|_ }}{{ period.spent|formatAmount }}
{{ 'earned'|_ }}{{ period.earned|formatAmount }}
{{ 'transferred'|_ }}{{ period.transferred|formatAmountPlain }}
-
-
- {% endif %} - {% endfor %} + {% if periods|length > 0 %} +
+ {% include 'list.periods' %}
{% endif %} {# lower show-all instruction #} - {% if periods.count > 0 %} + {% if periods|length > 0 %}

{{ 'showEverything'|_ }}

@@ -94,5 +59,6 @@ {% endblock %} {% block scripts %} - + {# required for groups.twig #} + {% endblock %} diff --git a/resources/views/v1/categories/show.twig b/resources/views/v1/categories/show.twig index 8523aafa1c..09c50649e6 100644 --- a/resources/views/v1/categories/show.twig +++ b/resources/views/v1/categories/show.twig @@ -49,7 +49,7 @@
{% endif %}
- {% if periods.count > 0 %} + {% if periods|length > 0 %}

{{ 'showEverything'|_ }}

@@ -58,15 +58,15 @@ {% endif %}
-
+

{{ 'transactions'|_ }}

- {% if periods.count > 0 %} - {% include 'list.transactions' %} + {% if periods|length > 0 %} + {% include 'list.groups' %}

@@ -74,7 +74,7 @@

{% else %} - {% include 'list.transactions' with {showBudgets:true, showBill:true} %} + {% include 'list.groups' %}

@@ -85,7 +85,7 @@

- {% if periods.count > 0 %} + {% if periods|length > 0 %}
{% include 'list.periods' %}
@@ -103,5 +103,6 @@ - + {# required for groups.twig #} + {% endblock %} diff --git a/resources/views/v1/emails/report-new-journals-html.twig b/resources/views/v1/emails/report-new-journals-html.twig index a3022d0775..d4d8e0394b 100644 --- a/resources/views/v1/emails/report-new-journals-html.twig +++ b/resources/views/v1/emails/report-new-journals-html.twig @@ -13,7 +13,7 @@

You can find it in your Firefly III installation:
{% for journal in journals %} -
{{ journal.description }} ({{ journal|journalTotalAmount }}) + {{ journal.description }} (AMOUNT TODO) {% endfor %}

{% endif %} @@ -25,7 +25,7 @@ diff --git a/resources/views/v1/emails/report-new-journals-text.twig b/resources/views/v1/emails/report-new-journals-text.twig index 81faa232d5..c3d3527a3a 100644 --- a/resources/views/v1/emails/report-new-journals-text.twig +++ b/resources/views/v1/emails/report-new-journals-text.twig @@ -10,7 +10,7 @@ Firefly III has created {{ journals.count }} transactions for you. You can find in in your Firefly III installation: {% for journal in journals %} -{{ journal.description }}: {{ route('transactions.show', journal.id) }} ({{ journal|journalTotalAmountPlain }}) +{{ journal.description }}: {{ route('transactions.show', journal.id) }} (AMOUNT TODO) {% endfor %} {% endif %} @@ -18,7 +18,7 @@ You can find in in your Firefly III installation: You can find them in your Firefly III installation: {% for journal in journals %} -- {{ journal.description }}: {{ route('transactions.show', journal.id) }} ({{ journal|journalTotalAmountPlain }}) +- {{ journal.description }}: {{ route('transactions.show', journal.id) }} (AMOUNT TODO) {% endfor %} {% endif %} diff --git a/resources/views/v1/errors/404.twig b/resources/views/v1/errors/404.twig index ee684195fb..5091a89d0a 100644 --- a/resources/views/v1/errors/404.twig +++ b/resources/views/v1/errors/404.twig @@ -39,6 +39,11 @@ The page you have requested does not exist. Please check that you have not entered the wrong URL. Did you make a typo perhaps?

+

+ If you were redirected to this page automatically, please accept my apologies. + There is a mention of this error in your log files and I would be grateful if you sent + me the error to me. +

diff --git a/resources/views/v1/errors/500.twig b/resources/views/v1/errors/500.twig index 88f8fe34e0..4d5cea9c04 100644 --- a/resources/views/v1/errors/500.twig +++ b/resources/views/v1/errors/500.twig @@ -38,12 +38,18 @@ font-size: 72px; margin-bottom: 40px; } + + .text { + font-size: 30px; + margin-bottom: 40px; + }
-
Be right back.
+
Whoops
+
There was a fatal error. Please check the log files.
diff --git a/resources/views/v1/export/index.twig b/resources/views/v1/export/index.twig deleted file mode 100644 index 0bb79c101f..0000000000 --- a/resources/views/v1/export/index.twig +++ /dev/null @@ -1,109 +0,0 @@ -{% extends "./layout/default" %} - -{% block breadcrumbs %} - {{ Breadcrumbs.render(Route.getCurrentRoute.getName) }} -{% endblock %} - -{% block content %} -
- - - -
-
- -
-
-

{{ 'export_and_backup_data'|_ }}

-
-
- - - -
-

- {{ 'export_data_intro'|_ }} -

-
-
- {{ ExpandedForm.date('export_start_range', first) }} - - {{ ExpandedForm.date('export_end_range', today) }} - - {# EXPORT FORMATS #} -
- - -
- {% if errors.has('exportFormat') %} - - {% endif %} - - {% for format in formats %} -
- -
- {% endfor %} -
-
- - - {# ACCOUNTS #} - {{ ExpandedForm.assetAccountCheckList('accounts', {' class': 'account-checkbox','select_all': true}) }} - - {{ ExpandedForm.checkbox('include_attachments','1', true) }} - - {{ ExpandedForm.checkbox('include_old_uploads','1', false, {helpText: 'include_old_uploads_help'|_}) }} -
-
-
-
- -
-
-
-
- - -{% endblock %} -{% block scripts %} - - - - -{% endblock %} -{% block styles %} - - -{% endblock %} diff --git a/resources/views/v1/form/options.twig b/resources/views/v1/form/options.twig index f11e49bbe9..ee638c0460 100644 --- a/resources/views/v1/form/options.twig +++ b/resources/views/v1/form/options.twig @@ -31,18 +31,4 @@
{% endif %} -{% if type == 'create' and name == 'transaction' %} -
- -
-
-
-
-
-{% endif %} diff --git a/resources/views/v1/index.twig b/resources/views/v1/index.twig index e8012d06a3..673f4cd63d 100644 --- a/resources/views/v1/index.twig +++ b/resources/views/v1/index.twig @@ -56,25 +56,11 @@
{% if data[0].count > 0 %}
- {% include 'list.journals-tiny' with {'transactions': data[0],'account': data[1]} %} + {% include 'list.groups-tiny' with {'groups': data[0],'account': data[1]} %}
{% else %}
@@ -95,22 +81,8 @@
{{ formatAmountByAccount(data[1], data[1]|balance, false) }} diff --git a/resources/views/v1/install/index.twig b/resources/views/v1/install/index.twig index 9171e273c3..9fa73d1d54 100644 --- a/resources/views/v1/install/index.twig +++ b/resources/views/v1/install/index.twig @@ -15,11 +15,8 @@ {% block scripts %} diff --git a/resources/views/v1/javascript/variables.twig b/resources/views/v1/javascript/variables.twig index 803eb3a4f2..5af0c92803 100644 --- a/resources/views/v1/javascript/variables.twig +++ b/resources/views/v1/javascript/variables.twig @@ -50,9 +50,13 @@ var helpPageTitle = "{{ trans('firefly.help_for_this_page')|escape('js') }}"; var noHelpForPage = "{{ trans('firefly.no_help_could_be_found')|escape('js') }}"; var noHelpForPageTitle = "{{ trans('firefly.no_help_title')|escape('js') }}"; -var edit_selected_txt = "{{ trans('firefly.edit')|escape('js') }}"; +var edit_selected_txt = "{{ trans('firefly.mass_edit')|escape('js') }}"; var edit_bulk_selected_txt = "{{ trans('firefly.bulk_edit')|escape('js') }}"; -var delete_selected_txt = "{{ trans('firefly.delete')|escape('js') }}"; +var delete_selected_txt = "{{ trans('firefly.mass_delete')|escape('js') }}"; + +var mass_edit_url = '{{ route('transactions.mass.edit', ['']) }}'; +var bulk_edit_url = '{{ route('transactions.bulk.edit', ['']) }}'; +var mass_delete_url = '{{ route('transactions.mass.delete', ['']) }}'; // for demo: var nextLabel = "{{ trans('firefly.intro_next_label')|escape('js') }}"; diff --git a/resources/views/v1/layout/default.twig b/resources/views/v1/layout/default.twig index 0658a1fd7f..b12da4122a 100644 --- a/resources/views/v1/layout/default.twig +++ b/resources/views/v1/layout/default.twig @@ -104,9 +104,9 @@
- {% if period.transactions > 0 %} - - - - + {% if period.total_transactions > 0 %} + + + + {% endif %} - {% for arr in period.spent %} - {% if arr.amount !=0 %} + + {% for entry in period.spent %} + {% if entry.amount != 0 %} - + {% endif %} {% endfor %} - {% for arr in period.earned %} - {% if arr.amount !=0 %} + {% for entry in period.earned %} + {% if entry.amount != 0 %} - + {% endif %} {% endfor %} - {% for arr in period.transferred %} - {% if arr.amount !=0 %} + {% for entry in period.transferred %} + {% if entry.amount != 0 %} - + + + {% endif %} + {% endfor %} + + {% for entry in period.transferred_away %} + {% if entry.amount != 0 %} + + + + + {% endif %} + {% endfor %} + + {% for entry in period.transferred_in %} + {% if entry.amount != 0 %} + + + {% endif %} {% endfor %} diff --git a/resources/views/v1/list/transactions.twig b/resources/views/v1/list/transactions.twig deleted file mode 100644 index f122531c9c..0000000000 --- a/resources/views/v1/list/transactions.twig +++ /dev/null @@ -1,80 +0,0 @@ -{# render pagination #} -{{ transactions.render|raw }} - -
{{ 'transactions'|_ }}{{ period.transactions }}
{{ 'transactions'|_ }}{{ period.total_transactions }}
{{ 'spent'|_ }}{{ formatAmountByCurrency(arr.currency, arr.amount) }} + + {{ formatAmountBySymbol(entry.amount, entry.currency_symbol, entry.currency_decimal_places) }} + +
{{ 'earned'|_ }}{{ formatAmountByCurrency(arr.currency, arr.amount) }} + + {{ formatAmountBySymbol(entry.amount*-1, entry.currency_symbol, entry.currency_decimal_places) }} + +
{{ 'transferred'|_ }}{{ formatAmountByCurrency(arr.currency, arr.amount, false) }} + + {{ formatAmountBySymbol(entry.amount*-1, entry.currency_symbol, entry.currency_decimal_places) }} + +
{{ 'transferred_away'|_ }} + + {{ formatAmountBySymbol(entry.amount*-1, entry.currency_symbol, entry.currency_decimal_places) }} + +
{{ 'transferred_in'|_ }} + + {{ formatAmountBySymbol(entry.amount*-1, entry.currency_symbol, entry.currency_decimal_places) }} + +
- - - {# hidden row for checkboxes #} - - - {# header for icon #} - - - - - - - - {# Only show budgets when asked in some way #} - {% if showBudgets %} - - {% endif %} - - {# Only show categories when asked in some way #} - {% if showCategories %} - - {% endif %} - - {# Only show bill when asked in some way #} - {% if showBill %} - - {% endif %} - - {# visible row for edit/delete buttons #} - - - - - {% for transaction in transactions %} - {% include 'partials.transaction-row' %} - {% endfor %} - -
{{ trans('list.description') }}{{ trans('list.amount') }}
- -
-
- {{ transactions.render|raw }} -
-
diff --git a/resources/views/v1/partials/boxes.twig b/resources/views/v1/partials/boxes.twig index 95fc6ea5d8..6bbe4ecb81 100644 --- a/resources/views/v1/partials/boxes.twig +++ b/resources/views/v1/partials/boxes.twig @@ -1,5 +1,5 @@