Merge branch 'release/4.8.0'

This commit is contained in:
James Cole 2019-08-09 21:03:12 +02:00
commit 81cc138700
1231 changed files with 84779 additions and 54205 deletions

View File

@ -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 .

View File

@ -2,7 +2,6 @@
docker run --rm --privileged multiarch/qemu-user-static:register --reset
# get qemu-arm-static binary
mkdir tmp
pushd tmp && \

View File

@ -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
exec apache2-foreground

178
.deploy/docker/wait-for-it.sh Executable file
View File

@ -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

View File

@ -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
nb_NO
cs_CZ
id_ID
hu_HU

View File

@ -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"

View File

@ -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

5
.github/funding.yml vendored Normal file
View File

@ -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

6
.github/ranger.yml vendored Normal file
View File

@ -0,0 +1,6 @@
# in .github/ranger.yml
comments:
- action: delete_comment
pattern: +1
- action: delete_comment
pattern: ":+1:"

1
.github/stale.yml vendored
View File

@ -14,6 +14,7 @@ exemptLabels:
- feature
- bug
- possible-bug
- "possible bug"
- announcement
# Set to true to ignore issues in a project (defaults to false)

View File

@ -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).

View File

@ -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

View File

@ -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"

View File

@ -1,7 +1,7 @@
sudo: required
language: bash
env:
- VERSION=4.7.17.6
- VERSION=4.8.0
dist: xenial

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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
*/

View File

@ -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
*/

View File

@ -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
*/

View File

@ -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
*/

View File

@ -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
*/

View File

@ -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;
}
}

View File

@ -42,6 +42,7 @@ class AvailableBudgetController extends Controller
/**
* AvailableBudgetController constructor.
* @codeCoverageIgnore
*/
public function __construct()
{

View File

@ -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;

View File

@ -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
{

View File

@ -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
{

View File

@ -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

View File

@ -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);

View File

@ -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');

View File

@ -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

View File

@ -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
*/

View File

@ -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;
}

View File

@ -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';

View File

@ -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));

View File

@ -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');
}
}

View File

@ -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);

View File

@ -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;
}
}

View File

@ -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');

View File

@ -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
{

View File

@ -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
*/

View File

@ -1,141 +0,0 @@
<?php
/**
* AccountRequest.php
* Copyright (c) 2018 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 <http://www.gnu.org/licenses/>.
*/
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;
}
}

View File

@ -0,0 +1,83 @@
<?php
/**
* AccountStoreRequest.php
* 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 <http://www.gnu.org/licenses/>.
*/
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;
}
}

View File

@ -0,0 +1,84 @@
<?php
/**
* AccountUpdateRequest.php
* Copyright (c) 2018 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 <http://www.gnu.org/licenses/>.
*/
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;
}
}

View File

@ -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 = [

View File

@ -25,6 +25,8 @@ namespace FireflyIII\Api\V1\Requests;
/**
* Class AvailableBudgetRequest
*
* @codeCoverageIgnore
*/
class AvailableBudgetRequest extends Request
{

View File

@ -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);

View File

@ -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
{

View File

@ -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
{

View File

@ -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
{

View File

@ -28,6 +28,7 @@ use FireflyIII\Rules\IsBoolean;
/**
* Class ConfigurationRequest
* @codeCoverageIgnore
*/
class ConfigurationRequest extends Request
{

View File

@ -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
{

View File

@ -1,7 +1,7 @@
<?php
/**
* ReconciliationUpdateRequest.php
* Copyright (c) 2017 thegrumpydictator@gmail.com
* DateRequest.php
* Copyright (c) 2019 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
@ -18,54 +18,51 @@
* You should have received a copy of the GNU General Public License
* along with Firefly III. If not, see <http://www.gnu.org/licenses/>.
*/
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;
}
}
}

View File

@ -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
{

View File

@ -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',

View File

@ -26,6 +26,7 @@ namespace FireflyIII\Api\V1\Requests;
/**
*
* Class PreferenceRequest
* @codeCoverageIgnore
*/
class PreferenceRequest extends Request
{

View File

@ -1,214 +0,0 @@
<?php
/**
* RecurrenceRequest.php
* Copyright (c) 2018 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 <http://www.gnu.org/licenses/>.
*/
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;
}
}

View File

@ -0,0 +1,78 @@
<?php
/**
* RecurrenceStoreRequest.php
* 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 <http://www.gnu.org/licenses/>.
*/
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);
}
);
}
}

View File

@ -0,0 +1,78 @@
<?php
/**
* RecurrenceUpdateRequest.php
* Copyright (c) 2018 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 <http://www.gnu.org/licenses/>.
*/
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);
}
);
}
}

View File

@ -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;
}
}

View File

@ -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
{

View File

@ -0,0 +1,144 @@
<?php
/**
* RuleGroupTestRequest.php
* 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 <http://www.gnu.org/licenses/>.
*/
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;
}
}

View File

@ -0,0 +1,119 @@
<?php
/**
* RuleGroupTriggerRequest.php
* 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 <http://www.gnu.org/licenses/>.
*/
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;
}
}

View File

@ -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;
}
}

View File

@ -0,0 +1,144 @@
<?php
/**
* RuleTestRequest.php
* 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 <http://www.gnu.org/licenses/>.
*/
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;
}
}

View File

@ -0,0 +1,119 @@
<?php
/**
* RuleTriggerRequest.php
* 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 <http://www.gnu.org/licenses/>.
*/
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;
}
}

View File

@ -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
{

View File

@ -81,7 +81,7 @@ class TransactionLinkRequest extends Request
/**
* Configure the validator instance.
*
* @param Validator $validator
* @param Validator $validator
*
* @return void
*/

View File

@ -1,222 +0,0 @@
<?php
/**
* TransactionRequest.php
* Copyright (c) 2018 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 <http://www.gnu.org/licenses/>.
*/
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;
}
}

View File

@ -0,0 +1,278 @@
<?php
/**
* TransactionStoreRequest.php
* Copyright (c) 2018 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 <http://www.gnu.org/licenses/>.
*/
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;
}
}

View File

@ -0,0 +1,324 @@
<?php
/**
* TransactionUpdateRequest.php
* 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 <http://www.gnu.org/licenses/>.
*/
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;
}
}

View File

@ -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
{

View File

@ -1,420 +0,0 @@
<?php
/**
* ApplyRules.php
* Copyright (c) 2018 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 <http://www.gnu.org/licenses/>.
*/
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;
}
}

View File

@ -0,0 +1,83 @@
<?php
/**
* CorrectDatabase.php
* 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 <http://www.gnu.org/licenses/>.
*/
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;
}
}

View File

@ -0,0 +1,57 @@
<?php
/**
* VerifySkeleton.php
* 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 <http://www.gnu.org/licenses/>.
*/
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;
}
}

View File

@ -0,0 +1,80 @@
<?php
/**
* CreateAccessTokens.php
* 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 <http://www.gnu.org/licenses/>.
*/
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;
}
}

View File

@ -0,0 +1,82 @@
<?php
/**
* CreateLinkTypes.php
* 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 <http://www.gnu.org/licenses/>.
*/
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;
}
}

View File

@ -0,0 +1,70 @@
<?php
/**
* DeleteEmptyGroups.php
* 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 <http://www.gnu.org/licenses/>.
*/
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;
}
}

View File

@ -0,0 +1,123 @@
<?php
/**
* DeleteEmptyJournals.php
* 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 <http://www.gnu.org/licenses/>.
*/
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.');
}
}
}

View File

@ -0,0 +1,137 @@
<?php
/**
* DeleteOrphanedTransactions.php
* 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 <http://www.gnu.org/licenses/>.
*/
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.');
}
}
}

View File

@ -0,0 +1,80 @@
<?php
/**
* DeleteZeroAmount.php
* 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 <http://www.gnu.org/licenses/>.
*/
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;
}
}

View File

@ -0,0 +1,104 @@
<?php
/**
* EnableCurrencies.php
* 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 <http://www.gnu.org/licenses/>.
*/
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;
}
}

View File

@ -0,0 +1,255 @@
<?php
/**
* FixAccountTypes.php
* 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 <http://www.gnu.org/licenses/>.
*/
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);
}
}
}

View File

@ -0,0 +1,101 @@
<?php
/**
* FixPiggies.php
* 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 <http://www.gnu.org/licenses/>.
*/
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;
}
}

View File

@ -0,0 +1,102 @@
<?php
/**
* FixUnevenAmount.php
* 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 <http://www.gnu.org/licenses/>.
*/
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);
}
}

View File

@ -0,0 +1,74 @@
<?php
/**
* RemoveBills.php
* 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 <http://www.gnu.org/licenses/>.
*/
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;
}
}

View File

@ -0,0 +1,98 @@
<?php
/**
* RenameMetaFields.php
* 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 <http://www.gnu.org/licenses/>.
*/
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;
}
}

View File

@ -0,0 +1,77 @@
<?php
/**
* TransferBudgets.php
* 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 <http://www.gnu.org/licenses/>.
*/
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;
}
}

View File

@ -1,150 +0,0 @@
<?php
/**
* CreateExport.php
* Copyright (c) 2018 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 <http://www.gnu.org/licenses/>.
*/
/** @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;
}
}

View File

@ -1,304 +0,0 @@
<?php
/**
* CreateImport.php
* Copyright (c) 2018 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 <http://www.gnu.org/licenses/>.
*/
/** @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;
}
}

View File

@ -1,111 +0,0 @@
<?php
/**
* DecryptAttachment.php
* Copyright (c) 2018 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 <http://www.gnu.org/licenses/>.
*/
/** @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;
}
}

View File

@ -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()));
}

View File

@ -1,165 +0,0 @@
<?php
/**
* Import.php
* Copyright (c) 2018 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 <http://www.gnu.org/licenses/>.
*/
/** @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;
}
}

View File

@ -0,0 +1,332 @@
<?php
/**
* CreateCSVImport.php
* 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 <http://www.gnu.org/licenses/>.
*/
/** @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);
}
}
}
}

View File

@ -0,0 +1,188 @@
<?php
/**
* ReportEmptyObjects.php
* 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 <http://www.gnu.org/licenses/>.
*/
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);
}
}
}

View File

@ -1,7 +1,7 @@
<?php
/**
* EncryptFile.php
* Copyright (c) 2018 thegrumpydictator@gmail.com
* ReportIntegrity.php
* Copyright (c) 2019 thegrumpydictator@gmail.com
*
* This file is part of Firefly III.
*
@ -21,53 +21,53 @@
declare(strict_types=1);
namespace FireflyIII\Console\Commands;
namespace FireflyIII\Console\Commands\Integrity;
use FireflyIII\Exceptions\FireflyException;
use FireflyIII\Services\Internal\File\EncryptService;
use Artisan;
use Illuminate\Console\Command;
use Schema;
/**
* Class EncryptFile.
*
* Class ReportIntegrity
* @codeCoverageIgnore
*/
class EncryptFile extends Command
class ReportIntegrity extends Command
{
/**
* The console command description.
*
* @var string
*/
protected $description = 'Encrypts a file and places it in the upload disk.';
protected $description = 'Will report on the integrity of your database.';
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'firefly:encrypt-file {file} {key}';
protected $signature = 'firefly-iii:report-integrity';
/**
* Execute the console command.
*
* @throws \Illuminate\Contracts\Encryption\EncryptException
*/
public function handle(): int
{
$code = 0;
$file = (string)$this->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;
}
}
}

Some files were not shown because too many files have changed in this diff Show More