mirror of
https://github.com/firefly-iii/firefly-iii.git
synced 2025-02-25 18:45:27 -06:00
Merge branch 'release/4.6.5'
This commit is contained in:
commit
81bef28607
37
CHANGELOG.md
37
CHANGELOG.md
@ -2,6 +2,43 @@
|
||||
All notable changes to this project will be documented in this file.
|
||||
This project adheres to [Semantic Versioning](http://semver.org/).
|
||||
|
||||
## [4.6.5] - 2017-09-09
|
||||
|
||||
### Added
|
||||
- #616, The ability to link transactions
|
||||
- #763, as suggested by @tannie
|
||||
- #770, as suggested by @skibbipl
|
||||
- #780, as suggested by @skibbipl
|
||||
- #784, as suggested by @SmilingWorlock
|
||||
- Lots of code for future support of automated Bunq imports
|
||||
|
||||
### Changed
|
||||
- Rewrote the export routine
|
||||
- #782, as suggested by @NiceGuyIT
|
||||
- #800, as suggested by @jleeong
|
||||
|
||||
### Fixed
|
||||
- #724, reported by @skibbipl
|
||||
- #738, reported by @skibbipl
|
||||
- #760, reported by @leander091
|
||||
- #764, reported by @tannie
|
||||
- #792, reported by @jleeong
|
||||
- #793, reported by @nicoschreiner
|
||||
- #797, reported by @leander091
|
||||
- #801, reported by @pkoziol
|
||||
- #803, reported by @pkoziol
|
||||
- #805, reported by @pkoziol
|
||||
- #806, reported by @pkoziol
|
||||
- #807, reported by @pkoziol
|
||||
- #808, reported by @pkoziol
|
||||
- #809, reported by @pkoziol
|
||||
- #814, reported by @nicoschreiner
|
||||
- #818, reported by @gavu
|
||||
- #819, reported by @DieBauer
|
||||
- #820, reported by @simonsmiley
|
||||
- Various other fixes
|
||||
|
||||
|
||||
## [4.6.4] - 2017-08-13
|
||||
### Added
|
||||
- PHP7.1 support
|
||||
|
22
Dockerfile
22
Dockerfile
@ -23,28 +23,18 @@ RUN docker-php-ext-install -j$(nproc) curl gd intl json mcrypt readline tidy zip
|
||||
# Generate locales supported by firefly
|
||||
RUN echo "en_US.UTF-8 UTF-8\nde_DE.UTF-8 UTF-8\nnl_NL.UTF-8 UTF-8\npt_BR.UTF-8 UTF-8" > /etc/locale.gen && locale-gen
|
||||
|
||||
COPY docker/apache2.conf /etc/apache2/apache2.conf
|
||||
# Enable apache mod rewrite..
|
||||
RUN a2enmod rewrite
|
||||
|
||||
# Setup the Composer installer
|
||||
RUN curl -o /tmp/composer-setup.php https://getcomposer.org/installer && \
|
||||
curl -o /tmp/composer-setup.sig https://composer.github.io/installer.sig && \
|
||||
php -r "if (hash('SHA384', file_get_contents('/tmp/composer-setup.php')) !== trim(file_get_contents('/tmp/composer-setup.sig'))) { unlink('/tmp/composer-setup.php'); echo 'Invalid installer' . PHP_EOL; exit(1); }" && \
|
||||
chmod +x /tmp/composer-setup.php && \
|
||||
php /tmp/composer-setup.php && \
|
||||
mv composer.phar /usr/local/bin/composer && \
|
||||
rm -f /tmp/composer-setup.{php,sig}
|
||||
run curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
|
||||
|
||||
ADD . /var/www/firefly-iii
|
||||
RUN chown -R www-data:www-data /var/www/
|
||||
RUN cd /var/www && composer create-project grumpydictator/firefly-iii --no-dev --prefer-dist firefly-iii 4.6.4
|
||||
COPY docker/entrypoint.sh /var/www/firefly-iii/docker/entrypoint.sh
|
||||
ADD docker/apache-firefly.conf /etc/apache2/sites-available/000-default.conf
|
||||
|
||||
USER www-data
|
||||
RUN chown -R www-data:www-data /var/www && chmod -R 775 /var/www/firefly-iii/storage
|
||||
|
||||
WORKDIR /var/www/firefly-iii
|
||||
|
||||
RUN composer install --no-scripts --no-dev
|
||||
|
||||
USER root
|
||||
|
||||
EXPOSE 80
|
||||
ENTRYPOINT ["/var/www/firefly-iii/docker/entrypoint.sh"]
|
||||
|
@ -54,6 +54,8 @@ class CreateImport extends Command
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the command.
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.ExcessiveMethodLength) // cannot be helped
|
||||
* @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's five exactly.
|
||||
*/
|
||||
@ -133,6 +135,8 @@ class CreateImport extends Command
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify user inserts correct arguments.
|
||||
*
|
||||
* @return bool
|
||||
* @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's five exactly.
|
||||
*/
|
||||
|
@ -51,7 +51,7 @@ class Import extends Command
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Run the import routine.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
@ -91,6 +91,8 @@ class Import extends Command
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if job is valid to be imported.
|
||||
*
|
||||
* @param ImportJob $job
|
||||
*
|
||||
* @return bool
|
||||
|
@ -20,22 +20,21 @@ use FireflyIII\Models\AccountMeta;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Models\BudgetLimit;
|
||||
use FireflyIII\Models\LimitRepetition;
|
||||
use FireflyIII\Models\PiggyBankEvent;
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Models\TransactionType;
|
||||
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Database\Query\JoinClause;
|
||||
use Illuminate\Database\QueryException;
|
||||
use Illuminate\Support\Collection;
|
||||
use Log;
|
||||
use Preferences;
|
||||
use Schema;
|
||||
use Steam;
|
||||
|
||||
/**
|
||||
* Class UpgradeDatabase
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.CouplingBetweenObjects) // it just touches a lot of things.
|
||||
*
|
||||
* @package FireflyIII\Console\Commands
|
||||
@ -71,76 +70,26 @@ class UpgradeDatabase extends Command
|
||||
{
|
||||
$this->setTransactionIdentifier();
|
||||
$this->migrateRepetitions();
|
||||
$this->repairPiggyBanks();
|
||||
$this->updateAccountCurrencies();
|
||||
$this->updateJournalCurrencies();
|
||||
$this->currencyInfoToTransactions();
|
||||
$this->verifyCurrencyInfo();
|
||||
$this->line('Updating currency information..');
|
||||
$this->updateTransferCurrencies();
|
||||
$this->updateOtherCurrencies();
|
||||
$this->info('Firefly III database is up to date.');
|
||||
|
||||
return;
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Moves the currency id info to the transaction instead of the journal.
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.CyclomaticComplexity) // cannot be helped.
|
||||
* Migrate budget repetitions to new format where the end date is in the budget limit as well,
|
||||
* making the limit_repetition table obsolete.
|
||||
*/
|
||||
private function currencyInfoToTransactions()
|
||||
public function migrateRepetitions(): void
|
||||
{
|
||||
$count = 0;
|
||||
$set = TransactionJournal::with('transactions')->get();
|
||||
/** @var TransactionJournal $journal */
|
||||
foreach ($set as $journal) {
|
||||
/** @var Transaction $transaction */
|
||||
foreach ($journal->transactions as $transaction) {
|
||||
if (is_null($transaction->transaction_currency_id)) {
|
||||
$transaction->transaction_currency_id = $journal->transaction_currency_id;
|
||||
$transaction->save();
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
|
||||
// read and use the foreign amounts when present.
|
||||
if ($journal->hasMeta('foreign_amount')) {
|
||||
$amount = Steam::positive($journal->getMeta('foreign_amount'));
|
||||
|
||||
// update both transactions:
|
||||
foreach ($journal->transactions as $transaction) {
|
||||
$transaction->foreign_amount = $amount;
|
||||
if (bccomp($transaction->amount, '0') === -1) {
|
||||
// update with negative amount:
|
||||
$transaction->foreign_amount = bcmul($amount, '-1');
|
||||
}
|
||||
// set foreign currency id:
|
||||
$transaction->foreign_currency_id = intval($journal->getMeta('foreign_currency_id'));
|
||||
$transaction->save();
|
||||
}
|
||||
$journal->deleteMeta('foreign_amount');
|
||||
$journal->deleteMeta('foreign_currency_id');
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$this->line(sprintf('Updated currency information for %d transactions', $count));
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate budget repetitions to new format.
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.CyclomaticComplexity) // it's 5.
|
||||
*/
|
||||
private function migrateRepetitions()
|
||||
{
|
||||
if (!Schema::hasTable('budget_limits')) {
|
||||
return;
|
||||
}
|
||||
// get all budget limits with end_date NULL
|
||||
$set = BudgetLimit::whereNull('end_date')->get();
|
||||
if ($set->count() > 0) {
|
||||
$this->line(sprintf('Found %d budget limit(s) to update', $set->count()));
|
||||
}
|
||||
/** @var BudgetLimit $budgetLimit */
|
||||
foreach ($set as $budgetLimit) {
|
||||
// get limit repetition (should be just one):
|
||||
/** @var LimitRepetition $repetition */
|
||||
$repetition = $budgetLimit->limitrepetitions()->first();
|
||||
if (!is_null($repetition)) {
|
||||
@ -150,59 +99,30 @@ class UpgradeDatabase extends Command
|
||||
$repetition->delete();
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure there are only transfers linked to piggy bank events.
|
||||
* This method gives all transactions which are part of a split journal (so more than 2) a sort of "order" so they are easier
|
||||
* to easier to match to their counterpart. When a journal is split, it has two or three transactions: -3, -4 and -5 for example.
|
||||
*
|
||||
* @SuppressWarnings(PHPMD.CyclomaticComplexity) // cannot be helped.
|
||||
* In the database this is reflected as 6 transactions: -3/+3, -4/+4, -5/+5.
|
||||
*
|
||||
* When either of these are the same amount, FF3 can't keep them apart: +3/-3, +3/-3, +3/-3. This happens more often than you would
|
||||
* think. So each set gets a number (1,2,3) to keep them apart.
|
||||
*/
|
||||
private function repairPiggyBanks()
|
||||
{
|
||||
// if table does not exist, return false
|
||||
if (!Schema::hasTable('piggy_bank_events')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$set = PiggyBankEvent::with(['PiggyBank', 'TransactionJournal', 'TransactionJournal.TransactionType'])->get();
|
||||
/** @var PiggyBankEvent $event */
|
||||
foreach ($set as $event) {
|
||||
|
||||
if (is_null($event->transaction_journal_id)) {
|
||||
continue;
|
||||
}
|
||||
/** @var TransactionJournal $journal */
|
||||
$journal = $event->transactionJournal()->first();
|
||||
if (is_null($journal)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$type = $journal->transactionType->type;
|
||||
if ($type !== TransactionType::TRANSFER) {
|
||||
$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 is strangely complex, because the HAVING modifier is a no-no. And subqueries in Laravel are weird.
|
||||
*/
|
||||
private function setTransactionIdentifier()
|
||||
public function setTransactionIdentifier(): void
|
||||
{
|
||||
// if table does not exist, return false
|
||||
if (!Schema::hasTable('transaction_journals')) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
$subQuery = TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
|
||||
->whereNull('transaction_journals.deleted_at')
|
||||
->whereNull('transactions.deleted_at')
|
||||
->groupBy(['transaction_journals.id'])
|
||||
->select(['transaction_journals.id', DB::raw('COUNT(transactions.id) AS t_count')]);
|
||||
|
||||
$subQuery = TransactionJournal::leftJoin('transactions', 'transactions.transaction_journal_id', '=', 'transaction_journals.id')
|
||||
->whereNull('transaction_journals.deleted_at')
|
||||
->whereNull('transactions.deleted_at')
|
||||
->groupBy(['transaction_journals.id'])
|
||||
->select(['transaction_journals.id', DB::raw('COUNT(transactions.id) AS t_count')]);
|
||||
$result = DB::table(DB::raw('(' . $subQuery->toSql() . ') AS derived'))
|
||||
->mergeBindings($subQuery->getQuery())
|
||||
->where('t_count', '>', 2)
|
||||
@ -210,55 +130,172 @@ class UpgradeDatabase extends Command
|
||||
$journalIds = array_unique($result->pluck('id')->toArray());
|
||||
|
||||
foreach ($journalIds as $journalId) {
|
||||
$this->updateJournal(intval($journalId));
|
||||
$this->updateJournalidentifiers(intval($journalId));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure all accounts have proper currency info.
|
||||
* Each (asset) account must have a reference to a preferred currency. If the account does not have one, it's forced upon the account.
|
||||
*/
|
||||
private function updateAccountCurrencies()
|
||||
public function updateAccountCurrencies(): void
|
||||
{
|
||||
$accounts = Account::leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id')
|
||||
->whereIn('account_types.type', [AccountType::DEFAULT, AccountType::ASSET])->get(['accounts.*']);
|
||||
|
||||
/** @var Account $account */
|
||||
foreach ($accounts as $account) {
|
||||
// get users preference, fall back to system pref.
|
||||
$defaultCurrencyCode = Preferences::getForUser($account->user, 'currencyPreference', config('firefly.default_currency', 'EUR'))->data;
|
||||
$defaultCurrency = TransactionCurrency::where('code', $defaultCurrencyCode)->first();
|
||||
$accountCurrency = intval($account->getMeta('currency_id'));
|
||||
$openingBalance = $account->getOpeningBalance();
|
||||
$obCurrency = intval($openingBalance->transaction_currency_id);
|
||||
$accounts->each(
|
||||
function (Account $account) {
|
||||
// get users preference, fall back to system pref.
|
||||
$defaultCurrencyCode = Preferences::getForUser($account->user, 'currencyPreference', config('firefly.default_currency', 'EUR'))->data;
|
||||
$defaultCurrency = TransactionCurrency::where('code', $defaultCurrencyCode)->first();
|
||||
$accountCurrency = intval($account->getMeta('currency_id'));
|
||||
$openingBalance = $account->getOpeningBalance();
|
||||
$obCurrency = intval($openingBalance->transaction_currency_id);
|
||||
|
||||
// both 0? set to default currency:
|
||||
if ($accountCurrency === 0 && $obCurrency === 0) {
|
||||
AccountMeta::create(['account_id' => $account->id, 'name' => 'currency_id', 'data' => $defaultCurrency->id]);
|
||||
$this->line(sprintf('Account #%d ("%s") now has a currency setting (%s).', $account->id, $account->name, $defaultCurrencyCode));
|
||||
continue;
|
||||
// both 0? set to default currency:
|
||||
if ($accountCurrency === 0 && $obCurrency === 0) {
|
||||
AccountMeta::create(['account_id' => $account->id, 'name' => 'currency_id', 'data' => $defaultCurrency->id]);
|
||||
$this->line(sprintf('Account #%d ("%s") now has a currency setting (%s).', $account->id, $account->name, $defaultCurrencyCode));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// account is set to 0, opening balance is not?
|
||||
if ($accountCurrency === 0 && $obCurrency > 0) {
|
||||
AccountMeta::create(['account_id' => $account->id, 'name' => 'currency_id', 'data' => $obCurrency]);
|
||||
$this->line(sprintf('Account #%d ("%s") now has a currency setting (%s).', $account->id, $account->name, $defaultCurrencyCode));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// do not match and opening balance id is not null.
|
||||
if ($accountCurrency !== $obCurrency && $openingBalance->id > 0) {
|
||||
// update opening balance:
|
||||
$openingBalance->transaction_currency_id = $accountCurrency;
|
||||
$openingBalance->save();
|
||||
$this->line(sprintf('Account #%d ("%s") now has a correct currency for opening balance.', $account->id, $account->name));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
// account is set to 0, opening balance is not?
|
||||
if ($accountCurrency === 0 && $obCurrency > 0) {
|
||||
AccountMeta::create(['account_id' => $account->id, 'name' => 'currency_id', 'data' => $obCurrency]);
|
||||
$this->line(sprintf('Account #%d ("%s") now has a currency setting (%s).', $account->id, $account->name, $defaultCurrencyCode));
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* This routine verifies that withdrawals, deposits and opening balances have the correct currency settings for
|
||||
* the accounts they are linked to.
|
||||
*
|
||||
* Both source and destination must match the respective currency preference of the related asset account.
|
||||
* So FF3 must verify all transactions.
|
||||
*/
|
||||
public function updateOtherCurrencies(): void
|
||||
{
|
||||
/** @var CurrencyRepositoryInterface $repository */
|
||||
$repository = app(CurrencyRepositoryInterface::class);
|
||||
$set = TransactionJournal
|
||||
::leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
|
||||
->whereIn('transaction_types.type', [TransactionType::WITHDRAWAL, TransactionType::DEPOSIT, TransactionType::OPENING_BALANCE])
|
||||
->get(['transaction_journals.*']);
|
||||
|
||||
$set->each(
|
||||
function (TransactionJournal $journal) use ($repository) {
|
||||
// get the transaction with the asset account in it:
|
||||
/** @var Transaction $transaction */
|
||||
$transaction = $journal->transactions()
|
||||
->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id')
|
||||
->leftJoin('account_types', 'account_types.id', '=', 'accounts.account_type_id')
|
||||
->whereIn('account_types.type', [AccountType::DEFAULT, AccountType::ASSET])->first(['transactions.*']);
|
||||
/** @var Account $account */
|
||||
$account = $transaction->account;
|
||||
$currency = $repository->find(intval($account->getMeta('currency_id')));
|
||||
$transactions = $journal->transactions()->get();
|
||||
$transactions->each(
|
||||
function (Transaction $transaction) use ($currency) {
|
||||
if (is_null($transaction->transaction_currency_id)) {
|
||||
$transaction->transaction_currency_id = $currency->id;
|
||||
$transaction->save();
|
||||
}
|
||||
|
||||
// when mismatch in transaction:
|
||||
if ($transaction->transaction_currency_id !== $currency->id) {
|
||||
$transaction->foreign_currency_id = $transaction->transaction_currency_id;
|
||||
$transaction->foreign_amount = $transaction->amount;
|
||||
$transaction->transaction_currency_id = $currency->id;
|
||||
$transaction->save();
|
||||
}
|
||||
}
|
||||
);
|
||||
// also update the journal, of course:
|
||||
$journal->transaction_currency_id = $currency->id;
|
||||
$journal->save();
|
||||
}
|
||||
);
|
||||
|
||||
// do not match:
|
||||
if ($accountCurrency !== $obCurrency) {
|
||||
// update opening balance:
|
||||
$openingBalance->transaction_currency_id = $accountCurrency;
|
||||
$openingBalance->save();
|
||||
$this->line(sprintf('Account #%d ("%s") now has a correct currency for opening balance.', $account->id, $account->name));
|
||||
continue;
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* This routine verifies that transfers have the correct currency settings for the accounts they are linked to.
|
||||
* For transfers, this is can be a destructive routine since we FORCE them into a currency setting whether they
|
||||
* like it or not. Previous routines MUST have set the currency setting for both accounts for this to work.
|
||||
*
|
||||
* A transfer always has the
|
||||
*
|
||||
* Both source and destination must match the respective currency preference. So FF3 must verify ALL
|
||||
* transactions.
|
||||
*/
|
||||
public function updateTransferCurrencies()
|
||||
{
|
||||
$set = TransactionJournal
|
||||
::leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
|
||||
->where('transaction_types.type', TransactionType::TRANSFER)
|
||||
->get(['transaction_journals.*']);
|
||||
|
||||
$set->each(
|
||||
function (TransactionJournal $transfer) {
|
||||
// select all "source" transactions:
|
||||
/** @var Collection $transactions */
|
||||
$transactions = $transfer->transactions()->where('amount', '<', 0)->get();
|
||||
$transactions->each(
|
||||
function (Transaction $transaction) {
|
||||
$this->updateTransactionCurrency($transaction);
|
||||
$this->updateJournalCurrency($transaction);
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// opening balance 0, account not zero? just continue:
|
||||
// both are equal, just continue:
|
||||
|
||||
/**
|
||||
* This method makes sure that the transaction journal uses the currency given in the transaction.
|
||||
*
|
||||
* @param Transaction $transaction
|
||||
*/
|
||||
private function updateJournalCurrency(Transaction $transaction): void
|
||||
{
|
||||
/** @var CurrencyRepositoryInterface $repository */
|
||||
$repository = app(CurrencyRepositoryInterface::class);
|
||||
$currency = $repository->find(intval($transaction->account->getMeta('currency_id')));
|
||||
$journal = $transaction->transactionJournal;
|
||||
|
||||
if (!(intval($currency->id) === intval($journal->transaction_currency_id))) {
|
||||
$this->line(
|
||||
sprintf(
|
||||
'Transfer #%d ("%s") has been updated to use %s instead of %s.', $journal->id, $journal->description, $currency->code,
|
||||
$journal->transactionCurrency->code
|
||||
)
|
||||
);
|
||||
$journal->transaction_currency_id = $currency->id;
|
||||
$journal->save();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -267,7 +304,7 @@ class UpgradeDatabase extends Command
|
||||
*
|
||||
* @param int $journalId
|
||||
*/
|
||||
private function updateJournal(int $journalId)
|
||||
private function updateJournalidentifiers(int $journalId): void
|
||||
{
|
||||
$identifier = 0;
|
||||
$processed = [];
|
||||
@ -295,121 +332,128 @@ class UpgradeDatabase extends Command
|
||||
if (!is_null($opposing)) {
|
||||
// give both a new identifier:
|
||||
$transaction->identifier = $identifier;
|
||||
$opposing->identifier = $identifier;
|
||||
$transaction->save();
|
||||
$opposing->identifier = $identifier;
|
||||
$opposing->save();
|
||||
$processed[] = $transaction->id;
|
||||
$processed[] = $opposing->id;
|
||||
}
|
||||
$identifier++;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes sure that withdrawals, deposits and transfers have
|
||||
* a currency setting matching their respective accounts
|
||||
*/
|
||||
private function updateJournalCurrencies()
|
||||
{
|
||||
$types = [
|
||||
TransactionType::WITHDRAWAL => '<',
|
||||
TransactionType::DEPOSIT => '>',
|
||||
];
|
||||
$repository = app(CurrencyRepositoryInterface::class);
|
||||
$notification = '%s #%d uses %s but should use %s. It has been updated. Please verify this in Firefly III.';
|
||||
$transfer = 'Transfer #%d has been updated to use the correct currencies. Please verify this in Firefly III.';
|
||||
$driver = DB::connection()->getDriverName();
|
||||
$pgsql = ['pgsql', 'postgresql'];
|
||||
|
||||
foreach ($types as $type => $operator) {
|
||||
$query = TransactionJournal
|
||||
::leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')->leftJoin(
|
||||
'transactions', function (JoinClause $join) use ($operator) {
|
||||
$join->on('transaction_journals.id', '=', 'transactions.transaction_journal_id')->where('transactions.amount', $operator, '0');
|
||||
}
|
||||
)
|
||||
->leftJoin('accounts', 'accounts.id', '=', 'transactions.account_id')
|
||||
->leftJoin('account_meta', 'account_meta.account_id', '=', 'accounts.id')
|
||||
->where('transaction_types.type', $type)
|
||||
->where('account_meta.name', 'currency_id');
|
||||
if (in_array($driver, $pgsql)) {
|
||||
$query->where('transaction_journals.transaction_currency_id', '!=', DB::raw('cast(account_meta.data as int)'));
|
||||
}
|
||||
if (!in_array($driver, $pgsql)) {
|
||||
$query->where('transaction_journals.transaction_currency_id', '!=', DB::raw('account_meta.data'));
|
||||
}
|
||||
|
||||
$set = $query->get(['transaction_journals.*', 'account_meta.data as expected_currency_id', 'transactions.amount as transaction_amount']);
|
||||
/** @var TransactionJournal $journal */
|
||||
foreach ($set as $journal) {
|
||||
$expectedCurrency = $repository->find(intval($journal->expected_currency_id));
|
||||
$line = sprintf($notification, $type, $journal->id, $journal->transactionCurrency->code, $expectedCurrency->code);
|
||||
|
||||
$journal->setMeta('foreign_amount', $journal->transaction_amount);
|
||||
$journal->setMeta('foreign_currency_id', $journal->transaction_currency_id);
|
||||
$journal->transaction_currency_id = $expectedCurrency->id;
|
||||
$journal->save();
|
||||
$this->line($line);
|
||||
}
|
||||
}
|
||||
/*
|
||||
* For transfers it's slightly different. Both source and destination
|
||||
* must match the respective currency preference. So we must verify ALL
|
||||
* transactions.
|
||||
*/
|
||||
$set = TransactionJournal
|
||||
::leftJoin('transaction_types', 'transaction_types.id', '=', 'transaction_journals.transaction_type_id')
|
||||
->where('transaction_types.type', TransactionType::TRANSFER)
|
||||
->get(['transaction_journals.*']);
|
||||
/** @var TransactionJournal $journal */
|
||||
foreach ($set as $journal) {
|
||||
$updated = false;
|
||||
/** @var Transaction $sourceTransaction */
|
||||
$sourceTransaction = $journal->transactions()->where('amount', '<', 0)->first();
|
||||
$sourceCurrency = $repository->find(intval($sourceTransaction->account->getMeta('currency_id')));
|
||||
|
||||
if ($sourceCurrency->id !== $journal->transaction_currency_id) {
|
||||
$updated = true;
|
||||
$journal->transaction_currency_id = $sourceCurrency->id;
|
||||
$journal->save();
|
||||
}
|
||||
|
||||
// destination
|
||||
$destinationTransaction = $journal->transactions()->where('amount', '>', 0)->first();
|
||||
$destinationCurrency = $repository->find(intval($destinationTransaction->account->getMeta('currency_id')));
|
||||
|
||||
if ($destinationCurrency->id !== $journal->transaction_currency_id) {
|
||||
$updated = true;
|
||||
$journal->deleteMeta('foreign_amount');
|
||||
$journal->deleteMeta('foreign_currency_id');
|
||||
$journal->setMeta('foreign_amount', $destinationTransaction->amount);
|
||||
$journal->setMeta('foreign_currency_id', $destinationCurrency->id);
|
||||
}
|
||||
if ($updated) {
|
||||
$line = sprintf($transfer, $journal->id);
|
||||
$this->line($line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method makes sure that the tranaction uses the same currency as the source account does.
|
||||
* If not, the currency is updated to include a reference to its original currency as the "foreign" currency.
|
||||
*
|
||||
* The transaction that is sent to this function MUST be the source transaction (amount negative).
|
||||
*
|
||||
* @param Transaction $transaction
|
||||
*/
|
||||
private function verifyCurrencyInfo()
|
||||
private function updateTransactionCurrency(Transaction $transaction): void
|
||||
{
|
||||
$count = 0;
|
||||
$transactions = Transaction::get();
|
||||
/** @var Transaction $transaction */
|
||||
foreach ($transactions as $transaction) {
|
||||
$currencyId = intval($transaction->transaction_currency_id);
|
||||
$foreignId = intval($transaction->foreign_currency_id);
|
||||
if ($currencyId === $foreignId) {
|
||||
$transaction->foreign_currency_id = null;
|
||||
$transaction->foreign_amount = null;
|
||||
$transaction->save();
|
||||
$count++;
|
||||
}
|
||||
/** @var CurrencyRepositoryInterface $repository */
|
||||
$repository = app(CurrencyRepositoryInterface::class);
|
||||
$currency = $repository->find(intval($transaction->account->getMeta('currency_id')));
|
||||
|
||||
// has no currency ID? Must have, so fill in using account preference:
|
||||
if (is_null($transaction->transaction_currency_id)) {
|
||||
$transaction->transaction_currency_id = $currency->id;
|
||||
Log::debug(sprintf('Transaction #%d has no currency setting, now set to %s', $transaction->id, $currency->code));
|
||||
$transaction->save();
|
||||
}
|
||||
$this->line(sprintf('Updated currency information for %d transactions', $count));
|
||||
|
||||
// does not match the source account (see above)? Can be fixed
|
||||
// when mismatch in transaction and NO foreign amount is set:
|
||||
if ($transaction->transaction_currency_id !== $currency->id && is_null($transaction->foreign_amount)) {
|
||||
Log::debug(
|
||||
sprintf(
|
||||
'Transaction #%d has a currency setting (#%d) that should be #%d. Amount remains %s, currency is changed.', $transaction->id,
|
||||
$transaction->transaction_currency_id, $currency->id, $transaction->amount
|
||||
)
|
||||
);
|
||||
$transaction->transaction_currency_id = $currency->id;
|
||||
$transaction->save();
|
||||
}
|
||||
|
||||
// grab opposing transaction:
|
||||
/** @var TransactionJournal $journal */
|
||||
$journal = $transaction->transactionJournal;
|
||||
/** @var Transaction $opposing */
|
||||
$opposing = $journal->transactions()->where('amount', '>', 0)->where('identifier', $transaction->identifier)->first();
|
||||
$opposingCurrency = $repository->find(intval($opposing->account->getMeta('currency_id')));
|
||||
|
||||
if (is_null($opposingCurrency->id)) {
|
||||
Log::error(sprintf('Account #%d ("%s") must have currency preference but has none.', $opposing->account->id, $opposing->account->name));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// if the destination account currency is the same, both foreign_amount and foreign_currency_id must be NULL for both transactions:
|
||||
if ($opposingCurrency->id === $currency->id) {
|
||||
// update both transactions to match:
|
||||
$transaction->foreign_amount = null;
|
||||
$transaction->foreign_currency_id = null;
|
||||
$opposing->foreign_amount = null;
|
||||
$opposing->foreign_currency_id = null;
|
||||
$opposing->transaction_currency_id = $currency->id;
|
||||
$transaction->save();
|
||||
$opposing->save();
|
||||
Log::debug(sprintf('Cleaned up transaction #%d and #%d', $transaction->id, $opposing->id));
|
||||
|
||||
return;
|
||||
}
|
||||
// if destination account currency is different, both transactions must have this currency as foreign currency id.
|
||||
if ($opposingCurrency->id !== $currency->id) {
|
||||
$transaction->foreign_currency_id = $opposingCurrency->id;
|
||||
$opposing->foreign_currency_id = $opposingCurrency->id;
|
||||
$transaction->save();
|
||||
$opposing->save();
|
||||
Log::debug(sprintf('Verified foreign currency ID of transaction #%d and #%d', $transaction->id, $opposing->id));
|
||||
}
|
||||
|
||||
// if foreign amount of one is null and the other is not, use this to restore:
|
||||
if (is_null($transaction->foreign_amount) && !is_null($opposing->foreign_amount)) {
|
||||
$transaction->foreign_amount = bcmul(strval($opposing->foreign_amount), '-1');
|
||||
$transaction->save();
|
||||
Log::debug(sprintf('Restored foreign amount of transaction (1) #%d to %s', $transaction->id, $transaction->foreign_amount));
|
||||
}
|
||||
|
||||
// if foreign amount of one is null and the other is not, use this to restore (other way around)
|
||||
if (is_null($opposing->foreign_amount) && !is_null($transaction->foreign_amount)) {
|
||||
$opposing->foreign_amount = bcmul(strval($transaction->foreign_amount), '-1');
|
||||
$opposing->save();
|
||||
Log::debug(sprintf('Restored foreign amount of transaction (2) #%d to %s', $opposing->id, $opposing->foreign_amount));
|
||||
}
|
||||
|
||||
// when both are zero, try to grab it from journal:
|
||||
if (is_null($opposing->foreign_amount) && is_null($transaction->foreign_amount)) {
|
||||
|
||||
$foreignAmount = $journal->getMeta('foreign_amount');
|
||||
if (is_null($foreignAmount)) {
|
||||
Log::debug(sprintf('Journal #%d has missing foreign currency data, forced to do 1:1 conversion :(.', $transaction->transaction_journal_id));
|
||||
$transaction->foreign_amount = bcmul(strval($transaction->amount), '-1');
|
||||
$opposing->foreign_amount = bcmul(strval($opposing->amount), '-1');
|
||||
$transaction->save();
|
||||
$opposing->save();
|
||||
|
||||
return;
|
||||
}
|
||||
$foreignPositive = app('steam')->positive(strval($foreignAmount));
|
||||
Log::debug(
|
||||
sprintf(
|
||||
'Journal #%d has missing foreign currency info, try to restore from meta-data ("%s").', $transaction->transaction_journal_id, $foreignAmount
|
||||
)
|
||||
);
|
||||
$transaction->foreign_amount = bcmul($foreignPositive, '-1');
|
||||
$opposing->foreign_amount = $foreignPositive;
|
||||
$transaction->save();
|
||||
$opposing->save();
|
||||
}
|
||||
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
@ -84,6 +84,9 @@ class UpgradeFireflyInstructions extends Command
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render instructions.
|
||||
*/
|
||||
private function installInstructions()
|
||||
{
|
||||
/** @var string $version */
|
||||
@ -102,7 +105,7 @@ class UpgradeFireflyInstructions extends Command
|
||||
$this->boxed('');
|
||||
if (is_null($text)) {
|
||||
|
||||
$this->boxed(sprintf('Thank you for installin Firefly III, v%s!', $version));
|
||||
$this->boxed(sprintf('Thank you for installing Firefly III, v%s!', $version));
|
||||
$this->boxedInfo('There are no extra installation instructions.');
|
||||
$this->boxed('Firefly III should be ready for use.');
|
||||
$this->boxed('');
|
||||
@ -131,6 +134,9 @@ class UpgradeFireflyInstructions extends Command
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Render upgrade instructions.
|
||||
*/
|
||||
private function updateInstructions()
|
||||
{
|
||||
/** @var string $version */
|
||||
|
@ -1,4 +1,14 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* UseEncryption.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
@ -16,6 +26,8 @@ use Illuminate\Support\Str;
|
||||
|
||||
/**
|
||||
* Class UseEncryption
|
||||
*
|
||||
* @package FireflyIII\Console\Commands
|
||||
*/
|
||||
class UseEncryption extends Command
|
||||
{
|
||||
@ -46,7 +58,6 @@ class UseEncryption extends Command
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
//
|
||||
$this->handleObjects('Account', 'name', 'encrypted');
|
||||
$this->handleObjects('Bill', 'name', 'name_encrypted');
|
||||
$this->handleObjects('Bill', 'match', 'match_encrypted');
|
||||
@ -57,6 +68,8 @@ class UseEncryption extends Command
|
||||
}
|
||||
|
||||
/**
|
||||
* Run each object and encrypt them (or not).
|
||||
*
|
||||
* @param string $class
|
||||
* @param string $field
|
||||
* @param string $indicator
|
||||
|
@ -17,6 +17,7 @@ use Crypt;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Models\Budget;
|
||||
use FireflyIII\Models\PiggyBankEvent;
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Models\TransactionType;
|
||||
@ -94,6 +95,40 @@ class VerifyDatabase extends Command
|
||||
// report on journals with the wrong types of accounts.
|
||||
$this->reportIncorrectJournals();
|
||||
|
||||
// report (and fix) piggy banks
|
||||
$this->repairPiggyBanks();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure there are only transfers linked to piggy bank events.
|
||||
*/
|
||||
private function repairPiggyBanks(): void
|
||||
{
|
||||
$set = PiggyBankEvent::with(['PiggyBank', 'TransactionJournal', 'TransactionJournal.TransactionType'])->get();
|
||||
$set->each(
|
||||
function (PiggyBankEvent $event) {
|
||||
if (is_null($event->transaction_journal_id)) {
|
||||
return true;
|
||||
}
|
||||
/** @var TransactionJournal $journal */
|
||||
$journal = $event->transactionJournal()->first();
|
||||
if (is_null($journal)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$type = $journal->transactionType->type;
|
||||
if ($type !== TransactionType::TRANSFER) {
|
||||
$event->transaction_journal_id = null;
|
||||
$event->save();
|
||||
$this->line(sprintf('Piggy bank #%d was referenced by an invalid event. This has been fixed.', $event->piggy_bank_id));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -169,6 +204,9 @@ class VerifyDatabase extends Command
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Report on journals with bad account types linked to them.
|
||||
*/
|
||||
private function reportIncorrectJournals()
|
||||
{
|
||||
$configuration = [
|
||||
@ -235,7 +273,7 @@ class VerifyDatabase extends Command
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Report on journals without transactions.
|
||||
*/
|
||||
private function reportNoTransactions()
|
||||
{
|
||||
@ -253,6 +291,8 @@ class VerifyDatabase extends Command
|
||||
}
|
||||
|
||||
/**
|
||||
* Report on things with no linked journals.
|
||||
*
|
||||
* @param string $name
|
||||
*/
|
||||
private function reportObject(string $name)
|
||||
@ -324,7 +364,7 @@ class VerifyDatabase extends Command
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* Report on transfers that have budgets.
|
||||
*/
|
||||
private function reportTransfersBudgets()
|
||||
{
|
||||
|
@ -15,6 +15,7 @@ namespace FireflyIII\Export\Collector;
|
||||
|
||||
use Crypt;
|
||||
use Illuminate\Contracts\Encryption\DecryptException;
|
||||
use Illuminate\Contracts\Filesystem\FileNotFoundException;
|
||||
use Log;
|
||||
use Storage;
|
||||
|
||||
@ -50,14 +51,6 @@ class UploadCollector extends BasicCollector implements CollectorInterface
|
||||
public function run(): bool
|
||||
{
|
||||
Log::debug('Going to collect attachments', ['key' => $this->job->key]);
|
||||
|
||||
// file names associated with the old import routine.
|
||||
$this->vintageFormat = sprintf('csv-upload-%d-', $this->job->user->id);
|
||||
|
||||
// collect old upload files (names beginning with "csv-upload".
|
||||
$this->collectVintageUploads();
|
||||
|
||||
// then collect current upload files:
|
||||
$this->collectModernUploads();
|
||||
|
||||
return true;
|
||||
@ -70,7 +63,8 @@ class UploadCollector extends BasicCollector implements CollectorInterface
|
||||
*/
|
||||
private function collectModernUploads(): bool
|
||||
{
|
||||
$set = $this->job->user->importJobs()->where('status', 'import_complete')->get(['import_jobs.*']);
|
||||
$set = $this->job->user->importJobs()->whereIn('status', ['import_complete', 'finished'])->get(['import_jobs.*']);
|
||||
Log::debug(sprintf('Found %d import jobs', $set->count()));
|
||||
$keys = [];
|
||||
if ($set->count() > 0) {
|
||||
$keys = $set->pluck('key')->toArray();
|
||||
@ -83,59 +77,6 @@ class UploadCollector extends BasicCollector implements CollectorInterface
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method collects all the uploads that are uploaded using the "old" importer. So from before the summer of 2016.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function collectVintageUploads(): bool
|
||||
{
|
||||
// grab upload directory.
|
||||
$files = $this->uploadDisk->files();
|
||||
|
||||
foreach ($files as $entry) {
|
||||
$this->processVintageUpload($entry);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method tells you when the vintage upload file was actually uploaded.
|
||||
*
|
||||
* @param string $entry
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function getVintageUploadDate(string $entry): string
|
||||
{
|
||||
// this is an original upload.
|
||||
$parts = explode('-', str_replace(['.csv.encrypted', $this->vintageFormat], '', $entry));
|
||||
$originalUpload = intval($parts[1]);
|
||||
$date = date('Y-m-d \a\t H-i-s', $originalUpload);
|
||||
|
||||
return $date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells you if a file name is a vintage upload.
|
||||
*
|
||||
* @param string $entry
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function isVintageImport(string $entry): bool
|
||||
{
|
||||
$len = strlen($this->vintageFormat);
|
||||
// file is part of the old import routine:
|
||||
if (substr($entry, 0, $len) === $this->vintageFormat) {
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
*
|
||||
@ -153,7 +94,7 @@ class UploadCollector extends BasicCollector implements CollectorInterface
|
||||
$content = '';
|
||||
try {
|
||||
$content = Crypt::decrypt($this->uploadDisk->get(sprintf('%s.upload', $key)));
|
||||
} catch (DecryptException $e) {
|
||||
} catch (FileNotFoundException | DecryptException $e) {
|
||||
Log::error(sprintf('Could not decrypt old import file "%s". Skipped because: %s', $key, $e->getMessage()));
|
||||
}
|
||||
|
||||
@ -168,47 +109,4 @@ class UploadCollector extends BasicCollector implements CollectorInterface
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the file is a vintage upload, process it.
|
||||
*
|
||||
* @param string $entry
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function processVintageUpload(string $entry): bool
|
||||
{
|
||||
if ($this->isVintageImport($entry)) {
|
||||
$this->saveVintageImportFile($entry);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* This will store the content of the old vintage upload somewhere.
|
||||
*
|
||||
* @param string $entry
|
||||
*/
|
||||
private function saveVintageImportFile(string $entry)
|
||||
{
|
||||
$content = '';
|
||||
try {
|
||||
$content = Crypt::decrypt($this->uploadDisk->get($entry));
|
||||
} catch (DecryptException $e) {
|
||||
Log::error('Could not decrypt old CSV import file ' . $entry . '. Skipped because ' . $e->getMessage());
|
||||
}
|
||||
|
||||
if (strlen($content) > 0) {
|
||||
// add to export disk.
|
||||
$date = $this->getVintageUploadDate($entry);
|
||||
$file = $this->job->key . '-Old import dated ' . $date . '.csv';
|
||||
$this->exportDisk->put($file, $content);
|
||||
$this->getEntries()->push($file);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Export\Entry;
|
||||
|
||||
use FireflyIII\Models\Transaction;
|
||||
use Steam;
|
||||
|
||||
/**
|
||||
@ -37,24 +38,43 @@ final class Entry
|
||||
{
|
||||
// @formatter:off
|
||||
public $journal_id;
|
||||
public $transaction_id = 0;
|
||||
|
||||
public $date;
|
||||
public $description;
|
||||
|
||||
public $currency_code;
|
||||
public $amount;
|
||||
public $foreign_currency_code = '';
|
||||
public $foreign_amount = '0';
|
||||
|
||||
public $transaction_type;
|
||||
|
||||
public $asset_account_id;
|
||||
public $asset_account_name;
|
||||
public $asset_account_iban;
|
||||
public $asset_account_bic;
|
||||
public $asset_account_number;
|
||||
public $asset_currency_code;
|
||||
|
||||
public $opposing_account_id;
|
||||
public $opposing_account_name;
|
||||
public $opposing_account_iban;
|
||||
public $opposing_account_bic;
|
||||
public $opposing_account_number;
|
||||
public $opposing_currency_code;
|
||||
|
||||
public $budget_id;
|
||||
public $budget_name;
|
||||
|
||||
public $category_id;
|
||||
public $category_name;
|
||||
|
||||
public $bill_id;
|
||||
public $bill_name;
|
||||
|
||||
public $notes;
|
||||
public $tags;
|
||||
// @formatter:on
|
||||
|
||||
/**
|
||||
@ -95,5 +115,75 @@ final class Entry
|
||||
return $entry;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a given transaction (as collected by the collector) into an export entry.
|
||||
*
|
||||
* @param Transaction $transaction
|
||||
*
|
||||
* @return Entry
|
||||
*/
|
||||
public static function fromTransaction(Transaction $transaction): Entry
|
||||
{
|
||||
$entry = new self;
|
||||
$entry->journal_id = $transaction->journal_id;
|
||||
$entry->transaction_id = $transaction->id;
|
||||
$entry->date = $transaction->date->format('Ymd');
|
||||
$entry->description = $transaction->description;
|
||||
if (strlen(strval($transaction->transaction_description)) > 0) {
|
||||
$entry->description = $transaction->transaction_description . '(' . $transaction->description . ')';
|
||||
}
|
||||
$entry->currency_code = $transaction->transactionCurrency->code;
|
||||
$entry->amount = round($transaction->transaction_amount, $transaction->transactionCurrency->decimal_places);
|
||||
|
||||
$entry->foreign_currency_code = is_null($transaction->foreign_currency_id) ? null : $transaction->foreignCurrency->code;
|
||||
$entry->foreign_amount = is_null($transaction->foreign_currency_id)
|
||||
? null
|
||||
: strval(
|
||||
round(
|
||||
$transaction->transaction_foreign_amount, $transaction->foreignCurrency->decimal_places
|
||||
)
|
||||
);
|
||||
|
||||
$entry->transaction_type = $transaction->transaction_type_type;
|
||||
$entry->asset_account_id = $transaction->account_id;
|
||||
$entry->asset_account_name = app('steam')->tryDecrypt($transaction->account_name);
|
||||
$entry->asset_account_iban = $transaction->account_iban;
|
||||
$entry->asset_account_number = $transaction->account_number;
|
||||
$entry->asset_account_bic = $transaction->account_bic;
|
||||
$entry->asset_currency_code = $transaction->account_currency_code;
|
||||
|
||||
$entry->opposing_account_id = $transaction->opposing_account_id;
|
||||
$entry->opposing_account_name = app('steam')->tryDecrypt($transaction->opposing_account_name);
|
||||
$entry->opposing_account_iban = $transaction->opposing_account_iban;
|
||||
$entry->opposing_account_number = $transaction->opposing_account_number;
|
||||
$entry->opposing_account_bic = $transaction->opposing_account_bic;
|
||||
$entry->opposing_currency_code = $transaction->opposing_currency_code;
|
||||
|
||||
/** budget */
|
||||
$entry->budget_id = $transaction->transaction_budget_id;
|
||||
$entry->budget_name = app('steam')->tryDecrypt($transaction->transaction_budget_name);
|
||||
if (is_null($transaction->transaction_budget_id)) {
|
||||
$entry->budget_id = $transaction->transaction_journal_budget_id;
|
||||
$entry->budget_name = app('steam')->tryDecrypt($transaction->transaction_journal_budget_name);
|
||||
}
|
||||
|
||||
/** category */
|
||||
$entry->category_id = $transaction->transaction_category_id;
|
||||
$entry->category_name = app('steam')->tryDecrypt($transaction->transaction_category_name);
|
||||
if (is_null($transaction->transaction_category_id)) {
|
||||
$entry->category_id = $transaction->transaction_journal_category_id;
|
||||
$entry->category_name = app('steam')->tryDecrypt($transaction->transaction_journal_category_name);
|
||||
}
|
||||
|
||||
/** budget */
|
||||
$entry->bill_id = $transaction->bill_id;
|
||||
$entry->bill_name = app('steam')->tryDecrypt($transaction->bill_name);
|
||||
|
||||
$entry->tags = $transaction->tags;
|
||||
$entry->notes = $transaction->notes;
|
||||
|
||||
return $entry;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
339
app/Export/ExpandedProcessor.php
Normal file
339
app/Export/ExpandedProcessor.php
Normal file
@ -0,0 +1,339 @@
|
||||
<?php
|
||||
/**
|
||||
* ExpandedProcessor.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Export;
|
||||
|
||||
|
||||
use Crypt;
|
||||
use DB;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Export\Collector\AttachmentCollector;
|
||||
use FireflyIII\Export\Collector\UploadCollector;
|
||||
use FireflyIII\Export\Entry\Entry;
|
||||
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
|
||||
use FireflyIII\Helpers\Filter\InternalTransferFilter;
|
||||
use FireflyIII\Models\AccountMeta;
|
||||
use FireflyIII\Models\ExportJob;
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Models\TransactionJournalMeta;
|
||||
use FireflyIII\Repositories\Currency\CurrencyRepositoryInterface;
|
||||
use Illuminate\Support\Collection;
|
||||
use Log;
|
||||
use Storage;
|
||||
use ZipArchive;
|
||||
|
||||
/**
|
||||
* Class ExpandedProcessor
|
||||
*
|
||||
* @package FireflyIII\Export
|
||||
*/
|
||||
class ExpandedProcessor implements ProcessorInterface
|
||||
{
|
||||
|
||||
/** @var Collection */
|
||||
public $accounts;
|
||||
/** @var string */
|
||||
public $exportFormat;
|
||||
/** @var bool */
|
||||
public $includeAttachments;
|
||||
/** @var bool */
|
||||
public $includeOldUploads;
|
||||
/** @var ExportJob */
|
||||
public $job;
|
||||
/** @var array */
|
||||
public $settings;
|
||||
/** @var Collection */
|
||||
private $exportEntries;
|
||||
/** @var Collection */
|
||||
private $files;
|
||||
/** @var Collection */
|
||||
private $journals;
|
||||
|
||||
/**
|
||||
* Processor constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->journals = new Collection;
|
||||
$this->exportEntries = new Collection;
|
||||
$this->files = new Collection;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function collectAttachments(): bool
|
||||
{
|
||||
/** @var AttachmentCollector $attachmentCollector */
|
||||
$attachmentCollector = app(AttachmentCollector::class);
|
||||
$attachmentCollector->setJob($this->job);
|
||||
$attachmentCollector->setDates($this->settings['startDate'], $this->settings['endDate']);
|
||||
$attachmentCollector->run();
|
||||
$this->files = $this->files->merge($attachmentCollector->getEntries());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function collectJournals(): bool
|
||||
{
|
||||
// use journal collector thing.
|
||||
/** @var JournalCollectorInterface $collector */
|
||||
$collector = app(JournalCollectorInterface::class);
|
||||
$collector->setAccounts($this->accounts)->setRange($this->settings['startDate'], $this->settings['endDate'])
|
||||
->withOpposingAccount()->withBudgetInformation()->withCategoryInformation()
|
||||
->removeFilter(InternalTransferFilter::class);
|
||||
$transactions = $collector->getJournals();
|
||||
// get some more meta data for each entry:
|
||||
$ids = $transactions->pluck('journal_id')->toArray();
|
||||
$assetIds = $transactions->pluck('account_id')->toArray();
|
||||
$opposingIds = $transactions->pluck('opposing_account_id')->toArray();
|
||||
$notes = $this->getNotes($ids);
|
||||
$tags = $this->getTags($ids);
|
||||
$ibans = $this->getIbans($assetIds) + $this->getIbans($opposingIds);
|
||||
$currencies = $this->getAccountCurrencies($ibans);
|
||||
$transactions->each(
|
||||
function (Transaction $transaction) use ($notes, $tags, $ibans, $currencies) {
|
||||
$journalId = intval($transaction->journal_id);
|
||||
$accountId = intval($transaction->account_id);
|
||||
$opposingId = intval($transaction->opposing_account_id);
|
||||
$currencyId = $ibans[$accountId]['currency_id'] ?? 0;
|
||||
$opposingCurrencyId = $ibans[$opposingId]['currency_id'] ?? 0;
|
||||
$transaction->notes = $notes[$journalId] ?? '';
|
||||
$transaction->tags = join(',', $tags[$journalId] ?? []);
|
||||
$transaction->account_number = $ibans[$accountId]['accountNumber'] ?? '';
|
||||
$transaction->account_bic = $ibans[$accountId]['BIC'] ?? '';
|
||||
$transaction->account_currency_code = $currencies[$currencyId] ?? '';
|
||||
$transaction->opposing_account_number = $ibans[$opposingId]['accountNumber'] ?? '';
|
||||
$transaction->opposing_account_bic = $ibans[$opposingId]['BIC'] ?? '';
|
||||
$transaction->opposing_currency_code = $currencies[$opposingCurrencyId] ?? '';
|
||||
|
||||
}
|
||||
);
|
||||
|
||||
$this->journals = $transactions;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function collectOldUploads(): bool
|
||||
{
|
||||
/** @var UploadCollector $uploadCollector */
|
||||
$uploadCollector = app(UploadCollector::class);
|
||||
$uploadCollector->setJob($this->job);
|
||||
$uploadCollector->run();
|
||||
|
||||
$this->files = $this->files->merge($uploadCollector->getEntries());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function convertJournals(): bool
|
||||
{
|
||||
$this->journals->each(
|
||||
function (Transaction $transaction) {
|
||||
$this->exportEntries->push(Entry::fromTransaction($transaction));
|
||||
}
|
||||
);
|
||||
Log::debug(sprintf('Count %d entries in exportEntries (convertJournals)', $this->exportEntries->count()));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function createZipFile(): bool
|
||||
{
|
||||
$zip = new ZipArchive;
|
||||
$file = $this->job->key . '.zip';
|
||||
$fullPath = storage_path('export') . '/' . $file;
|
||||
|
||||
if ($zip->open($fullPath, ZipArchive::CREATE) !== true) {
|
||||
throw new FireflyException('Cannot store zip file.');
|
||||
}
|
||||
// for each file in the collection, add it to the zip file.
|
||||
$disk = Storage::disk('export');
|
||||
foreach ($this->getFiles() as $entry) {
|
||||
// is part of this job?
|
||||
$zipFileName = str_replace($this->job->key . '-', '', $entry);
|
||||
$zip->addFromString($zipFileName, $disk->get($entry));
|
||||
}
|
||||
|
||||
$zip->close();
|
||||
|
||||
// delete the files:
|
||||
$this->deleteFiles();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function exportJournals(): bool
|
||||
{
|
||||
$exporterClass = config('firefly.export_formats.' . $this->exportFormat);
|
||||
$exporter = app($exporterClass);
|
||||
$exporter->setJob($this->job);
|
||||
$exporter->setEntries($this->exportEntries);
|
||||
$exporter->run();
|
||||
$this->files->push($exporter->getFileName());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection
|
||||
*/
|
||||
public function getFiles(): Collection
|
||||
{
|
||||
return $this->files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save export job settings to class.
|
||||
*
|
||||
* @param array $settings
|
||||
*/
|
||||
public function setSettings(array $settings)
|
||||
{
|
||||
// save settings
|
||||
$this->settings = $settings;
|
||||
$this->accounts = $settings['accounts'];
|
||||
$this->exportFormat = $settings['exportFormat'];
|
||||
$this->includeAttachments = $settings['includeAttachments'];
|
||||
$this->includeOldUploads = $settings['includeOldUploads'];
|
||||
$this->job = $settings['job'];
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private function deleteFiles()
|
||||
{
|
||||
$disk = Storage::disk('export');
|
||||
foreach ($this->getFiles() as $file) {
|
||||
$disk->delete($file);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getAccountCurrencies(array $array): array
|
||||
{
|
||||
/** @var CurrencyRepositoryInterface $repository */
|
||||
$repository = app(CurrencyRepositoryInterface::class);
|
||||
$return = [];
|
||||
$ids = [];
|
||||
$repository->setUser($this->job->user);
|
||||
foreach ($array as $value) {
|
||||
$ids[] = $value['currency_id'] ?? 0;
|
||||
}
|
||||
$ids = array_unique($ids);
|
||||
$result = $repository->getByIds($ids);
|
||||
|
||||
foreach ($result as $currency) {
|
||||
$return[$currency->id] = $currency->code;
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all IBAN / SWIFT / account numbers
|
||||
*
|
||||
* @param array $array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getIbans(array $array): array
|
||||
{
|
||||
$array = array_unique($array);
|
||||
$return = [];
|
||||
$set = AccountMeta::whereIn('account_id', $array)
|
||||
->leftJoin('accounts', 'accounts.id', 'account_meta.account_id')
|
||||
->where('accounts.user_id', $this->job->user_id)
|
||||
->whereIn('account_meta.name', ['accountNumber', 'BIC', 'currency_id'])
|
||||
->get(['account_meta.id', 'account_meta.account_id', 'account_meta.name', 'account_meta.data']);
|
||||
/** @var AccountMeta $meta */
|
||||
foreach ($set as $meta) {
|
||||
$id = intval($meta->account_id);
|
||||
$return[$id][$meta->name] = $meta->data;
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns, if present, for the given journal ID's the notes.
|
||||
*
|
||||
* @param array $array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getNotes(array $array): array
|
||||
{
|
||||
$array = array_unique($array);
|
||||
$set = TransactionJournalMeta::whereIn('journal_meta.transaction_journal_id', $array)
|
||||
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id')
|
||||
->where('transaction_journals.user_id', $this->job->user_id)
|
||||
->where('journal_meta.name', 'notes')->get(
|
||||
['journal_meta.transaction_journal_id', 'journal_meta.data', 'journal_meta.id']
|
||||
);
|
||||
$return = [];
|
||||
/** @var TransactionJournalMeta $meta */
|
||||
foreach ($set as $meta) {
|
||||
$id = intval($meta->transaction_journal_id);
|
||||
$return[$id] = $meta->data;
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a comma joined list of all the users tags linked to these journals.
|
||||
*
|
||||
* @param array $array
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getTags(array $array): array
|
||||
{
|
||||
$set = DB::table('tag_transaction_journal')
|
||||
->whereIn('tag_transaction_journal.transaction_journal_id', $array)
|
||||
->leftJoin('tags', 'tag_transaction_journal.tag_id', '=', 'tags.id')
|
||||
->leftJoin('transaction_journals', 'transaction_journals.id', '=', 'tag_transaction_journal.transaction_journal_id')
|
||||
->where('transaction_journals.user_id', $this->job->user_id)
|
||||
->get(['tag_transaction_journal.transaction_journal_id', 'tags.tag']);
|
||||
$result = [];
|
||||
foreach ($set as $entry) {
|
||||
$id = intval($entry->transaction_journal_id);
|
||||
$result[$id] = isset($result[$id]) ? $result[$id] : [];
|
||||
$result[$id][] = Crypt::decrypt($entry->tag);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
@ -58,8 +58,12 @@ class CsvExporter extends BasicExporter implements ExporterInterface
|
||||
|
||||
// get field names for header row:
|
||||
$first = $this->getEntries()->first();
|
||||
$headers = array_keys(get_object_vars($first));
|
||||
$rows[] = $headers;
|
||||
$headers = [];
|
||||
if (!is_null($first)) {
|
||||
$headers = array_keys(get_object_vars($first));
|
||||
}
|
||||
|
||||
$rows[] = $headers;
|
||||
|
||||
/** @var Entry $entry */
|
||||
foreach ($this->getEntries() as $entry) {
|
||||
|
@ -15,7 +15,6 @@ namespace FireflyIII\Helpers\Collector;
|
||||
|
||||
|
||||
use Carbon\Carbon;
|
||||
use Crypt;
|
||||
use DB;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Helpers\Filter\FilterInterface;
|
||||
@ -31,7 +30,6 @@ use FireflyIII\Models\Tag;
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Contracts\Encryption\DecryptException;
|
||||
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
|
||||
use Illuminate\Database\Query\JoinClause;
|
||||
use Illuminate\Pagination\LengthAwarePaginator;
|
||||
@ -86,7 +84,9 @@ class JournalCollector implements JournalCollectorInterface
|
||||
|
||||
'accounts.name as account_name',
|
||||
'accounts.encrypted as account_encrypted',
|
||||
'accounts.iban as account_iban',
|
||||
'account_types.type as account_type',
|
||||
|
||||
];
|
||||
/** @var bool */
|
||||
private $filterTransfers = false;
|
||||
@ -175,12 +175,10 @@ class JournalCollector implements JournalCollectorInterface
|
||||
if (!is_null($transaction->bill_name)) {
|
||||
$transaction->bill_name = Steam::decrypt(intval($transaction->bill_name_encrypted), $transaction->bill_name);
|
||||
}
|
||||
$transaction->opposing_account_name = app('steam')->tryDecrypt($transaction->opposing_account_name);
|
||||
$transaction->account_iban = app('steam')->tryDecrypt($transaction->account_iban);
|
||||
$transaction->opposing_account_iban = app('steam')->tryDecrypt($transaction->opposing_account_iban);
|
||||
|
||||
try {
|
||||
$transaction->opposing_account_name = Crypt::decrypt($transaction->opposing_account_name);
|
||||
} catch (DecryptException $e) {
|
||||
// if this fails its already decrypted.
|
||||
}
|
||||
|
||||
}
|
||||
);
|
||||
@ -621,6 +619,8 @@ class JournalCollector implements JournalCollectorInterface
|
||||
$this->query->leftJoin('budgets as transaction_journal_budgets', 'transaction_journal_budgets.id', '=', 'budget_transaction_journal.budget_id');
|
||||
$this->query->leftJoin('budget_transaction', 'budget_transaction.transaction_id', '=', 'transactions.id');
|
||||
$this->query->leftJoin('budgets as transaction_budgets', 'transaction_budgets.id', '=', 'budget_transaction.budget_id');
|
||||
$this->query->whereNull('transaction_journal_budgets.deleted_at');
|
||||
$this->query->whereNull('transaction_budgets.deleted_at');
|
||||
|
||||
$this->fields[] = 'budget_transaction_journal.budget_id as transaction_journal_budget_id';
|
||||
$this->fields[] = 'transaction_journal_budgets.encrypted as transaction_journal_budget_encrypted';
|
||||
@ -677,10 +677,12 @@ class JournalCollector implements JournalCollectorInterface
|
||||
$this->query->leftJoin('account_types as opposing_account_types', 'opposing_accounts.account_type_id', '=', 'opposing_account_types.id');
|
||||
$this->query->whereNull('opposing.deleted_at');
|
||||
|
||||
$this->fields[] = 'opposing.id as opposing_id';
|
||||
$this->fields[] = 'opposing.account_id as opposing_account_id';
|
||||
$this->fields[] = 'opposing_accounts.name as opposing_account_name';
|
||||
$this->fields[] = 'opposing_accounts.encrypted as opposing_account_encrypted';
|
||||
$this->fields[] = 'opposing.id as opposing_id';
|
||||
$this->fields[] = 'opposing.account_id as opposing_account_id';
|
||||
$this->fields[] = 'opposing_accounts.name as opposing_account_name';
|
||||
$this->fields[] = 'opposing_accounts.encrypted as opposing_account_encrypted';
|
||||
$this->fields[] = 'opposing_accounts.iban as opposing_account_iban';
|
||||
|
||||
$this->fields[] = 'opposing_account_types.type as opposing_account_type';
|
||||
$this->joinedOpposing = true;
|
||||
Log::debug('joinedOpposing is now true!');
|
||||
|
@ -14,16 +14,12 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Helpers\Report;
|
||||
|
||||
use Carbon\Carbon;
|
||||
use DB;
|
||||
use FireflyIII\Helpers\Collection\Balance;
|
||||
use FireflyIII\Helpers\Collection\BalanceEntry;
|
||||
use FireflyIII\Helpers\Collection\BalanceHeader;
|
||||
use FireflyIII\Helpers\Collection\BalanceLine;
|
||||
use FireflyIII\Models\BudgetLimit;
|
||||
use FireflyIII\Models\Tag;
|
||||
use FireflyIII\Models\TransactionType;
|
||||
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
||||
use Illuminate\Database\Query\JoinClause;
|
||||
use Illuminate\Support\Collection;
|
||||
use Log;
|
||||
|
||||
@ -74,14 +70,9 @@ class BalanceReportHelper implements BalanceReportHelperInterface
|
||||
$line = $this->createBalanceLine($budgetLimit, $accounts);
|
||||
$balance->addBalanceLine($line);
|
||||
}
|
||||
Log::debug('Create rest of the things.');
|
||||
$noBudgetLine = $this->createNoBudgetLine($accounts, $start, $end);
|
||||
$coveredByTagLine = $this->createTagsBalanceLine($accounts, $start, $end);
|
||||
$leftUnbalancedLine = $this->createLeftUnbalancedLine($noBudgetLine, $coveredByTagLine);
|
||||
$noBudgetLine = $this->createNoBudgetLine($accounts, $start, $end);
|
||||
|
||||
$balance->addBalanceLine($noBudgetLine);
|
||||
$balance->addBalanceLine($coveredByTagLine);
|
||||
$balance->addBalanceLine($leftUnbalancedLine);
|
||||
$balance->setBalanceHeader($header);
|
||||
|
||||
Log::debug('Clear unused budgets.');
|
||||
@ -93,54 +84,6 @@ class BalanceReportHelper implements BalanceReportHelperInterface
|
||||
return $balance;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method collects all transfers that are part of a "balancing act" tag
|
||||
* and groups the amounts of those transfers by their destination account.
|
||||
*
|
||||
* This is used to indicate which expenses, usually outside of budgets, have been
|
||||
* corrected by transfers from a savings account.
|
||||
*
|
||||
* @param Collection $accounts
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
private function allCoveredByBalancingActs(Collection $accounts, Carbon $start, Carbon $end): Collection
|
||||
{
|
||||
$ids = $accounts->pluck('id')->toArray();
|
||||
$set = auth()->user()->tags()
|
||||
->leftJoin('tag_transaction_journal', 'tag_transaction_journal.tag_id', '=', 'tags.id')
|
||||
->leftJoin('transaction_journals', 'tag_transaction_journal.transaction_journal_id', '=', 'transaction_journals.id')
|
||||
->leftJoin('transaction_types', 'transaction_journals.transaction_type_id', '=', 'transaction_types.id')
|
||||
->leftJoin(
|
||||
'transactions AS t_source', function (JoinClause $join) {
|
||||
$join->on('transaction_journals.id', '=', 't_source.transaction_journal_id')->where('t_source.amount', '<', 0);
|
||||
}
|
||||
)
|
||||
->leftJoin(
|
||||
'transactions AS t_destination', function (JoinClause $join) {
|
||||
$join->on('transaction_journals.id', '=', 't_destination.transaction_journal_id')->where('t_destination.amount', '>', 0);
|
||||
}
|
||||
)
|
||||
->where('tags.tagMode', 'balancingAct')
|
||||
->where('transaction_types.type', TransactionType::TRANSFER)
|
||||
->where('transaction_journals.date', '>=', $start->format('Y-m-d'))
|
||||
->where('transaction_journals.date', '<=', $end->format('Y-m-d'))
|
||||
->whereNull('transaction_journals.deleted_at')
|
||||
->whereIn('t_source.account_id', $ids)
|
||||
->whereIn('t_destination.account_id', $ids)
|
||||
->groupBy('t_destination.account_id')
|
||||
->get(
|
||||
[
|
||||
't_destination.account_id',
|
||||
DB::raw('SUM(t_destination.amount) AS sum'),
|
||||
]
|
||||
);
|
||||
|
||||
return $set;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param BudgetLimit $budgetLimit
|
||||
@ -168,40 +111,6 @@ class BalanceReportHelper implements BalanceReportHelperInterface
|
||||
return $line;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param BalanceLine $noBudgetLine
|
||||
* @param BalanceLine $coveredByTagLine
|
||||
*
|
||||
* @return BalanceLine
|
||||
*/
|
||||
private function createLeftUnbalancedLine(BalanceLine $noBudgetLine, BalanceLine $coveredByTagLine): BalanceLine
|
||||
{
|
||||
$line = new BalanceLine;
|
||||
$line->setRole(BalanceLine::ROLE_DIFFROLE);
|
||||
$noBudgetEntries = $noBudgetLine->getBalanceEntries();
|
||||
$tagEntries = $coveredByTagLine->getBalanceEntries();
|
||||
|
||||
foreach ($noBudgetEntries as $entry) {
|
||||
$account = $entry->getAccount();
|
||||
$tagEntry = $tagEntries->filter(
|
||||
function (BalanceEntry $current) use ($account) {
|
||||
return $current->getAccount()->id === $account->id;
|
||||
}
|
||||
);
|
||||
if ($tagEntry->first()) {
|
||||
// found corresponding entry. As we should:
|
||||
$newEntry = new BalanceEntry;
|
||||
$newEntry->setAccount($account);
|
||||
$spent = bcadd($tagEntry->first()->getLeft(), $entry->getSpent());
|
||||
$newEntry->setSpent($spent);
|
||||
$line->addBalanceEntry($newEntry);
|
||||
}
|
||||
}
|
||||
|
||||
return $line;
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Collection $accounts
|
||||
@ -227,41 +136,6 @@ class BalanceReportHelper implements BalanceReportHelperInterface
|
||||
return $empty;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Collection $accounts
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
*
|
||||
* @return BalanceLine
|
||||
*/
|
||||
private function createTagsBalanceLine(Collection $accounts, Carbon $start, Carbon $end): BalanceLine
|
||||
{
|
||||
$tags = new BalanceLine;
|
||||
$tagsLeft = $this->allCoveredByBalancingActs($accounts, $start, $end);
|
||||
|
||||
$tags->setRole(BalanceLine::ROLE_TAGROLE);
|
||||
|
||||
foreach ($accounts as $account) {
|
||||
$leftEntry = $tagsLeft->filter(
|
||||
function (Tag $tag) use ($account) {
|
||||
return $tag->account_id === $account->id;
|
||||
}
|
||||
);
|
||||
$left = '0';
|
||||
if (!is_null($leftEntry->first())) {
|
||||
$left = $leftEntry->first()->sum;
|
||||
}
|
||||
|
||||
// balanced by tags
|
||||
$tagEntry = new BalanceEntry;
|
||||
$tagEntry->setAccount($account);
|
||||
$tagEntry->setLeft($left);
|
||||
$tags->addBalanceEntry($tagEntry);
|
||||
|
||||
}
|
||||
|
||||
return $tags;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Balance $balance
|
||||
|
@ -194,7 +194,7 @@ class AccountController extends Controller
|
||||
|
||||
return view(
|
||||
'accounts.edit', compact(
|
||||
'allCurrencies', 'currencySelectList', 'account', 'currency', 'subTitle', 'subTitleIcon', 'what', 'roles'
|
||||
'allCurrencies', 'currencySelectList', 'account', 'currency', 'subTitle', 'subTitleIcon', 'what', 'roles', 'preFilled'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
229
app/Http/Controllers/Admin/LinkController.php
Normal file
229
app/Http/Controllers/Admin/LinkController.php
Normal file
@ -0,0 +1,229 @@
|
||||
<?php
|
||||
/**
|
||||
* LinkController.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Http\Controllers\Admin;
|
||||
|
||||
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\Http\Requests\LinkTypeFormRequest;
|
||||
use FireflyIII\Models\LinkType;
|
||||
use FireflyIII\Repositories\LinkType\LinkTypeRepositoryInterface;
|
||||
use Illuminate\Http\Request;
|
||||
use Preferences;
|
||||
use View;
|
||||
|
||||
/**
|
||||
* Class LinkController
|
||||
*
|
||||
* @package FireflyIII\Http\Controllers\Admin
|
||||
*/
|
||||
class LinkController extends Controller
|
||||
{
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->middleware(
|
||||
function ($request, $next) {
|
||||
View::share('title', strval(trans('firefly.administration')));
|
||||
View::share('mainTitleIcon', 'fa-hand-spock-o');
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||
*/
|
||||
public function create()
|
||||
{
|
||||
$subTitle = trans('firefly.create_new_link_type');
|
||||
$subTitleIcon = 'fa-link';
|
||||
|
||||
// put previous url in session if not redirect from store (not "create another").
|
||||
if (session('link_types.create.fromStore') !== true) {
|
||||
$this->rememberPreviousUri('link_types.create.uri');
|
||||
}
|
||||
|
||||
return view('admin.link.create', compact('subTitle', 'subTitleIcon'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param LinkTypeRepositoryInterface $repository
|
||||
* @param LinkType $linkType
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View
|
||||
*/
|
||||
public function delete(Request $request, LinkTypeRepositoryInterface $repository, LinkType $linkType)
|
||||
{
|
||||
if (!$linkType->editable) {
|
||||
$request->session()->flash('error', strval(trans('firefly.cannot_edit_link_type', ['name' => $linkType->name])));
|
||||
|
||||
return redirect(route('admin.links.index'));
|
||||
}
|
||||
|
||||
$subTitle = trans('firefly.delete_link_type', ['name' => $linkType->name]);
|
||||
$otherTypes = $repository->get();
|
||||
$count = $repository->countJournals($linkType);
|
||||
$moveTo = [];
|
||||
$moveTo[0] = trans('firefly.do_not_save_connection');
|
||||
/** @var LinkType $otherType */
|
||||
foreach ($otherTypes as $otherType) {
|
||||
if ($otherType->id !== $linkType->id) {
|
||||
$moveTo[$otherType->id] = sprintf('%s (%s / %s)', $otherType->name, $otherType->inward, $otherType->outward);
|
||||
}
|
||||
}
|
||||
// put previous url in session
|
||||
$this->rememberPreviousUri('link_types.delete.uri');
|
||||
|
||||
return view('admin.link.delete', compact('linkType', 'subTitle', 'moveTo', 'count'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param LinkTypeRepositoryInterface $repository
|
||||
* @param LinkType $linkType
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||
*/
|
||||
public function destroy(Request $request, LinkTypeRepositoryInterface $repository, LinkType $linkType)
|
||||
{
|
||||
$name = $linkType->name;
|
||||
$moveTo = $repository->find(intval($request->get('move_link_type_before_delete')));
|
||||
$repository->destroy($linkType, $moveTo);
|
||||
|
||||
$request->session()->flash('success', strval(trans('firefly.deleted_link_type', ['name' => $name])));
|
||||
Preferences::mark();
|
||||
|
||||
return redirect($this->getPreviousUri('link_types.delete.uri'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param LinkType $linkType
|
||||
*
|
||||
* @return \Illuminate\Contracts\View\Factory|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|\Illuminate\View\View
|
||||
*/
|
||||
public function edit(Request $request, LinkType $linkType)
|
||||
{
|
||||
if (!$linkType->editable) {
|
||||
$request->session()->flash('error', strval(trans('firefly.cannot_edit_link_type', ['name' => $linkType->name])));
|
||||
|
||||
return redirect(route('admin.links.index'));
|
||||
}
|
||||
$subTitle = trans('firefly.edit_link_type', ['name' => $linkType->name]);
|
||||
$subTitleIcon = 'fa-link';
|
||||
|
||||
// put previous url in session if not redirect from store (not "return_to_edit").
|
||||
if (session('link_types.edit.fromUpdate') !== true) {
|
||||
$this->rememberPreviousUri('link_types.edit.uri');
|
||||
}
|
||||
$request->session()->forget('link_types.edit.fromUpdate');
|
||||
|
||||
return view('admin.link.edit', compact('subTitle', 'subTitleIcon', 'linkType'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param LinkTypeRepositoryInterface $repository
|
||||
*
|
||||
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||
*/
|
||||
public function index(LinkTypeRepositoryInterface $repository)
|
||||
{
|
||||
$subTitle = trans('firefly.journal_link_configuration');
|
||||
$subTitleIcon = 'fa-link';
|
||||
$linkTypes = $repository->get();
|
||||
$linkTypes->each(
|
||||
function (LinkType $linkType) use ($repository) {
|
||||
$linkType->journalCount = $repository->countJournals($linkType);
|
||||
}
|
||||
);
|
||||
|
||||
return view('admin.link.index', compact('subTitle', 'subTitleIcon', 'linkTypes'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param LinkType $linkType
|
||||
*
|
||||
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||
*/
|
||||
public function show(LinkType $linkType)
|
||||
{
|
||||
$subTitle = trans('firefly.overview_for_link', ['name' => $linkType->name]);
|
||||
$subTitleIcon = 'fa-link';
|
||||
$links = $linkType->transactionJournalLinks()->get();
|
||||
|
||||
return view('admin.link.show', compact('subTitle', 'subTitleIcon', 'linkType', 'links'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param LinkTypeFormRequest $request
|
||||
* @param LinkTypeRepositoryInterface $repository
|
||||
*
|
||||
* @return $this|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||
*/
|
||||
public function store(LinkTypeFormRequest $request, LinkTypeRepositoryInterface $repository)
|
||||
{
|
||||
$data = [
|
||||
'name' => $request->string('name'),
|
||||
'inward' => $request->string('inward'),
|
||||
'outward' => $request->string('outward'),
|
||||
];
|
||||
$linkType = $repository->store($data);
|
||||
$request->session()->flash('success', strval(trans('firefly.stored_new_link_type', ['name' => $linkType->name])));
|
||||
|
||||
if (intval($request->get('create_another')) === 1) {
|
||||
// set value so create routine will not overwrite URL:
|
||||
$request->session()->put('link_types.create.fromStore', true);
|
||||
|
||||
return redirect(route('link_types.create', [$request->input('what')]))->withInput();
|
||||
}
|
||||
|
||||
// redirect to previous URL.
|
||||
return redirect($this->getPreviousUri('link_types.create.uri'));
|
||||
}
|
||||
|
||||
public function update(LinkTypeFormRequest $request, LinkTypeRepositoryInterface $repository, LinkType $linkType)
|
||||
{
|
||||
if (!$linkType->editable) {
|
||||
$request->session()->flash('error', strval(trans('firefly.cannot_edit_link_type', ['name' => $linkType->name])));
|
||||
|
||||
return redirect(route('admin.links.index'));
|
||||
}
|
||||
|
||||
$data = [
|
||||
'name' => $request->string('name'),
|
||||
'inward' => $request->string('inward'),
|
||||
'outward' => $request->string('outward'),
|
||||
];
|
||||
$repository->update($linkType, $data);
|
||||
|
||||
$request->session()->flash('success', strval(trans('firefly.updated_link_type', ['name' => $linkType->name])));
|
||||
Preferences::mark();
|
||||
|
||||
if (intval($request->get('return_to_edit')) === 1) {
|
||||
// set value so edit routine will not overwrite URL:
|
||||
$request->session()->put('link_types.edit.fromUpdate', true);
|
||||
|
||||
return redirect(route('admin.links.edit', [$linkType->id]))->withInput(['return_to_edit' => 1]);
|
||||
}
|
||||
|
||||
// redirect to previous URL.
|
||||
return redirect($this->getPreviousUri('link_types.edit.uri'));
|
||||
}
|
||||
|
||||
}
|
@ -13,6 +13,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Http\Controllers;
|
||||
|
||||
use Amount;
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
|
||||
use FireflyIII\Http\Requests\BillFormRequest;
|
||||
@ -129,6 +130,11 @@ class BillController extends Controller
|
||||
if (session('bills.edit.fromUpdate') !== true) {
|
||||
$this->rememberPreviousUri('bills.edit.uri');
|
||||
}
|
||||
|
||||
$currency = Amount::getDefaultCurrency();
|
||||
$bill->amount_min = round($bill->amount_min, $currency->decimal_places);
|
||||
$bill->amount_max = round($bill->amount_max, $currency->decimal_places);
|
||||
|
||||
$request->session()->forget('bills.edit.fromUpdate');
|
||||
$request->session()->flash('gaEventCategory', 'bills');
|
||||
$request->session()->flash('gaEventAction', 'edit');
|
||||
|
@ -75,11 +75,9 @@ class BudgetController extends Controller
|
||||
*/
|
||||
public function amount(Request $request, Budget $budget)
|
||||
{
|
||||
$amount = intval($request->get('amount'));
|
||||
/** @var Carbon $start */
|
||||
$start = session('start', Carbon::now()->startOfMonth());
|
||||
/** @var Carbon $end */
|
||||
$end = session('end', Carbon::now()->endOfMonth());
|
||||
$amount = intval($request->get('amount'));
|
||||
$start = Carbon::createFromFormat('Y-m-d', $request->get('start'));
|
||||
$end = Carbon::createFromFormat('Y-m-d', $request->get('end'));
|
||||
$budgetLimit = $this->repository->updateLimitAmount($budget, $start, $end, $amount);
|
||||
if ($amount === 0) {
|
||||
$budgetLimit = null;
|
||||
@ -243,7 +241,7 @@ class BudgetController extends Controller
|
||||
compact(
|
||||
'available', 'currentMonth', 'next', 'nextText', 'prev', 'prevText',
|
||||
'periodStart', 'periodEnd', 'budgetInformation', 'inactive', 'budgets',
|
||||
'spent', 'budgeted', 'previousLoop', 'nextLoop', 'start'
|
||||
'spent', 'budgeted', 'previousLoop', 'nextLoop', 'start', 'end'
|
||||
)
|
||||
);
|
||||
}
|
||||
@ -313,15 +311,15 @@ class BudgetController extends Controller
|
||||
*/
|
||||
public function postUpdateIncome(BudgetIncomeRequest $request)
|
||||
{
|
||||
$start = session('start', new Carbon);
|
||||
$end = session('end', new Carbon);
|
||||
$start = Carbon::createFromFormat('Y-m-d', $request->string('start'));
|
||||
$end = Carbon::createFromFormat('Y-m-d', $request->string('end'));
|
||||
$defaultCurrency = Amount::getDefaultCurrency();
|
||||
$amount = $request->get('amount');
|
||||
|
||||
$this->repository->setAvailableBudget($defaultCurrency, $start, $end, $amount);
|
||||
Preferences::mark();
|
||||
|
||||
return redirect(route('budgets.index'));
|
||||
return redirect(route('budgets.index', [$start->format('Y-m-d')]));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -443,15 +441,16 @@ class BudgetController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* @return View
|
||||
* @param Carbon $start
|
||||
* @param Carbon $end
|
||||
*
|
||||
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||
*/
|
||||
public function updateIncome()
|
||||
public function updateIncome(Carbon $start, Carbon $end)
|
||||
{
|
||||
$start = session('start', new Carbon);
|
||||
$end = session('end', new Carbon);
|
||||
$defaultCurrency = Amount::getDefaultCurrency();
|
||||
$available = $this->repository->getAvailableBudget($defaultCurrency, $start, $end);
|
||||
|
||||
$available = round($available, $defaultCurrency->decimal_places);
|
||||
|
||||
return view('budgets.income', compact('available', 'start', 'end'));
|
||||
}
|
||||
|
@ -137,13 +137,14 @@ class ExportController extends Controller
|
||||
public function postIndex(ExportFormRequest $request, AccountRepositoryInterface $repository, ExportJobRepositoryInterface $jobs)
|
||||
{
|
||||
$job = $jobs->findByKey($request->get('job'));
|
||||
$accounts = $request->get('accounts') ?? [];
|
||||
$settings = [
|
||||
'accounts' => $repository->getAccountsById($request->get('accounts')),
|
||||
'accounts' => $repository->getAccountsById($accounts),
|
||||
'startDate' => new Carbon($request->get('export_start_range')),
|
||||
'endDate' => new Carbon($request->get('export_end_range')),
|
||||
'exportFormat' => $request->get('exportFormat'),
|
||||
'includeAttachments' => intval($request->get('include_attachments')) === 1,
|
||||
'includeOldUploads' => intval($request->get('include_old_uploads')) === 1,
|
||||
'includeAttachments' => $request->boolean('include_attachments'),
|
||||
'includeOldUploads' => $request->boolean('include_old_uploads'),
|
||||
'job' => $job,
|
||||
];
|
||||
|
||||
@ -159,12 +160,14 @@ class ExportController extends Controller
|
||||
$jobs->changeStatus($job, 'export_status_collecting_journals');
|
||||
$processor->collectJournals();
|
||||
$jobs->changeStatus($job, 'export_status_collected_journals');
|
||||
|
||||
/*
|
||||
* Transform to exportable entries:
|
||||
*/
|
||||
$jobs->changeStatus($job, 'export_status_converting_to_export_format');
|
||||
$processor->convertJournals();
|
||||
$jobs->changeStatus($job, 'export_status_converted_to_export_format');
|
||||
|
||||
/*
|
||||
* Transform to (temporary) file:
|
||||
*/
|
||||
@ -180,6 +183,7 @@ class ExportController extends Controller
|
||||
$jobs->changeStatus($job, 'export_status_collected_attachments');
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Collect old uploads
|
||||
*/
|
||||
|
@ -82,6 +82,14 @@ class HomeController extends Controller
|
||||
*/
|
||||
public function displayError()
|
||||
{
|
||||
Log::debug('This is a test message at the DEBUG level.');
|
||||
Log::info('This is a test message at the INFO level.');
|
||||
Log::notice('This is a test message at the NOTICE level.');
|
||||
Log::warning('This is a test message at the WARNING level.');
|
||||
Log::error('This is a test message at the ERROR level.');
|
||||
Log::critical('This is a test message at the CRITICAL level.');
|
||||
Log::alert('This is a test message at the ALERT level.');
|
||||
Log::emergency('This is a test message at the EMERGENCY level.');
|
||||
throw new FireflyException('A very simple test error.');
|
||||
}
|
||||
|
||||
|
@ -13,18 +13,126 @@ namespace FireflyIII\Http\Controllers\Import;
|
||||
|
||||
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\Support\Import\Information\InformationInterface;
|
||||
use FireflyIII\Support\Import\Prerequisites\PrerequisitesInterface;
|
||||
use Illuminate\Http\Request;
|
||||
use Log;
|
||||
use Session;
|
||||
|
||||
class BankController extends Controller
|
||||
{
|
||||
|
||||
public function postPrerequisites()
|
||||
/**
|
||||
* This method must ask the user all parameters necessary to start importing data. This may not be enough
|
||||
* to finish the import itself (ie. mapping) but it should be enough to begin: accounts to import from,
|
||||
* accounts to import into, data ranges, etc.
|
||||
*
|
||||
* @param string $bank
|
||||
*
|
||||
* @return \Illuminate\Contracts\View\Factory|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|\Illuminate\View\View
|
||||
*/
|
||||
public function form(string $bank)
|
||||
{
|
||||
$class = config(sprintf('firefly.import_pre.%s', $bank));
|
||||
/** @var PrerequisitesInterface $object */
|
||||
$object = app($class);
|
||||
$object->setUser(auth()->user());
|
||||
|
||||
if ($object->hasPrerequisites()) {
|
||||
return redirect(route('import.bank.prerequisites', [$bank]));
|
||||
}
|
||||
$class = config(sprintf('firefly.import_info.%s', $bank));
|
||||
/** @var InformationInterface $object */
|
||||
$object = app($class);
|
||||
$object->setUser(auth()->user());
|
||||
$remoteAccounts = $object->getAccounts();
|
||||
|
||||
return view('import.bank.form', compact('remoteAccounts', 'bank'));
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* With the information given in the submitted form Firefly III will call upon the bank's classes to return transaction
|
||||
* information as requested. The user will be able to map unknown data and continue. Or maybe, it's put into some kind of
|
||||
* fake CSV file and forwarded to the import routine.
|
||||
*
|
||||
* @param Request $request
|
||||
* @param string $bank
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse|null
|
||||
*/
|
||||
public function postForm(Request $request, string $bank)
|
||||
{
|
||||
|
||||
$class = config(sprintf('firefly.import_pre.%s', $bank));
|
||||
/** @var PrerequisitesInterface $object */
|
||||
$object = app($class);
|
||||
$object->setUser(auth()->user());
|
||||
|
||||
if ($object->hasPrerequisites()) {
|
||||
return redirect(route('import.bank.prerequisites', [$bank]));
|
||||
}
|
||||
$remoteAccounts = $request->get('do_import');
|
||||
if (!is_array($remoteAccounts) || count($remoteAccounts) === 0) {
|
||||
Session::flash('error', 'Must select accounts');
|
||||
|
||||
return redirect(route('import.bank.form', [$bank]));
|
||||
}
|
||||
$remoteAccounts = array_keys($remoteAccounts);
|
||||
|
||||
$class = config(sprintf('firefly.import_pre.%s', $bank));
|
||||
// get import file
|
||||
|
||||
// get import config
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* This method processes the prerequisites the user has entered in the previous step.
|
||||
*
|
||||
* Whatever storePrerequisites does, it should make sure that the system is ready to continue immediately. So
|
||||
* no extra calls or stuff, except maybe to open a session
|
||||
*
|
||||
* @see PrerequisitesInterface::storePrerequisites
|
||||
*
|
||||
* @param Request $request
|
||||
* @param string $bank
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||
*/
|
||||
public function postPrerequisites(Request $request, string $bank)
|
||||
{
|
||||
Log::debug(sprintf('Now in postPrerequisites for %s', $bank));
|
||||
$class = config(sprintf('firefly.import_pre.%s', $bank));
|
||||
/** @var PrerequisitesInterface $object */
|
||||
$object = app($class);
|
||||
$object->setUser(auth()->user());
|
||||
if (!$object->hasPrerequisites()) {
|
||||
Log::debug(sprintf('No more prerequisites for %s, move to form.', $bank));
|
||||
|
||||
return redirect(route('import.bank.form', [$bank]));
|
||||
}
|
||||
Log::debug('Going to store entered preprerequisites.');
|
||||
// store post data
|
||||
$result = $object->storePrerequisites($request);
|
||||
|
||||
if ($result->count() > 0) {
|
||||
Session::flash('error', $result->first());
|
||||
|
||||
return redirect(route('import.bank.prerequisites', [$bank]));
|
||||
}
|
||||
|
||||
return redirect(route('import.bank.form', [$bank]));
|
||||
}
|
||||
|
||||
/**
|
||||
* This method shows you, if necessary, a form that allows you to enter any required values, such as API keys,
|
||||
* login passwords or other values.
|
||||
*
|
||||
* @param string $bank
|
||||
*
|
||||
* @return \Illuminate\Contracts\View\Factory|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|\Illuminate\View\View
|
||||
*/
|
||||
public function prerequisites(string $bank)
|
||||
{
|
||||
@ -40,10 +148,7 @@ class BankController extends Controller
|
||||
return view($view, $parameters);
|
||||
}
|
||||
|
||||
if (!$object->hasPrerequisites()) {
|
||||
echo 'redirect to import form.';
|
||||
}
|
||||
|
||||
return redirect(route('import.bank.form', [$bank]));
|
||||
}
|
||||
|
||||
}
|
||||
|
171
app/Http/Controllers/Json/AutoCompleteController.php
Normal file
171
app/Http/Controllers/Json/AutoCompleteController.php
Normal file
@ -0,0 +1,171 @@
|
||||
<?php
|
||||
/**
|
||||
* AutoCompleteController.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Http\Controllers\Json;
|
||||
|
||||
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Support\CacheProperties;
|
||||
use Response;
|
||||
|
||||
/**
|
||||
* Class AutoCompleteController
|
||||
*
|
||||
* @package FireflyIII\Http\Controllers\Json
|
||||
*/
|
||||
class AutoCompleteController extends Controller
|
||||
{
|
||||
|
||||
/**
|
||||
* Returns a JSON list of all accounts.
|
||||
*
|
||||
* @param AccountRepositoryInterface $repository
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*
|
||||
*/
|
||||
public function allAccounts(AccountRepositoryInterface $repository)
|
||||
{
|
||||
$return = array_unique(
|
||||
$repository->getAccountsByType(
|
||||
[AccountType::REVENUE, AccountType::EXPENSE, AccountType::BENEFICIARY, AccountType::DEFAULT, AccountType::ASSET]
|
||||
)->pluck('name')->toArray()
|
||||
);
|
||||
sort($return);
|
||||
|
||||
return Response::json($return);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param JournalCollectorInterface $collector
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function allTransactionJournals(JournalCollectorInterface $collector)
|
||||
{
|
||||
$collector->setLimit(250)->setPage(1);
|
||||
$return = array_unique($collector->getJournals()->pluck('description')->toArray());
|
||||
sort($return);
|
||||
|
||||
return Response::json($return);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a JSON list of all beneficiaries.
|
||||
*
|
||||
* @param AccountRepositoryInterface $repository
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*
|
||||
*/
|
||||
public function expenseAccounts(AccountRepositoryInterface $repository)
|
||||
{
|
||||
$set = $repository->getAccountsByType([AccountType::EXPENSE, AccountType::BENEFICIARY]);
|
||||
$filtered = $set->filter(
|
||||
function (Account $account) {
|
||||
if ($account->active) {
|
||||
return $account;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
);
|
||||
$return = array_unique($filtered->pluck('name')->toArray());
|
||||
|
||||
sort($return);
|
||||
|
||||
return Response::json($return);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param JournalCollectorInterface $collector
|
||||
*
|
||||
* @param TransactionJournal $except
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse|mixed
|
||||
*/
|
||||
public function journalsWithId(JournalCollectorInterface $collector, TransactionJournal $except)
|
||||
{
|
||||
|
||||
$cache = new CacheProperties;
|
||||
$cache->addProperty('recent-journals-id');
|
||||
|
||||
if ($cache->has()) {
|
||||
return $cache->get(); // @codeCoverageIgnore
|
||||
}
|
||||
|
||||
$collector->setLimit(400)->setPage(1);
|
||||
$set = $collector->getJournals()->pluck('description', 'journal_id')->toArray();
|
||||
$return = [];
|
||||
foreach ($set as $id => $description) {
|
||||
$id = intval($id);
|
||||
if ($id !== $except->id) {
|
||||
$return[] = [
|
||||
'id' => $id,
|
||||
'name' => $id . ': ' . $description,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$cache->store($return);
|
||||
|
||||
return Response::json($return);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AccountRepositoryInterface $repository
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*
|
||||
*/
|
||||
public function revenueAccounts(AccountRepositoryInterface $repository)
|
||||
{
|
||||
$set = $repository->getAccountsByType([AccountType::REVENUE]);
|
||||
$filtered = $set->filter(
|
||||
function (Account $account) {
|
||||
if ($account->active) {
|
||||
return $account;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
);
|
||||
$return = array_unique($filtered->pluck('name')->toArray());
|
||||
sort($return);
|
||||
|
||||
return Response::json($return);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param JournalCollectorInterface $collector
|
||||
* @param string $what
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function transactionJournals(JournalCollectorInterface $collector, string $what)
|
||||
{
|
||||
$type = config('firefly.transactionTypesByWhat.' . $what);
|
||||
$types = [$type];
|
||||
|
||||
$collector->setTypes($types)->setLimit(250)->setPage(1);
|
||||
$return = array_unique($collector->getJournals()->pluck('description')->toArray());
|
||||
sort($return);
|
||||
|
||||
return Response::json($return);
|
||||
}
|
||||
|
||||
}
|
@ -16,9 +16,7 @@ namespace FireflyIII\Http\Controllers;
|
||||
use Amount;
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Models\TransactionType;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
use FireflyIII\Repositories\Bill\BillRepositoryInterface;
|
||||
use FireflyIII\Repositories\Budget\BudgetRepositoryInterface;
|
||||
use FireflyIII\Repositories\Category\CategoryRepositoryInterface;
|
||||
@ -62,43 +60,6 @@ class JsonController extends Controller
|
||||
return Response::json(['html' => $view]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a JSON list of all accounts.
|
||||
*
|
||||
* @param AccountRepositoryInterface $repository
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*
|
||||
*/
|
||||
public function allAccounts(AccountRepositoryInterface $repository)
|
||||
{
|
||||
$return = array_unique(
|
||||
$repository->getAccountsByType(
|
||||
[AccountType::REVENUE, AccountType::EXPENSE, AccountType::BENEFICIARY, AccountType::DEFAULT, AccountType::ASSET]
|
||||
)->pluck('name')->toArray()
|
||||
);
|
||||
sort($return);
|
||||
|
||||
return Response::json($return);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param JournalCollectorInterface $collector
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function allTransactionJournals(JournalCollectorInterface $collector)
|
||||
{
|
||||
$collector->setLimit(100)->setPage(1);
|
||||
$return = array_unique($collector->getJournals()->pluck('description')->toArray());
|
||||
sort($return);
|
||||
|
||||
return Response::json($return);
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param BillRepositoryInterface $repository
|
||||
*
|
||||
@ -235,36 +196,6 @@ class JsonController extends Controller
|
||||
return Response::json($return);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a JSON list of all beneficiaries.
|
||||
*
|
||||
* @param AccountRepositoryInterface $repository
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*
|
||||
*/
|
||||
public function expenseAccounts(AccountRepositoryInterface $repository)
|
||||
{
|
||||
$return = array_unique($repository->getAccountsByType([AccountType::EXPENSE, AccountType::BENEFICIARY])->pluck('name')->toArray());
|
||||
sort($return);
|
||||
|
||||
return Response::json($return);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AccountRepositoryInterface $repository
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*
|
||||
*/
|
||||
public function revenueAccounts(AccountRepositoryInterface $repository)
|
||||
{
|
||||
$return = array_unique($repository->getAccountsByType([AccountType::REVENUE])->pluck('name')->toArray());
|
||||
sort($return);
|
||||
|
||||
return Response::json($return);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a JSON list of all beneficiaries.
|
||||
*
|
||||
@ -281,26 +212,6 @@ class JsonController extends Controller
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param JournalCollectorInterface $collector
|
||||
* @param string $what
|
||||
*
|
||||
* @return \Illuminate\Http\JsonResponse
|
||||
*/
|
||||
public function transactionJournals(JournalCollectorInterface $collector, string $what)
|
||||
{
|
||||
$type = config('firefly.transactionTypesByWhat.' . $what);
|
||||
$types = [$type];
|
||||
|
||||
$collector->setTypes($types)->setLimit(100)->setPage(1);
|
||||
$return = array_unique($collector->getJournals()->pluck('description')->toArray());
|
||||
sort($return);
|
||||
|
||||
return Response::json($return);
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param JournalRepositoryInterface $repository
|
||||
*
|
||||
@ -329,6 +240,7 @@ class JsonController extends Controller
|
||||
$triggers[$key] = trans('firefly.rule_trigger_' . $key . '_choice');
|
||||
}
|
||||
}
|
||||
asort($triggers);
|
||||
|
||||
$view = view('rules.partials.trigger', compact('triggers', 'count'))->render();
|
||||
|
||||
|
@ -166,10 +166,13 @@ class RuleController extends Controller
|
||||
*/
|
||||
public function edit(Request $request, RuleRepositoryInterface $repository, Rule $rule)
|
||||
{
|
||||
$oldTriggers = $this->getCurrentTriggers($rule);
|
||||
$triggerCount = count($oldTriggers);
|
||||
$oldActions = $this->getCurrentActions($rule);
|
||||
$actionCount = count($oldActions);
|
||||
/** @var RuleGroupRepositoryInterface $ruleGroupRepository */
|
||||
$ruleGroupRepository = app(RuleGroupRepositoryInterface::class);
|
||||
$oldTriggers = $this->getCurrentTriggers($rule);
|
||||
$triggerCount = count($oldTriggers);
|
||||
$oldActions = $this->getCurrentActions($rule);
|
||||
$actionCount = count($oldActions);
|
||||
$ruleGroups = ExpandedForm::makeSelectList($ruleGroupRepository->get());
|
||||
|
||||
// has old input?
|
||||
if ($request->old()) {
|
||||
@ -191,7 +194,12 @@ class RuleController extends Controller
|
||||
Session::flash('gaEventCategory', 'rules');
|
||||
Session::flash('gaEventAction', 'edit-rule');
|
||||
|
||||
return view('rules.rule.edit', compact('rule', 'subTitle', 'primaryTrigger', 'oldTriggers', 'oldActions', 'triggerCount', 'actionCount'));
|
||||
return view(
|
||||
'rules.rule.edit', compact(
|
||||
'rule', 'subTitle',
|
||||
'primaryTrigger', 'oldTriggers', 'oldActions', 'triggerCount', 'actionCount', 'ruleGroups'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -523,7 +531,7 @@ class RuleController extends Controller
|
||||
$actions[] = view(
|
||||
'rules.partials.action',
|
||||
[
|
||||
'oldTrigger' => $entry->action_type,
|
||||
'oldAction' => $entry->action_type,
|
||||
'oldValue' => $entry->action_value,
|
||||
'oldChecked' => $entry->stop_processing,
|
||||
'count' => $count,
|
||||
|
@ -43,9 +43,6 @@ use View;
|
||||
class TagController extends Controller
|
||||
{
|
||||
|
||||
/** @var array */
|
||||
public $tagOptions = [];
|
||||
|
||||
/** @var TagRepositoryInterface */
|
||||
protected $repository;
|
||||
|
||||
@ -60,17 +57,8 @@ class TagController extends Controller
|
||||
$this->middleware(
|
||||
function ($request, $next) {
|
||||
$this->repository = app(TagRepositoryInterface::class);
|
||||
$this->tagOptions = [
|
||||
'nothing' => trans('firefly.regular_tag'),
|
||||
'balancingAct' => trans('firefly.balancing_act'),
|
||||
'advancePayment' => trans('firefly.advance_payment'),
|
||||
];
|
||||
|
||||
|
||||
View::share('title', strval(trans('firefly.tags')));
|
||||
View::share('mainTitleIcon', 'fa-tags');
|
||||
View::share('tagOptions', $this->tagOptions);
|
||||
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
@ -168,41 +156,22 @@ class TagController extends Controller
|
||||
*/
|
||||
public function index(TagRepositoryInterface $repository)
|
||||
{
|
||||
$title = 'Tags';
|
||||
$mainTitleIcon = 'fa-tags';
|
||||
$types = ['nothing', 'balancingAct', 'advancePayment'];
|
||||
$hasTypes = 0; // which types of tag the user actually has.
|
||||
$counts = []; // how many of each type?
|
||||
$count = $repository->count();
|
||||
|
||||
// loop each types and get the tags, group them by year.
|
||||
$collection = [];
|
||||
foreach ($types as $type) {
|
||||
// collect tags by year:
|
||||
/** @var Carbon $start */
|
||||
$start = clone(session('first'));
|
||||
$now = new Carbon;
|
||||
$clouds = [];
|
||||
$clouds['no-date'] = $repository->tagCloud(null);
|
||||
while ($now > $start) {
|
||||
$year = $now->year;
|
||||
$clouds[$year] = $repository->tagCloud($year);
|
||||
|
||||
/** @var Collection $tags */
|
||||
$tags = $repository->getByType($type);
|
||||
$tags = $tags->sortBy(
|
||||
function (Tag $tag) {
|
||||
$date = !is_null($tag->date) ? $tag->date->format('Ymd') : '000000';
|
||||
|
||||
return strtolower($date . $tag->tag);
|
||||
}
|
||||
);
|
||||
if ($tags->count() > 0) {
|
||||
$hasTypes++;
|
||||
}
|
||||
$counts[$type] = $tags->count();
|
||||
|
||||
/** @var Tag $tag */
|
||||
foreach ($tags as $tag) {
|
||||
$year = is_null($tag->date) ? trans('firefly.no_year') : $tag->date->year;
|
||||
$monthFormatted = is_null($tag->date) ? trans('firefly.no_month') : $tag->date->formatLocalized($this->monthFormat);
|
||||
|
||||
$collection[$type][$year][$monthFormatted][] = $tag;
|
||||
}
|
||||
$now->subYear();
|
||||
}
|
||||
$count = $repository->count();
|
||||
|
||||
return view('tags.index', compact('title', 'mainTitleIcon', 'counts', 'hasTypes', 'types', 'collection', 'count'));
|
||||
return view('tags.index', compact('clouds', 'count'));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -235,6 +204,7 @@ class TagController extends Controller
|
||||
$start = $repository->firstUseDate($tag);
|
||||
$end = new Carbon;
|
||||
$sum = $repository->sumOfTag($tag, null, null);
|
||||
$result = $repository->resultOfTag($tag, null, null);
|
||||
$path = route('tags.show', [$tag->id, 'all']);
|
||||
}
|
||||
|
||||
@ -249,6 +219,7 @@ class TagController extends Controller
|
||||
);
|
||||
$periods = $this->getPeriodOverview($tag);
|
||||
$sum = $repository->sumOfTag($tag, $start, $end);
|
||||
$result = $repository->resultOfTag($tag, $start, $end);
|
||||
$path = route('tags.show', [$tag->id, $moment]);
|
||||
}
|
||||
|
||||
@ -257,6 +228,8 @@ class TagController extends Controller
|
||||
$start = clone session('start', Navigation::startOfPeriod(new Carbon, $range));
|
||||
$end = clone session('end', Navigation::endOfPeriod(new Carbon, $range));
|
||||
$periods = $this->getPeriodOverview($tag);
|
||||
$sum = $repository->sumOfTag($tag, $start, $end);
|
||||
$result = $repository->resultOfTag($tag, $start, $end);
|
||||
$subTitle = trans(
|
||||
'firefly.journals_in_period_for_tag',
|
||||
['tag' => $tag->tag, 'start' => $start->formatLocalized($this->monthAndDayFormat), 'end' => $end->formatLocalized($this->monthAndDayFormat)]
|
||||
@ -271,10 +244,9 @@ class TagController extends Controller
|
||||
$journals->setPath($path);
|
||||
|
||||
|
||||
return view('tags.show', compact('apiKey', 'tag', 'periods', 'subTitle', 'subTitleIcon', 'journals', 'sum', 'start', 'end', 'moment'));
|
||||
return view('tags.show', compact('apiKey', 'tag', 'result', 'periods', 'subTitle', 'subTitleIcon', 'journals', 'sum', 'start', 'end', 'moment'));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param TagFormRequest $request
|
||||
*
|
||||
@ -371,4 +343,6 @@ class TagController extends Controller
|
||||
return $collection;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
143
app/Http/Controllers/Transaction/LinkController.php
Normal file
143
app/Http/Controllers/Transaction/LinkController.php
Normal file
@ -0,0 +1,143 @@
|
||||
<?php
|
||||
/**
|
||||
* LinkController.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Http\Controllers\Transaction;
|
||||
|
||||
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\Http\Requests\JournalLinkRequest;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Models\TransactionJournalLink;
|
||||
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
|
||||
use FireflyIII\Repositories\LinkType\LinkTypeRepositoryInterface;
|
||||
use Log;
|
||||
use Preferences;
|
||||
use Session;
|
||||
use URL;
|
||||
use View;
|
||||
|
||||
/**
|
||||
* Class LinkController
|
||||
*
|
||||
* @package FireflyIII\Http\Controllers\Transaction
|
||||
*/
|
||||
class LinkController extends Controller
|
||||
{
|
||||
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
// some useful repositories:
|
||||
$this->middleware(
|
||||
function ($request, $next) {
|
||||
View::share('title', trans('firefly.transactions'));
|
||||
View::share('mainTitleIcon', 'fa-repeat');
|
||||
|
||||
return $next($request);
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param TransactionJournalLink $link
|
||||
*
|
||||
* @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||
*/
|
||||
public function delete(TransactionJournalLink $link)
|
||||
{
|
||||
$subTitleIcon = 'fa-link';
|
||||
$subTitle = trans('breadcrumbs.delete_journal_link');
|
||||
$this->rememberPreviousUri('journal_links.delete.uri');
|
||||
|
||||
return view('transactions.links.delete', compact('link', 'subTitle', 'subTitleIcon'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param LinkTypeRepositoryInterface $repository
|
||||
* @param TransactionJournalLink $link
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||
*/
|
||||
public function destroy(LinkTypeRepositoryInterface $repository, TransactionJournalLink $link)
|
||||
{
|
||||
$repository->destroyLink($link);
|
||||
|
||||
Session::flash('success', strval(trans('firefly.deleted_link')));
|
||||
Preferences::mark();
|
||||
|
||||
return redirect(strval(session('journal_links.delete.uri')));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param JournalLinkRequest $request
|
||||
* @param LinkTypeRepositoryInterface $repository
|
||||
* @param JournalRepositoryInterface $journalRepository
|
||||
* @param TransactionJournal $journal
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||
*/
|
||||
public function store(
|
||||
JournalLinkRequest $request, LinkTypeRepositoryInterface $repository, JournalRepositoryInterface $journalRepository, TransactionJournal $journal
|
||||
) {
|
||||
$linkInfo = $request->getLinkInfo();
|
||||
$linkType = $repository->find($linkInfo['link_type_id']);
|
||||
$other = $journalRepository->find($linkInfo['transaction_journal_id']);
|
||||
$alreadyLinked = $repository->findLink($journal, $other);
|
||||
if ($alreadyLinked) {
|
||||
Session::flash('error', trans('firefly.journals_error_linked'));
|
||||
|
||||
return redirect(route('transactions.show', [$journal->id]));
|
||||
}
|
||||
Log::debug(sprintf('Journal is %d, opposing is %d', $journal->id, $other->id));
|
||||
|
||||
$journalLink = new TransactionJournalLink;
|
||||
$journalLink->linkType()->associate($linkType);
|
||||
if ($linkInfo['direction'] === 'inward') {
|
||||
Log::debug(sprintf('Link type is inwards ("%s"), so %d is source and %d is destination.', $linkType->inward, $other->id, $journal->id));
|
||||
$journalLink->source()->associate($other);
|
||||
$journalLink->destination()->associate($journal);
|
||||
}
|
||||
|
||||
if ($linkInfo['direction'] === 'outward') {
|
||||
Log::debug(sprintf('Link type is inwards ("%s"), so %d is source and %d is destination.', $linkType->outward, $journal->id, $other->id));
|
||||
$journalLink->source()->associate($journal);
|
||||
$journalLink->destination()->associate($other);
|
||||
}
|
||||
|
||||
$journalLink->comment = $linkInfo['comments'];
|
||||
$journalLink->save();
|
||||
Session::flash('success', trans('firefly.journals_linked'));
|
||||
|
||||
return redirect(route('transactions.show', [$journal->id]));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param LinkTypeRepositoryInterface $repository
|
||||
* @param TransactionJournalLink $link
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||
*/
|
||||
public function switch(LinkTypeRepositoryInterface $repository, TransactionJournalLink $link)
|
||||
{
|
||||
|
||||
$repository->switchLink($link);
|
||||
|
||||
return redirect(URL::previous());
|
||||
}
|
||||
|
||||
}
|
@ -14,12 +14,14 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Http\Controllers\Transaction;
|
||||
|
||||
|
||||
use Carbon\Carbon;
|
||||
use ExpandedForm;
|
||||
use FireflyIII\Events\StoredTransactionJournal;
|
||||
use FireflyIII\Events\UpdatedTransactionJournal;
|
||||
use FireflyIII\Helpers\Attachments\AttachmentHelperInterface;
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\Http\Requests\JournalFormRequest;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
@ -115,7 +117,7 @@ class SingleController extends Controller
|
||||
'foreign_amount' => $foreignAmount,
|
||||
'native_amount' => $foreignAmount,
|
||||
'amount_currency_id_amount' => $transaction->foreign_currency_id ?? 0,
|
||||
'date' => $journal->date->format('Y-m-d'),
|
||||
'date' => (new Carbon())->format('Y-m-d'),
|
||||
'budget_id' => $budgetId,
|
||||
'category' => $categoryName,
|
||||
'tags' => $tags,
|
||||
@ -142,7 +144,7 @@ class SingleController extends Controller
|
||||
{
|
||||
$what = strtolower($what);
|
||||
$uploadSize = min(Steam::phpBytes(ini_get('upload_max_filesize')), Steam::phpBytes(ini_get('post_max_size')));
|
||||
$assetAccounts = ExpandedForm::makeSelectList($this->accounts->getActiveAccountsByType([AccountType::DEFAULT, AccountType::ASSET]));
|
||||
$assetAccounts = $this->groupedActiveAccountList();
|
||||
$budgets = ExpandedForm::makeSelectListWithEmpty($this->budgets->getActiveBudgets());
|
||||
$piggyBanks = $this->piggyBanks->getPiggyBanksWithAmount();
|
||||
$piggies = ExpandedForm::makeSelectListWithEmpty($piggyBanks);
|
||||
@ -238,7 +240,7 @@ class SingleController extends Controller
|
||||
}
|
||||
|
||||
$what = strtolower($journal->transactionTypeStr());
|
||||
$assetAccounts = ExpandedForm::makeSelectList($this->accounts->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]));
|
||||
$assetAccounts = $this->groupedAccountList();
|
||||
$budgetList = ExpandedForm::makeSelectListWithEmpty($this->budgets->getBudgets());
|
||||
|
||||
// view related code
|
||||
@ -408,6 +410,46 @@ class SingleController extends Controller
|
||||
return redirect($this->getPreviousUri('transactions.edit.uri'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private function groupedAccountList(): array
|
||||
{
|
||||
$accounts = $this->accounts->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
|
||||
$return = [];
|
||||
/** @var Account $account */
|
||||
foreach ($accounts as $account) {
|
||||
$type = $account->getMeta('accountRole');
|
||||
if (strlen($type) === 0) {
|
||||
$type = 'no_account_type';
|
||||
}
|
||||
$key = strval(trans('firefly.opt_group_' . $type));
|
||||
$return[$key][$account->id] = $account->name;
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
private function groupedActiveAccountList(): array
|
||||
{
|
||||
$accounts = $this->accounts->getActiveAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
|
||||
$return = [];
|
||||
/** @var Account $account */
|
||||
foreach ($accounts as $account) {
|
||||
$type = $account->getMeta('accountRole');
|
||||
if (strlen($type) === 0) {
|
||||
$type = 'no_account_type';
|
||||
}
|
||||
$key = strval(trans('firefly.opt_group_' . $type));
|
||||
$return[$key][$account->id] = $account->name;
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TransactionJournal $journal
|
||||
*
|
||||
|
@ -18,6 +18,8 @@ use ExpandedForm;
|
||||
use FireflyIII\Events\UpdatedTransactionJournal;
|
||||
use FireflyIII\Helpers\Attachments\AttachmentHelperInterface;
|
||||
use FireflyIII\Http\Controllers\Controller;
|
||||
use FireflyIII\Http\Requests\SplitJournalFormRequest;
|
||||
use FireflyIII\Models\Account;
|
||||
use FireflyIII\Models\AccountType;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Repositories\Account\AccountRepositoryInterface;
|
||||
@ -94,13 +96,23 @@ class SplitController extends Controller
|
||||
|
||||
$uploadSize = min(Steam::phpBytes(ini_get('upload_max_filesize')), Steam::phpBytes(ini_get('post_max_size')));
|
||||
$currencies = $this->currencies->get();
|
||||
$assetAccounts = ExpandedForm::makeSelectList($this->accounts->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]));
|
||||
$accountList = $this->accounts->getAccountsByType([AccountType::DEFAULT, AccountType::ASSET]);
|
||||
$assetAccounts = ExpandedForm::makeSelectList($accountList);
|
||||
$optionalFields = Preferences::get('transaction_journal_optional_fields', [])->data;
|
||||
$budgets = ExpandedForm::makeSelectListWithEmpty($this->budgets->getActiveBudgets());
|
||||
$preFilled = $this->arrayFromJournal($request, $journal);
|
||||
$subTitle = trans('breadcrumbs.edit_journal', ['description' => $journal->description]);
|
||||
$subTitleIcon = 'fa-pencil';
|
||||
|
||||
$accountArray = [];
|
||||
// account array to display currency info:
|
||||
/** @var Account $account */
|
||||
foreach ($accountList as $account) {
|
||||
$accountArray[$account->id] = $account;
|
||||
$accountArray[$account->id]['currency_id'] = intval($account->getMeta('currency_id'));
|
||||
}
|
||||
|
||||
|
||||
Session::flash('gaEventCategory', 'transactions');
|
||||
Session::flash('gaEventAction', 'edit-split-' . $preFilled['what']);
|
||||
|
||||
@ -115,25 +127,24 @@ class SplitController extends Controller
|
||||
compact(
|
||||
'subTitleIcon', 'currencies', 'optionalFields',
|
||||
'preFilled', 'subTitle', 'uploadSize', 'assetAccounts',
|
||||
'budgets', 'journal'
|
||||
'budgets', 'journal', 'accountArray', 'previous'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param SplitJournalFormRequest $request
|
||||
* @param JournalRepositoryInterface $repository
|
||||
* @param TransactionJournal $journal
|
||||
*
|
||||
* @return $this|\Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector
|
||||
*/
|
||||
public function update(Request $request, JournalRepositoryInterface $repository, TransactionJournal $journal)
|
||||
public function update(SplitJournalFormRequest $request, JournalRepositoryInterface $repository, TransactionJournal $journal)
|
||||
{
|
||||
if ($this->isOpeningBalance($journal)) {
|
||||
return $this->redirectToAccount($journal);
|
||||
}
|
||||
|
||||
$data = $this->arrayFromInput($request);
|
||||
$journal = $repository->updateSplitJournal($journal, $data);
|
||||
/** @var array $files */
|
||||
@ -167,11 +178,11 @@ class SplitController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param SplitJournalFormRequest $request
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function arrayFromInput(Request $request): array
|
||||
private function arrayFromInput(SplitJournalFormRequest $request): array
|
||||
{
|
||||
$array = [
|
||||
'journal_description' => $request->get('journal_description'),
|
||||
@ -200,8 +211,8 @@ class SplitController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param TransactionJournal $journal
|
||||
* @param SplitJournalFormRequest|Request $request
|
||||
* @param TransactionJournal $journal
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
@ -234,6 +245,8 @@ class SplitController extends Controller
|
||||
// transactions.
|
||||
'transactions' => $this->getTransactionDataFromJournal($journal),
|
||||
];
|
||||
// update transactions array with old request data.
|
||||
$array['transactions'] = $this->updateWithPrevious($array['transactions'], $request->old());
|
||||
|
||||
return $array;
|
||||
}
|
||||
@ -282,11 +295,11 @@ class SplitController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Request $request
|
||||
* @param SplitJournalFormRequest|Request $request
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function getTransactionDataFromRequest(Request $request): array
|
||||
private function getTransactionDataFromRequest(SplitJournalFormRequest $request): array
|
||||
{
|
||||
$return = [];
|
||||
$transactions = $request->get('transactions');
|
||||
@ -312,5 +325,36 @@ class SplitController extends Controller
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $array
|
||||
* @param $old
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function updateWithPrevious($array, $old): array
|
||||
{
|
||||
if (count($old) === 0 || !isset($old['transactions'])) {
|
||||
return $array;
|
||||
}
|
||||
$old = $old['transactions'];
|
||||
foreach ($old as $index => $row) {
|
||||
if (isset($array[$index])) {
|
||||
$array[$index] = array_merge($array[$index], $row);
|
||||
continue;
|
||||
}
|
||||
// take some info from first transaction, that should at least exist.
|
||||
$array[$index] = $row;
|
||||
$array[$index]['transaction_currency_id'] = $array[0]['transaction_currency_id'];
|
||||
$array[$index]['transaction_currency_code'] = $array[0]['transaction_currency_code'];
|
||||
$array[$index]['transaction_currency_symbol'] = $array[0]['transaction_currency_symbol'];
|
||||
$array[$index]['foreign_amount'] = round($array[0]['foreign_destination_amount'] ?? '0', 12);
|
||||
$array[$index]['foreign_currency_id'] = $array[0]['foreign_currency_id'];
|
||||
$array[$index]['foreign_currency_code'] = $array[0]['foreign_currency_code'];
|
||||
$array[$index]['foreign_currency_symbol'] = $array[0]['foreign_currency_symbol'];
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -17,9 +17,11 @@ use Carbon\Carbon;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Helpers\Collector\JournalCollectorInterface;
|
||||
use FireflyIII\Helpers\Filter\InternalTransferFilter;
|
||||
use FireflyIII\Models\Transaction;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
|
||||
use FireflyIII\Repositories\Journal\JournalTaskerInterface;
|
||||
use FireflyIII\Repositories\LinkType\LinkTypeRepositoryInterface;
|
||||
use FireflyIII\Support\CacheProperties;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Collection;
|
||||
@ -27,7 +29,6 @@ use Log;
|
||||
use Navigation;
|
||||
use Preferences;
|
||||
use Response;
|
||||
use Steam;
|
||||
use View;
|
||||
|
||||
/**
|
||||
@ -150,23 +151,26 @@ class TransactionController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TransactionJournal $journal
|
||||
* @param JournalTaskerInterface $tasker
|
||||
* @param TransactionJournal $journal
|
||||
* @param JournalTaskerInterface $tasker
|
||||
*
|
||||
* @param LinkTypeRepositoryInterface $linkTypeRepository
|
||||
*
|
||||
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Routing\Redirector|View
|
||||
*/
|
||||
public function show(TransactionJournal $journal, JournalTaskerInterface $tasker)
|
||||
public function show(TransactionJournal $journal, JournalTaskerInterface $tasker, LinkTypeRepositoryInterface $linkTypeRepository)
|
||||
{
|
||||
if ($this->isOpeningBalance($journal)) {
|
||||
return $this->redirectToAccount($journal);
|
||||
}
|
||||
|
||||
$linkTypes = $linkTypeRepository->get();
|
||||
$links = $linkTypeRepository->getLinks($journal);
|
||||
$events = $tasker->getPiggyBankEvents($journal);
|
||||
$transactions = $tasker->getTransactionsOverview($journal);
|
||||
$what = strtolower($journal->transaction_type_type ?? $journal->transactionType->type);
|
||||
$subTitle = trans('firefly.' . $what) . ' "' . e($journal->description) . '"';
|
||||
|
||||
return view('transactions.show', compact('journal', 'events', 'subTitle', 'what', 'transactions'));
|
||||
return view('transactions.show', compact('journal', 'events', 'subTitle', 'what', 'transactions', 'linkTypes', 'links'));
|
||||
|
||||
|
||||
}
|
||||
@ -210,35 +214,24 @@ class TransactionController extends Controller
|
||||
$collector = app(JournalCollectorInterface::class);
|
||||
$collector->setAllAssetAccounts()->setRange($end, $currentEnd)->withOpposingAccount()->setTypes($types);
|
||||
$collector->removeFilter(InternalTransferFilter::class);
|
||||
$set = $collector->getJournals();
|
||||
$sum = $set->sum('transaction_amount');
|
||||
$journals = $set->count();
|
||||
$journals = $collector->getJournals();
|
||||
$sum = $journals->sum('transaction_amount');
|
||||
|
||||
// count per currency:
|
||||
$sums = $this->sumPerCurrency($journals);
|
||||
$dateStr = $end->format('Y-m-d');
|
||||
$dateName = Navigation::periodShow($end, $range);
|
||||
$array = [
|
||||
'string' => $dateStr,
|
||||
'name' => $dateName,
|
||||
'count' => $journals,
|
||||
'spent' => 0,
|
||||
'earned' => 0,
|
||||
'transferred' => 0,
|
||||
'date' => clone $end,
|
||||
'string' => $dateStr,
|
||||
'name' => $dateName,
|
||||
'sum' => $sum,
|
||||
'sums' => $sums,
|
||||
'date' => clone $end,
|
||||
];
|
||||
Log::debug(sprintf('What is %s', $what));
|
||||
switch ($what) {
|
||||
case 'withdrawal':
|
||||
$array['spent'] = $sum;
|
||||
break;
|
||||
case 'deposit':
|
||||
$array['earned'] = $sum;
|
||||
break;
|
||||
case 'transfers':
|
||||
case 'transfer':
|
||||
$array['transferred'] = Steam::positive($sum);
|
||||
break;
|
||||
|
||||
if ($journals->count() > 0) {
|
||||
$entries->push($array);
|
||||
}
|
||||
$entries->push($array);
|
||||
$end = Navigation::subtractPeriod($end, $range, 1);
|
||||
}
|
||||
Log::debug('End of loop');
|
||||
@ -247,4 +240,41 @@ class TransactionController extends Controller
|
||||
return $entries;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Collection $collection
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function sumPerCurrency(Collection $collection): array
|
||||
{
|
||||
$return = [];
|
||||
/** @var Transaction $transaction */
|
||||
foreach ($collection as $transaction) {
|
||||
$currencyId = $transaction->transaction_currency_id;
|
||||
|
||||
// save currency information:
|
||||
if (!isset($return[$currencyId])) {
|
||||
$currencySymbol = $transaction->transaction_currency_symbol;
|
||||
$decimalPlaces = $transaction->transaction_currency_dp;
|
||||
$currencyCode = $transaction->transaction_currency_code;
|
||||
$return[$currencyId] = [
|
||||
'currency' => [
|
||||
'id' => $currencyId,
|
||||
'code' => $currencyCode,
|
||||
'symbol' => $currencySymbol,
|
||||
'dp' => $decimalPlaces,
|
||||
],
|
||||
'sum' => '0',
|
||||
'count' => 0,
|
||||
];
|
||||
}
|
||||
// save amount:
|
||||
$return[$currencyId]['sum'] = bcadd($return[$currencyId]['sum'], $transaction->transaction_amount);
|
||||
$return[$currencyId]['count']++;
|
||||
}
|
||||
asort($return);
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -37,6 +37,8 @@ class BudgetIncomeRequest extends Request
|
||||
{
|
||||
return [
|
||||
'amount' => 'numeric|required|min:0',
|
||||
'start' => 'required|date|before:end',
|
||||
'end' => 'required|date|after:start',
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -38,10 +38,9 @@ class ExportFormRequest extends Request
|
||||
public function rules()
|
||||
{
|
||||
$sessionFirst = clone session('first');
|
||||
|
||||
$first = $sessionFirst->subDay()->format('Y-m-d');
|
||||
$today = Carbon::create()->addDay()->format('Y-m-d');
|
||||
$formats = join(',', array_keys(config('firefly.export_formats')));
|
||||
$first = $sessionFirst->subDay()->format('Y-m-d');
|
||||
$today = Carbon::create()->addDay()->format('Y-m-d');
|
||||
$formats = join(',', array_keys(config('firefly.export_formats')));
|
||||
|
||||
return [
|
||||
'export_start_range' => 'required|date|after:' . $first,
|
||||
|
@ -110,7 +110,7 @@ class JournalFormRequest extends Request
|
||||
// foreign currency amounts
|
||||
'native_amount' => 'numeric|more:0',
|
||||
'source_amount' => 'numeric|more:0',
|
||||
'destination_amount' => 'numeric|more:0',
|
||||
'destination_amount' => 'numeric',
|
||||
];
|
||||
|
||||
// some rules get an upgrade depending on the type of data:
|
||||
|
74
app/Http/Requests/JournalLinkRequest.php
Normal file
74
app/Http/Requests/JournalLinkRequest.php
Normal file
@ -0,0 +1,74 @@
|
||||
<?php
|
||||
/**
|
||||
* JournalLinkRequest.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Http\Requests;
|
||||
|
||||
use FireflyIII\Models\LinkType;
|
||||
|
||||
/**
|
||||
* Class JournalLink
|
||||
*
|
||||
*
|
||||
* @package FireflyIII\Http\Requests
|
||||
*/
|
||||
class JournalLinkRequest extends Request
|
||||
{
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
// Only allow logged in users
|
||||
return auth()->check();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getLinkInfo(): array
|
||||
{
|
||||
$return = [];
|
||||
$linkType = $this->get('link_type');
|
||||
$parts = explode('_', $linkType);
|
||||
$return['link_type_id'] = intval($parts[0]);
|
||||
$return['transaction_journal_id'] = $this->integer('link_journal_id');
|
||||
$return['comments'] = strlen($this->string('comments')) > 0 ? $this->string('comments') : null;
|
||||
$return['direction'] = $parts[1];
|
||||
if ($return['transaction_journal_id'] === 0 && ctype_digit($this->string('link_other'))) {
|
||||
$return['transaction_journal_id'] = $this->integer('link_other');
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
// all possible combinations of link types and inward / outward:
|
||||
$combinations = [];
|
||||
$linkTypes = LinkType::get(['id']);
|
||||
/** @var LinkType $type */
|
||||
foreach ($linkTypes as $type) {
|
||||
$combinations[] = sprintf('%d_inward', $type->id);
|
||||
$combinations[] = sprintf('%d_outward', $type->id);
|
||||
}
|
||||
$string = join(',', $combinations);
|
||||
|
||||
return [
|
||||
'link_type' => sprintf('required|in:%s', $string),
|
||||
'link_other' => 'belongsToUser:transaction_journals',
|
||||
'link_journal_id' => 'belongsToUser:transaction_journals',
|
||||
];
|
||||
}
|
||||
}
|
57
app/Http/Requests/LinkTypeFormRequest.php
Normal file
57
app/Http/Requests/LinkTypeFormRequest.php
Normal file
@ -0,0 +1,57 @@
|
||||
<?php
|
||||
/**
|
||||
* LinkTypeFormRequest.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Http\Requests;
|
||||
|
||||
use FireflyIII\Repositories\LinkType\LinkTypeRepositoryInterface;
|
||||
|
||||
/**
|
||||
* Class BillFormRequest
|
||||
*
|
||||
*
|
||||
* @package FireflyIII\Http\Requests
|
||||
*/
|
||||
class LinkTypeFormRequest extends Request
|
||||
{
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function authorize()
|
||||
{
|
||||
// Only allow logged and admins
|
||||
return auth()->check() && auth()->user()->hasRole('owner');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
/** @var LinkTypeRepositoryInterface $repository */
|
||||
$repository = app(LinkTypeRepositoryInterface::class);
|
||||
$nameRule = 'required|min:1|unique:link_types,name';
|
||||
$idRule = '';
|
||||
if (!is_null($repository->find($this->integer('id'))->id)) {
|
||||
$idRule = 'exists:link_types,id';
|
||||
$nameRule = 'required|min:1';
|
||||
}
|
||||
|
||||
$rules = [
|
||||
'id' => $idRule,
|
||||
'name' => $nameRule,
|
||||
'inward' => 'required|min:1|different:outward',
|
||||
'outward' => 'required|min:1|different:inward',
|
||||
];
|
||||
|
||||
return $rules;
|
||||
}
|
||||
}
|
@ -28,64 +28,17 @@ class Request extends FormRequest
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function boolean(string $field): bool
|
||||
public function boolean(string $field): bool
|
||||
{
|
||||
return intval($this->input($field)) === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $field
|
||||
*
|
||||
* @return Carbon|null
|
||||
*/
|
||||
protected function date(string $field)
|
||||
{
|
||||
return $this->get($field) ? new Carbon($this->get($field)) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $field
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
protected function float(string $field): float
|
||||
{
|
||||
return round($this->input($field), 12);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $field
|
||||
* @param string $type
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getArray(string $field, string $type): array
|
||||
{
|
||||
$original = $this->get($field);
|
||||
$return = [];
|
||||
foreach ($original as $index => $value) {
|
||||
$return[$index] = $this->$type($value);
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $field
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function integer(string $field): int
|
||||
{
|
||||
return intval($this->get($field));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $field
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function string(string $field): string
|
||||
public function string(string $field): string
|
||||
{
|
||||
$string = $this->get($field) ?? '';
|
||||
$search = [
|
||||
@ -140,4 +93,51 @@ class Request extends FormRequest
|
||||
|
||||
return trim($string);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $field
|
||||
*
|
||||
* @return Carbon|null
|
||||
*/
|
||||
protected function date(string $field)
|
||||
{
|
||||
return $this->get($field) ? new Carbon($this->get($field)) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $field
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
protected function float(string $field): float
|
||||
{
|
||||
return round($this->input($field), 12);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $field
|
||||
* @param string $type
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getArray(string $field, string $type): array
|
||||
{
|
||||
$original = $this->get($field);
|
||||
$return = [];
|
||||
foreach ($original as $index => $value) {
|
||||
$return[$index] = $this->$type($value);
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $field
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function integer(string $field): int
|
||||
{
|
||||
return intval($this->get($field));
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Http\Requests;
|
||||
|
||||
use FireflyIII\Repositories\RuleGroup\RuleGroupRepositoryInterface;
|
||||
use FireflyIII\Repositories\Rule\RuleRepositoryInterface;
|
||||
|
||||
/**
|
||||
* Class RuleFormRequest
|
||||
@ -39,6 +39,7 @@ class RuleFormRequest extends Request
|
||||
{
|
||||
return [
|
||||
'title' => $this->string('title'),
|
||||
'rule_group_id' => $this->integer('rule_group_id'),
|
||||
'active' => $this->boolean('active'),
|
||||
'trigger' => $this->string('trigger'),
|
||||
'description' => $this->string('description'),
|
||||
@ -57,19 +58,18 @@ class RuleFormRequest extends Request
|
||||
*/
|
||||
public function rules()
|
||||
{
|
||||
/** @var RuleGroupRepositoryInterface $repository */
|
||||
$repository = app(RuleGroupRepositoryInterface::class);
|
||||
/** @var RuleRepositoryInterface $repository */
|
||||
$repository = app(RuleRepositoryInterface::class);
|
||||
$validTriggers = array_keys(config('firefly.rule-triggers'));
|
||||
$validActions = array_keys(config('firefly.rule-actions'));
|
||||
|
||||
// some actions require text:
|
||||
$contextActions = join(',', config('firefly.rule-actions-text'));
|
||||
|
||||
$titleRule = 'required|between:1,100|uniqueObjectForUser:rule_groups,title';
|
||||
$titleRule = 'required|between:1,100|uniqueObjectForUser:rules,title';
|
||||
if (!is_null($repository->find(intval($this->get('id')))->id)) {
|
||||
$titleRule = 'required|between:1,100|uniqueObjectForUser:rule_groups,title,' . intval($this->get('id'));
|
||||
$titleRule = 'required|between:1,100|uniqueObjectForUser:rules,title,' . intval($this->get('id'));
|
||||
}
|
||||
|
||||
$rules = [
|
||||
'title' => $titleRule,
|
||||
'description' => 'between:1,5000',
|
||||
|
@ -61,23 +61,23 @@ class SplitJournalFormRequest extends Request
|
||||
public function rules(): array
|
||||
{
|
||||
return [
|
||||
'what' => 'required|in:withdrawal,deposit,transfer',
|
||||
'journal_description' => 'required|between:1,255',
|
||||
'id' => 'numeric|belongsToUser:transaction_journals,id',
|
||||
'journal_source_account_id' => 'numeric|belongsToUser:accounts,id',
|
||||
'journal_source_account_name.*' => 'between:1,255',
|
||||
'journal_currency_id' => 'required|exists:transaction_currencies,id',
|
||||
'date' => 'required|date',
|
||||
'interest_date' => 'date',
|
||||
'book_date' => 'date',
|
||||
'process_date' => 'date',
|
||||
'description.*' => 'required|between:1,255',
|
||||
'destination_account_id.*' => 'numeric|belongsToUser:accounts,id',
|
||||
'destination_account_name.*' => 'between:1,255',
|
||||
'amount.*' => 'required|numeric',
|
||||
'budget_id.*' => 'belongsToUser:budgets,id',
|
||||
'category.*' => 'between:1,255',
|
||||
'piggy_bank_id.*' => 'between:1,255',
|
||||
'what' => 'required|in:withdrawal,deposit,transfer',
|
||||
'journal_description' => 'required|between:1,255',
|
||||
'id' => 'numeric|belongsToUser:transaction_journals,id',
|
||||
'journal_source_account_id' => 'numeric|belongsToUser:accounts,id',
|
||||
'journal_source_account_name.*' => 'between:1,255',
|
||||
'journal_currency_id' => 'required|exists:transaction_currencies,id',
|
||||
'date' => 'required|date',
|
||||
'interest_date' => 'date',
|
||||
'book_date' => 'date',
|
||||
'process_date' => 'date',
|
||||
'transactions.*.description' => 'required|between:1,255',
|
||||
'transactions.*.destination_account_id' => 'numeric|belongsToUser:accounts,id',
|
||||
'transactions.*.destination_account_name' => 'between:1,255',
|
||||
'transactions.*.amount' => 'required|numeric',
|
||||
'transactions.*.budget_id' => 'belongsToUser:budgets,id',
|
||||
'transactions.*.category' => 'between:1,255',
|
||||
'transactions.*.piggy_bank_id' => 'between:1,255',
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -37,14 +37,13 @@ class TagFormRequest extends Request
|
||||
*/
|
||||
public function collectTagData(): array
|
||||
{
|
||||
$latitude = null;
|
||||
$longitude = null;
|
||||
$zoomLevel = null;
|
||||
if ($this->get('setTag') === 'true') {
|
||||
$latitude = $this->string('latitude');
|
||||
$longitude = $this->string('longitude');
|
||||
$zoomLevel = $this->integer('zoomLevel');
|
||||
} else {
|
||||
$latitude = null;
|
||||
$longitude = null;
|
||||
$zoomLevel = null;
|
||||
}
|
||||
|
||||
$data = [
|
||||
|
@ -21,12 +21,14 @@ use FireflyIII\Models\Budget;
|
||||
use FireflyIII\Models\BudgetLimit;
|
||||
use FireflyIII\Models\Category;
|
||||
use FireflyIII\Models\ImportJob;
|
||||
use FireflyIII\Models\LinkType;
|
||||
use FireflyIII\Models\PiggyBank;
|
||||
use FireflyIII\Models\Rule;
|
||||
use FireflyIII\Models\RuleGroup;
|
||||
use FireflyIII\Models\Tag;
|
||||
use FireflyIII\Models\TransactionCurrency;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Models\TransactionJournalLink;
|
||||
use FireflyIII\Models\TransactionType;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Support\Collection;
|
||||
@ -152,6 +154,49 @@ Breadcrumbs::register(
|
||||
);
|
||||
|
||||
|
||||
Breadcrumbs::register(
|
||||
'admin.links.index', function (BreadCrumbGenerator $breadcrumbs) {
|
||||
$breadcrumbs->parent('admin.index');
|
||||
$breadcrumbs->push(trans('firefly.journal_link_configuration'), route('admin.links.index'));
|
||||
}
|
||||
);
|
||||
|
||||
Breadcrumbs::register(
|
||||
'admin.links.create', function (BreadCrumbGenerator $breadcrumbs) {
|
||||
$breadcrumbs->parent('admin.links.index');
|
||||
$breadcrumbs->push(trans('firefly.create_new_link_type'), route('admin.links.create'));
|
||||
}
|
||||
);
|
||||
|
||||
Breadcrumbs::register(
|
||||
'admin.links.show', function (BreadCrumbGenerator $breadcrumbs, LinkType $linkType) {
|
||||
$breadcrumbs->parent('admin.links.index');
|
||||
$breadcrumbs->push(trans('firefly.overview_for_link', [$linkType->name]), route('admin.links.show', [$linkType->id]));
|
||||
}
|
||||
);
|
||||
|
||||
Breadcrumbs::register(
|
||||
'admin.links.edit', function (BreadCrumbGenerator $breadcrumbs, LinkType $linkType) {
|
||||
$breadcrumbs->parent('admin.links.index');
|
||||
$breadcrumbs->push(trans('firefly.edit_link_type', ['name' => $linkType->name]), route('admin.links.edit', [$linkType->id]));
|
||||
}
|
||||
);
|
||||
|
||||
Breadcrumbs::register(
|
||||
'admin.links.delete', function (BreadCrumbGenerator $breadcrumbs, LinkType $linkType) {
|
||||
$breadcrumbs->parent('admin.links.index');
|
||||
$breadcrumbs->push(trans('firefly.delete_link_type', ['name' => $linkType->name]), route('admin.links.delete', [$linkType->id]));
|
||||
}
|
||||
);
|
||||
|
||||
Breadcrumbs::register(
|
||||
'transactions.link.delete', function (BreadCrumbGenerator $breadcrumbs, TransactionJournalLink $link) {
|
||||
$breadcrumbs->parent('home');
|
||||
$breadcrumbs->push(trans('breadcrumbs.delete_journal_link'), route('transactions.link.delete', $link->id));
|
||||
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* ATTACHMENTS
|
||||
*/
|
||||
|
@ -13,6 +13,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Import\Converter;
|
||||
|
||||
use Log;
|
||||
|
||||
/**
|
||||
* Class RabobankDebetCredit
|
||||
*
|
||||
@ -34,31 +36,40 @@ class Amount implements ConverterInterface
|
||||
*/
|
||||
public function convert($value): string
|
||||
{
|
||||
Log::debug(sprintf('Start with amount "%s"', $value));
|
||||
$len = strlen($value);
|
||||
$decimalPosition = $len - 3;
|
||||
$decimal = null;
|
||||
|
||||
if (($len > 2 && $value{$decimalPosition} === '.') || ($len > 2 && strpos($value, '.') > $decimalPosition)) {
|
||||
$decimal = '.';
|
||||
Log::debug(sprintf('Decimal character in "%s" seems to be a dot.', $value));
|
||||
}
|
||||
if ($len > 2 && $value{$decimalPosition} === ',') {
|
||||
$decimal = ',';
|
||||
Log::debug(sprintf('Decimal character in "%s" seems to be a comma.', $value));
|
||||
}
|
||||
|
||||
// if decimal is dot, replace all comma's and spaces with nothing. then parse as float (round to 4 pos)
|
||||
if ($decimal === '.') {
|
||||
$search = [',', ' '];
|
||||
$value = str_replace($search, '', $value);
|
||||
$search = [',', ' '];
|
||||
$oldValue = $value;
|
||||
$value = str_replace($search, '', $value);
|
||||
Log::debug(sprintf('Converted amount from "%s" to "%s".', $oldValue, $value));
|
||||
}
|
||||
if ($decimal === ',') {
|
||||
$search = ['.', ' '];
|
||||
$value = str_replace($search, '', $value);
|
||||
$value = str_replace(',', '.', $value);
|
||||
$search = ['.', ' '];
|
||||
$oldValue = $value;
|
||||
$value = str_replace($search, '', $value);
|
||||
$value = str_replace(',', '.', $value);
|
||||
Log::debug(sprintf('Converted amount from "%s" to "%s".', $oldValue, $value));
|
||||
}
|
||||
if (is_null($decimal)) {
|
||||
// replace all:
|
||||
$search = ['.', ' ', ','];
|
||||
$value = str_replace($search, '', $value);
|
||||
$search = ['.', ' ', ','];
|
||||
$oldValue = $value;
|
||||
$value = str_replace($search, '', $value);
|
||||
Log::debug(sprintf('No decimal character found. Converted amount from "%s" to "%s".', $oldValue, $value));
|
||||
}
|
||||
|
||||
return strval(round(floatval($value), 12));
|
||||
|
@ -151,10 +151,14 @@ class CsvProcessor implements FileProcessorInterface
|
||||
*/
|
||||
private function getImportArray(): Iterator
|
||||
{
|
||||
$content = $this->job->uploadFileContents();
|
||||
$config = $this->job->configuration;
|
||||
$reader = Reader::createFromString($content);
|
||||
$reader->setDelimiter($config['delimiter']);
|
||||
$content = $this->job->uploadFileContents();
|
||||
$config = $this->job->configuration;
|
||||
$reader = Reader::createFromString($content);
|
||||
$delimiter = $config['delimiter'];
|
||||
if ($delimiter === 'tab') {
|
||||
$delimiter = "\t";
|
||||
}
|
||||
$reader->setDelimiter($delimiter);
|
||||
$start = $config['has-headers'] ? 1 : 0;
|
||||
$results = $reader->setOffset($start)->fetch();
|
||||
Log::debug(sprintf('Created a CSV reader starting at offset %d', $start));
|
||||
|
@ -37,6 +37,8 @@ class ImportJournal
|
||||
public $budget;
|
||||
/** @var ImportCategory */
|
||||
public $category;
|
||||
/** @var ImportCurrency */
|
||||
public $currency;
|
||||
/** @var string */
|
||||
public $description = '';
|
||||
/** @var string */
|
||||
@ -51,8 +53,8 @@ class ImportJournal
|
||||
public $tags = [];
|
||||
/** @var string */
|
||||
private $amount;
|
||||
/** @var ImportCurrency */
|
||||
public $currency;
|
||||
/** @var string */
|
||||
private $convertedAmount = null;
|
||||
/** @var string */
|
||||
private $date = '';
|
||||
/** @var string */
|
||||
@ -85,25 +87,37 @@ class ImportJournal
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @throws FireflyException
|
||||
*/
|
||||
public function getAmount(): string
|
||||
{
|
||||
if (is_null($this->amount)) {
|
||||
Log::debug('Now in getAmount()');
|
||||
if (is_null($this->convertedAmount)) {
|
||||
Log::debug('convertedAmount is NULL');
|
||||
/** @var ConverterInterface $amountConverter */
|
||||
$amountConverter = app(Amount::class);
|
||||
$this->amount = $amountConverter->convert($this->amount);
|
||||
$amountConverter = app(Amount::class);
|
||||
$this->convertedAmount = $amountConverter->convert($this->amount);
|
||||
Log::debug(sprintf('First attempt to convert gives "%s"', $this->convertedAmount));
|
||||
// modify
|
||||
foreach ($this->modifiers as $modifier) {
|
||||
$class = sprintf('FireflyIII\Import\Converter\%s', config(sprintf('csv.import_roles.%s.converter', $modifier['role'])));
|
||||
/** @var ConverterInterface $converter */
|
||||
$converter = app($class);
|
||||
Log::debug(sprintf('Now launching converter %s', $class));
|
||||
if ($converter->convert($modifier['value']) === -1) {
|
||||
$this->amount = Steam::negative($this->amount);
|
||||
$this->convertedAmount = Steam::negative($this->convertedAmount);
|
||||
}
|
||||
Log::debug(sprintf('convertedAmount after conversion is %s', $this->convertedAmount));
|
||||
}
|
||||
|
||||
Log::debug(sprintf('After modifiers the result is: "%s"', $this->convertedAmount));
|
||||
}
|
||||
Log::debug(sprintf('convertedAmount is: "%s"', $this->convertedAmount));
|
||||
if (bccomp($this->convertedAmount, '0') === 0) {
|
||||
throw new FireflyException('Amount is zero.');
|
||||
}
|
||||
|
||||
return $this->amount;
|
||||
return $this->convertedAmount;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -59,7 +59,8 @@ class RabobankDescription implements SpecificInterface
|
||||
);
|
||||
$row[6] = $alternateName;
|
||||
$row[10] = '';
|
||||
} else {
|
||||
}
|
||||
if (!(strlen($oppositeAccount) < 1 && strlen($oppositeName) < 1)) {
|
||||
Log::debug('Rabobank specific: either opposite account or name are filled.');
|
||||
}
|
||||
|
||||
|
@ -11,6 +11,8 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Import\Storage;
|
||||
|
||||
use ErrorException;
|
||||
use Exception;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Import\Object\ImportJournal;
|
||||
use FireflyIII\Models\ImportJob;
|
||||
@ -95,7 +97,7 @@ class ImportStorage
|
||||
function (ImportJournal $importJournal, int $index) {
|
||||
try {
|
||||
$this->storeImportJournal($index, $importJournal);
|
||||
} catch (FireflyException $e) {
|
||||
} catch (FireflyException | ErrorException | Exception $e) {
|
||||
$this->errors->push($e->getMessage());
|
||||
Log::error(sprintf('Cannot import row #%d because: %s', $index, $e->getMessage()));
|
||||
}
|
||||
@ -142,7 +144,10 @@ class ImportStorage
|
||||
if ($this->isDoubleTransfer($parameters) || $this->hashAlreadyImported($importJournal->hash)) {
|
||||
$this->job->addStepsDone(3);
|
||||
// throw error
|
||||
throw new FireflyException('Detected a possible duplicate, skip this one.');
|
||||
$message = sprintf('Detected a possible duplicate, skip this one (hash: %s).', $importJournal->hash);
|
||||
Log::error($message, $parameters);
|
||||
throw new FireflyException($message);
|
||||
|
||||
}
|
||||
unset($parameters);
|
||||
|
||||
@ -202,25 +207,32 @@ class ImportStorage
|
||||
return false;
|
||||
}
|
||||
|
||||
$amount = app('steam')->positive($parameters['amount']);
|
||||
$names = [$parameters['asset'], $parameters['opposing']];
|
||||
$amount = app('steam')->positive($parameters['amount']);
|
||||
$names = [$parameters['asset'], $parameters['opposing']];
|
||||
$transfer = [];
|
||||
$hit = false;
|
||||
sort($names);
|
||||
|
||||
foreach ($this->transfers as $transfer) {
|
||||
if ($parameters['description'] !== $transfer['description']) {
|
||||
return false;
|
||||
if ($parameters['description'] === $transfer['description']) {
|
||||
$hit = true;
|
||||
}
|
||||
if ($names !== $transfer['names']) {
|
||||
return false;
|
||||
if ($names === $transfer['names']) {
|
||||
$hit = true;
|
||||
}
|
||||
if (bccomp($amount, $transfer['amount']) !== 0) {
|
||||
return false;
|
||||
if (bccomp($amount, $transfer['amount']) === 0) {
|
||||
$hit = true;
|
||||
}
|
||||
if ($parameters['date'] !== $transfer['date']) {
|
||||
return false;
|
||||
if ($parameters['date'] === $transfer['date']) {
|
||||
$hit = true;
|
||||
}
|
||||
}
|
||||
if ($hit === true) {
|
||||
Log::error(
|
||||
'There already is a transfer imported with these properties. Compare existing with new. ', ['existing' => $transfer, 'new' => $parameters]
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
return $hit;
|
||||
}
|
||||
}
|
||||
|
@ -214,7 +214,7 @@ trait ImportSupport
|
||||
*/
|
||||
private function getTransactionType(string $amount, Account $account): string
|
||||
{
|
||||
$transactionType = '';
|
||||
$transactionType = TransactionType::WITHDRAWAL;
|
||||
// amount is negative, it's a withdrawal, opposing is an expense:
|
||||
if (bccomp($amount, '0') === -1) {
|
||||
$transactionType = TransactionType::WITHDRAWAL;
|
||||
@ -296,12 +296,15 @@ trait ImportSupport
|
||||
*/
|
||||
private function hashAlreadyImported(string $hash): bool
|
||||
{
|
||||
$json = json_encode($hash);
|
||||
$json = json_encode($hash);
|
||||
/** @var TransactionJournalMeta $entry */
|
||||
$entry = TransactionJournalMeta::leftJoin('transaction_journals', 'transaction_journals.id', '=', 'journal_meta.transaction_journal_id')
|
||||
->where('data', $json)
|
||||
->where('name', 'importHash')
|
||||
->first();
|
||||
if (!is_null($entry)) {
|
||||
Log::error(sprintf('A journal with hash %s has already been imported (spoiler: it\'s journal #%d)', $hash, $entry->transaction_journal_id));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -441,4 +444,4 @@ trait ImportSupport
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,15 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* RegisteredUser.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
/**
|
||||
|
@ -1,6 +1,15 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* RequestedNewPassword.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
/**
|
||||
|
@ -17,6 +17,7 @@ use Crypt;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
use Watson\Validating\ValidatingTrait;
|
||||
|
||||
@ -28,7 +29,7 @@ use Watson\Validating\ValidatingTrait;
|
||||
class Bill extends Model
|
||||
{
|
||||
|
||||
use ValidatingTrait;
|
||||
use SoftDeletes, ValidatingTrait;
|
||||
/**
|
||||
* The attributes that should be casted to native types.
|
||||
*
|
||||
|
@ -13,12 +13,15 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Models;
|
||||
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
/**
|
||||
* Class ExportJob
|
||||
*
|
||||
* @property User $user
|
||||
*
|
||||
* @package FireflyIII\Models
|
||||
*/
|
||||
class ExportJob extends Model
|
||||
|
62
app/Models/LinkType.php
Normal file
62
app/Models/LinkType.php
Normal file
@ -0,0 +1,62 @@
|
||||
<?php
|
||||
/**
|
||||
* LinkType.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Models;
|
||||
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
/**
|
||||
* @property int $journalCount
|
||||
* Class LinkType
|
||||
*
|
||||
* @package FireflyIII\Models
|
||||
*/
|
||||
class LinkType extends Model
|
||||
{
|
||||
/**
|
||||
* The attributes that should be casted to native types.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $casts
|
||||
= [
|
||||
'created_at' => 'date',
|
||||
'updated_at' => 'date',
|
||||
'deleted_at' => 'date',
|
||||
'editable' => 'boolean',
|
||||
];
|
||||
|
||||
/**
|
||||
* @param $value
|
||||
*
|
||||
* @return mixed
|
||||
* @throws NotFoundHttpException
|
||||
*/
|
||||
public static function routeBinder($value)
|
||||
{
|
||||
if (auth()->check()) {
|
||||
$model = self::where('id', $value)->first();
|
||||
if (!is_null($model)) {
|
||||
return $model;
|
||||
}
|
||||
}
|
||||
throw new NotFoundHttpException;
|
||||
}
|
||||
|
||||
public function transactionJournalLinks()
|
||||
{
|
||||
return $this->hasMany(TransactionJournalLink::class);
|
||||
}
|
||||
|
||||
}
|
@ -84,6 +84,7 @@ class PiggyBank extends Model
|
||||
return $this->currentRep;
|
||||
}
|
||||
// repeating piggy banks are no longer supported.
|
||||
/** @var PiggyBankRepetition $rep */
|
||||
$rep = $this->piggyBankRepetitions()->first(['piggy_bank_repetitions.*']);
|
||||
if (is_null($rep)) {
|
||||
return new PiggyBankRepetition();
|
||||
|
@ -14,6 +14,7 @@ declare(strict_types=1);
|
||||
namespace FireflyIII\Models;
|
||||
|
||||
use Crypt;
|
||||
use Exception;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use Illuminate\Contracts\Encryption\DecryptException;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
@ -56,7 +57,15 @@ class Preference extends Model
|
||||
sprintf('Could not decrypt preference #%d. If this error persists, please run "php artisan cache:clear" on the command line.', $this->id)
|
||||
);
|
||||
}
|
||||
|
||||
$unserialized = false;
|
||||
try {
|
||||
$unserialized = unserialize($data);
|
||||
} catch (Exception $e) {
|
||||
// don't care, assume is false.
|
||||
}
|
||||
if (!($unserialized === false)) {
|
||||
return $unserialized;
|
||||
}
|
||||
|
||||
return json_decode($data, true);
|
||||
}
|
||||
@ -66,7 +75,7 @@ class Preference extends Model
|
||||
*/
|
||||
public function setDataAttribute($value)
|
||||
{
|
||||
$this->attributes['data'] = Crypt::encrypt(json_encode($value));
|
||||
$this->attributes['data'] = Crypt::encrypt(serialize($value));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -22,6 +22,48 @@ use Watson\Validating\ValidatingTrait;
|
||||
/**
|
||||
* Class Transaction
|
||||
*
|
||||
* @property-read int $journal_id
|
||||
* @property-read Carbon $date
|
||||
* @property-read string $transaction_description
|
||||
* @property-read string $transaction_amount
|
||||
* @property-read string $transaction_foreign_amount
|
||||
* @property-read string $transaction_type_type
|
||||
*
|
||||
* @property-read int $account_id
|
||||
* @property-read string $account_name
|
||||
* @property string $account_iban
|
||||
* @property string $account_number
|
||||
* @property string $account_bic
|
||||
* @property string $account_currency_code
|
||||
*
|
||||
* @property-read int $opposing_account_id
|
||||
* @property string $opposing_account_name
|
||||
* @property string $opposing_account_iban
|
||||
* @property string $opposing_account_number
|
||||
* @property string $opposing_account_bic
|
||||
* @property string $opposing_currency_code
|
||||
*
|
||||
*
|
||||
* @property-read int $transaction_budget_id
|
||||
* @property-read string $transaction_budget_name
|
||||
* @property-read int $transaction_journal_budget_id
|
||||
* @property-read string $transaction_journal_budget_name
|
||||
*
|
||||
* @property-read int $transaction_category_id
|
||||
* @property-read string $transaction_category_name
|
||||
* @property-read int $transaction_journal_category_id
|
||||
* @property-read string $transaction_journal_category_name
|
||||
*
|
||||
* @property-read int $bill_id
|
||||
* @property string $bill_name
|
||||
*
|
||||
* @property string $notes
|
||||
* @property string $tags
|
||||
*
|
||||
* @property string $transaction_currency_symbol
|
||||
* @property int $transaction_currency_dp
|
||||
* @property string $transaction_currency_code
|
||||
*
|
||||
* @package FireflyIII\Models
|
||||
*/
|
||||
class Transaction extends Model
|
||||
|
@ -140,6 +140,14 @@ class TransactionJournal extends Model
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return HasMany
|
||||
*/
|
||||
public function destinationJournalLinks(): HasMany
|
||||
{
|
||||
return $this->hasMany(TransactionJournalLink::class, 'destination_id');
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param $value
|
||||
@ -372,6 +380,14 @@ class TransactionJournal extends Model
|
||||
return $entry;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return HasMany
|
||||
*/
|
||||
public function sourceJournalLinks(): HasMany
|
||||
{
|
||||
return $this->hasMany(TransactionJournalLink::class, 'source_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
|
||||
*/
|
||||
|
105
app/Models/TransactionJournalLink.php
Normal file
105
app/Models/TransactionJournalLink.php
Normal file
@ -0,0 +1,105 @@
|
||||
<?php
|
||||
/**
|
||||
* TransactionJournalLink.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Models;
|
||||
|
||||
|
||||
use Crypt;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
|
||||
|
||||
/**
|
||||
* Class TransactionJournalLink
|
||||
*
|
||||
* @package FireflyIII\Models
|
||||
*/
|
||||
class TransactionJournalLink extends Model
|
||||
{
|
||||
protected $table = 'journal_links';
|
||||
|
||||
/**
|
||||
* @param $value
|
||||
*
|
||||
* @return mixed
|
||||
* @throws NotFoundHttpException
|
||||
*/
|
||||
public static function routeBinder($value)
|
||||
{
|
||||
if (auth()->check()) {
|
||||
$model = self::where('journal_links.id', $value)
|
||||
->leftJoin('transaction_journals as t_a', 't_a.id', '=', 'source_id')
|
||||
->leftJoin('transaction_journals as t_b', 't_b.id', '=', 'destination_id')
|
||||
->where('t_a.user_id', auth()->user()->id)
|
||||
->where('t_b.user_id', auth()->user()->id)
|
||||
->first(['journal_links.*']);
|
||||
if (!is_null($model)) {
|
||||
return $model;
|
||||
}
|
||||
}
|
||||
throw new NotFoundHttpException;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function destination()
|
||||
{
|
||||
return $this->belongsTo(TransactionJournal::class, 'destination_id');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $value
|
||||
*
|
||||
* @return null|string
|
||||
*/
|
||||
public function getCommentAttribute($value): ?string
|
||||
{
|
||||
if (!is_null($value)) {
|
||||
return Crypt::decrypt($value);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function linkType(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(LinkType::class);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param $value
|
||||
*/
|
||||
public function setCommentAttribute($value): void
|
||||
{
|
||||
if (!is_null($value) && strlen($value) > 0) {
|
||||
$this->attributes['comment'] = Crypt::encrypt($value);
|
||||
|
||||
return;
|
||||
}
|
||||
$this->attributes['comment'] = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
*/
|
||||
public function source()
|
||||
{
|
||||
return $this->belongsTo(TransactionJournal::class, 'source_id');
|
||||
}
|
||||
|
||||
|
||||
}
|
63
app/Providers/AdminServiceProvider.php
Normal file
63
app/Providers/AdminServiceProvider.php
Normal file
@ -0,0 +1,63 @@
|
||||
<?php
|
||||
/**
|
||||
* AdminServiceProvider.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Providers;
|
||||
|
||||
use FireflyIII\Repositories\LinkType\LinkTypeRepository;
|
||||
use FireflyIII\Repositories\LinkType\LinkTypeRepositoryInterface;
|
||||
use Illuminate\Foundation\Application;
|
||||
use Illuminate\Support\ServiceProvider;
|
||||
|
||||
|
||||
class AdminServiceProvider extends ServiceProvider
|
||||
{
|
||||
/**
|
||||
* Bootstrap the application services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function boot()
|
||||
{
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the application services.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register()
|
||||
{
|
||||
$this->linkType();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private function linkType()
|
||||
{
|
||||
$this->app->bind(
|
||||
LinkTypeRepositoryInterface::class,
|
||||
function (Application $app) {
|
||||
/** @var LinkTypeRepository $repository */
|
||||
$repository = app(LinkTypeRepository::class);
|
||||
if ($app->auth->check()) {
|
||||
$repository->setUser(auth()->user());
|
||||
}
|
||||
|
||||
return $repository;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
}
|
@ -13,7 +13,7 @@ declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Providers;
|
||||
|
||||
use FireflyIII\Export\Processor;
|
||||
use FireflyIII\Export\ExpandedProcessor;
|
||||
use FireflyIII\Export\ProcessorInterface;
|
||||
use FireflyIII\Generator\Chart\Basic\ChartJsGenerator;
|
||||
use FireflyIII\Generator\Chart\Basic\GeneratorInterface;
|
||||
@ -138,7 +138,8 @@ class FireflyServiceProvider extends ServiceProvider
|
||||
);
|
||||
|
||||
// other generators
|
||||
$this->app->bind(ProcessorInterface::class, Processor::class);
|
||||
// export:
|
||||
$this->app->bind(ProcessorInterface::class, ExpandedProcessor::class);
|
||||
$this->app->bind(UserRepositoryInterface::class, UserRepository::class);
|
||||
$this->app->bind(AttachmentHelperInterface::class, AttachmentHelper::class);
|
||||
|
||||
|
@ -205,10 +205,12 @@ trait FindAccountsTrait
|
||||
*/
|
||||
public function getCashAccount(): Account
|
||||
{
|
||||
$type = AccountType::where('type', AccountType::CASH)->first();
|
||||
$account = Account::firstOrCreateEncrypted(
|
||||
['user_id' => $this->user->id, 'account_type_id' => $type->id, 'name' => 'Cash account', 'active' => 1]
|
||||
$type = AccountType::where('type', AccountType::CASH)->first();
|
||||
$account = Account::firstOrCreateEncrypted(
|
||||
['user_id' => $this->user->id, 'account_type_id' => $type->id, 'name' => 'Cash account']
|
||||
);
|
||||
$account->active = true;
|
||||
$account->save();
|
||||
|
||||
return $account;
|
||||
}
|
||||
|
@ -116,7 +116,7 @@ class BillRepository implements BillRepositoryInterface
|
||||
$set = $set->sortBy(
|
||||
function (Bill $bill) {
|
||||
|
||||
$int = $bill->active === 1 ? 0 : 1;
|
||||
$int = $bill->active ? 0 : 1;
|
||||
|
||||
return $int . strtolower($bill->name);
|
||||
}
|
||||
|
@ -166,6 +166,16 @@ class CurrencyRepository implements CurrencyRepositoryInterface
|
||||
return TransactionCurrency::get();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $ids
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function getByIds(array $ids): Collection
|
||||
{
|
||||
return TransactionCurrency::whereIn('id', $ids)->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Preference $preference
|
||||
*
|
||||
|
@ -90,6 +90,13 @@ interface CurrencyRepositoryInterface
|
||||
*/
|
||||
public function get(): Collection;
|
||||
|
||||
/**
|
||||
* @param array $ids
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function getByIds(array $ids): Collection;
|
||||
|
||||
/**
|
||||
* @param Preference $preference
|
||||
*
|
||||
|
@ -117,8 +117,11 @@ trait SupportJournalsTrait
|
||||
if (strlen($data['source_account_name']) > 0) {
|
||||
$sourceType = AccountType::where('type', 'Revenue account')->first();
|
||||
$sourceAccount = Account::firstOrCreateEncrypted(
|
||||
['user_id' => $user->id, 'account_type_id' => $sourceType->id, 'name' => $data['source_account_name'], 'active' => 1]
|
||||
['user_id' => $user->id, 'account_type_id' => $sourceType->id, 'name' => $data['source_account_name']]
|
||||
);
|
||||
// always make account active
|
||||
$sourceAccount->active = true;
|
||||
$sourceAccount->save();
|
||||
|
||||
Log::debug(sprintf('source account name is "%s", account is %d', $data['source_account_name'], $sourceAccount->id));
|
||||
|
||||
@ -132,8 +135,11 @@ trait SupportJournalsTrait
|
||||
|
||||
$sourceType = AccountType::where('type', AccountType::CASH)->first();
|
||||
$sourceAccount = Account::firstOrCreateEncrypted(
|
||||
['user_id' => $user->id, 'account_type_id' => $sourceType->id, 'name' => 'Cash account', 'active' => 1]
|
||||
['user_id' => $user->id, 'account_type_id' => $sourceType->id, 'name' => 'Cash account']
|
||||
);
|
||||
// always make account active
|
||||
$sourceAccount->active = true;
|
||||
$sourceAccount->save();
|
||||
|
||||
return [
|
||||
'source' => $sourceAccount,
|
||||
@ -161,10 +167,13 @@ trait SupportJournalsTrait
|
||||
'user_id' => $user->id,
|
||||
'account_type_id' => $destinationType->id,
|
||||
'name' => $data['destination_account_name'],
|
||||
'active' => 1,
|
||||
]
|
||||
);
|
||||
|
||||
// always make account active
|
||||
$destinationAccount->active = true;
|
||||
$destinationAccount->save();
|
||||
|
||||
Log::debug(sprintf('destination account name is "%s", account is %d', $data['destination_account_name'], $destinationAccount->id));
|
||||
|
||||
return [
|
||||
@ -175,8 +184,11 @@ trait SupportJournalsTrait
|
||||
Log::debug('destination_account_name is empty, so default to cash account!');
|
||||
$destinationType = AccountType::where('type', AccountType::CASH)->first();
|
||||
$destinationAccount = Account::firstOrCreateEncrypted(
|
||||
['user_id' => $user->id, 'account_type_id' => $destinationType->id, 'name' => 'Cash account', 'active' => 1]
|
||||
['user_id' => $user->id, 'account_type_id' => $destinationType->id, 'name' => 'Cash account']
|
||||
);
|
||||
// always make account active
|
||||
$destinationAccount->active = true;
|
||||
$destinationAccount->save();
|
||||
|
||||
return [
|
||||
'source' => $sourceAccount,
|
||||
|
181
app/Repositories/LinkType/LinkTypeRepository.php
Normal file
181
app/Repositories/LinkType/LinkTypeRepository.php
Normal file
@ -0,0 +1,181 @@
|
||||
<?php
|
||||
/**
|
||||
* LinkTypeRepository.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Repositories\LinkType;
|
||||
|
||||
use FireflyIII\Models\LinkType;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Models\TransactionJournalLink;
|
||||
use FireflyIII\User;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
* Class LinkTypeRepository
|
||||
*
|
||||
* @package FireflyIII\Repositories\LinkType
|
||||
*/
|
||||
class LinkTypeRepository implements LinkTypeRepositoryInterface
|
||||
{
|
||||
/** @var User */
|
||||
private $user;
|
||||
|
||||
/**
|
||||
* @param LinkType $linkType
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function countJournals(LinkType $linkType): int
|
||||
{
|
||||
return $linkType->transactionJournalLinks()->count() * 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param LinkType $linkType
|
||||
* @param LinkType $moveTo
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function destroy(LinkType $linkType, LinkType $moveTo): bool
|
||||
{
|
||||
if (!is_null($moveTo->id)) {
|
||||
TransactionJournalLink::where('link_type_id', $linkType->id)->update(['link_type_id' => $moveTo->id]);
|
||||
}
|
||||
$linkType->delete();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TransactionJournalLink $link
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function destroyLink(TransactionJournalLink $link): bool
|
||||
{
|
||||
$link->delete();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $id
|
||||
*
|
||||
* @return LinkType
|
||||
*/
|
||||
public function find(int $id): LinkType
|
||||
{
|
||||
$linkType = LinkType::find($id);
|
||||
if (is_null($linkType)) {
|
||||
return new LinkType;
|
||||
}
|
||||
|
||||
return $linkType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if link exists between journals.
|
||||
*
|
||||
* @param TransactionJournal $one
|
||||
* @param TransactionJournal $two
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function findLink(TransactionJournal $one, TransactionJournal $two): bool
|
||||
{
|
||||
$count = TransactionJournalLink::whereDestinationId($one->id)->whereSourceId($two->id)->count();
|
||||
$opposingCount = TransactionJournalLink::whereDestinationId($two->id)->whereSourceId($one->id)->count();
|
||||
|
||||
return ($count + $opposingCount > 0);
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection
|
||||
*/
|
||||
public function get(): Collection
|
||||
{
|
||||
return LinkType::orderBy('name', 'ASC')->get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return list of existing connections.
|
||||
*
|
||||
* @param TransactionJournal $journal
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function getLinks(TransactionJournal $journal): Collection
|
||||
{
|
||||
$outward = TransactionJournalLink::whereSourceId($journal->id)->get();
|
||||
$inward = TransactionJournalLink::whereDestinationId($journal->id)->get();
|
||||
|
||||
return $outward->merge($inward);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param User $user
|
||||
*/
|
||||
public function setUser(User $user)
|
||||
{
|
||||
$this->user = $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
*
|
||||
* @return LinkType
|
||||
*/
|
||||
public function store(array $data): LinkType
|
||||
{
|
||||
$linkType = new LinkType;
|
||||
$linkType->name = $data['name'];
|
||||
$linkType->inward = $data['inward'];
|
||||
$linkType->outward = $data['outward'];
|
||||
$linkType->editable = true;
|
||||
$linkType->save();
|
||||
|
||||
return $linkType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TransactionJournalLink $link
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function switchLink(TransactionJournalLink $link): bool
|
||||
{
|
||||
$source = $link->source_id;
|
||||
$link->source_id = $link->destination_id;
|
||||
$link->destination_id = $source;
|
||||
$link->save();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param LinkType $linkType
|
||||
* @param array $data
|
||||
*
|
||||
* @return LinkType
|
||||
*/
|
||||
public function update(LinkType $linkType, array $data): LinkType
|
||||
{
|
||||
$linkType->name = $data['name'];
|
||||
$linkType->inward = $data['inward'];
|
||||
$linkType->outward = $data['outward'];
|
||||
$linkType->save();
|
||||
|
||||
return $linkType;
|
||||
|
||||
}
|
||||
}
|
102
app/Repositories/LinkType/LinkTypeRepositoryInterface.php
Normal file
102
app/Repositories/LinkType/LinkTypeRepositoryInterface.php
Normal file
@ -0,0 +1,102 @@
|
||||
<?php
|
||||
/**
|
||||
* LinkTypeRepositoryInterface.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Repositories\LinkType;
|
||||
|
||||
use FireflyIII\Models\LinkType;
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use FireflyIII\Models\TransactionJournalLink;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
* Interface LinkTypeRepositoryInterface
|
||||
*
|
||||
* @package FireflyIII\Repositories\LinkType
|
||||
*/
|
||||
interface LinkTypeRepositoryInterface
|
||||
{
|
||||
/**
|
||||
* @param LinkType $linkType
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function countJournals(LinkType $linkType): int;
|
||||
|
||||
/**
|
||||
* @param LinkType $linkType
|
||||
* @param LinkType $moveTo
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function destroy(LinkType $linkType, LinkType $moveTo): bool;
|
||||
|
||||
/**
|
||||
* @param TransactionJournalLink $link
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function destroyLink(TransactionJournalLink $link): bool;
|
||||
|
||||
/**
|
||||
* @param int $id
|
||||
*
|
||||
* @return LinkType
|
||||
*/
|
||||
public function find(int $id): LinkType;
|
||||
|
||||
/**
|
||||
* Check if link exists between journals.
|
||||
*
|
||||
* @param TransactionJournal $one
|
||||
* @param TransactionJournal $two
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function findLink(TransactionJournal $one, TransactionJournal $two): bool;
|
||||
|
||||
/**
|
||||
* @return Collection
|
||||
*/
|
||||
public function get(): Collection;
|
||||
|
||||
/**
|
||||
* Return list of existing connections.
|
||||
*
|
||||
* @param TransactionJournal $journal
|
||||
*
|
||||
* @return Collection
|
||||
*/
|
||||
public function getLinks(TransactionJournal $journal): Collection;
|
||||
|
||||
/**
|
||||
* @param array $data
|
||||
*
|
||||
* @return LinkType
|
||||
*/
|
||||
public function store(array $data): LinkType;
|
||||
|
||||
/**
|
||||
* @param TransactionJournalLink $link
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function switchLink(TransactionJournalLink $link): bool;
|
||||
|
||||
/**
|
||||
* @param LinkType $linkType
|
||||
* @param array $data
|
||||
*
|
||||
* @return LinkType
|
||||
*/
|
||||
public function update(LinkType $linkType, array $data): LinkType;
|
||||
|
||||
}
|
@ -56,6 +56,21 @@ class RuleRepository implements RuleRepositoryInterface
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $ruleId
|
||||
*
|
||||
* @return Rule
|
||||
*/
|
||||
public function find(int $ruleId): Rule
|
||||
{
|
||||
$rule = $this->user->rules()->find($ruleId);
|
||||
if (is_null($rule)) {
|
||||
return new Rule;
|
||||
}
|
||||
|
||||
return $rule;
|
||||
}
|
||||
|
||||
/**
|
||||
* FIxXME can return null
|
||||
*
|
||||
@ -286,7 +301,7 @@ class RuleRepository implements RuleRepositoryInterface
|
||||
$ruleTrigger->active = 1;
|
||||
$ruleTrigger->stop_processing = $values['stopProcessing'];
|
||||
$ruleTrigger->trigger_type = $values['action'];
|
||||
$ruleTrigger->trigger_value = $values['value'];
|
||||
$ruleTrigger->trigger_value = is_null($values['value']) ? '' : $values['value'];
|
||||
$ruleTrigger->save();
|
||||
|
||||
return $ruleTrigger;
|
||||
@ -301,6 +316,7 @@ class RuleRepository implements RuleRepositoryInterface
|
||||
public function update(Rule $rule, array $data): Rule
|
||||
{
|
||||
// update rule:
|
||||
$rule->rule_group_id = $data['rule_group_id'];
|
||||
$rule->active = $data['active'];
|
||||
$rule->stop_processing = $data['stop_processing'];
|
||||
$rule->title = $data['title'];
|
||||
|
@ -38,6 +38,14 @@ interface RuleRepositoryInterface
|
||||
*/
|
||||
public function destroy(Rule $rule): bool;
|
||||
|
||||
/**
|
||||
* @param int $ruleId
|
||||
*
|
||||
* @return Rule
|
||||
*/
|
||||
public function find(int $ruleId): Rule;
|
||||
|
||||
|
||||
/**
|
||||
* @return RuleGroup
|
||||
*/
|
||||
|
@ -186,6 +186,34 @@ class TagRepository implements TagRepositoryInterface
|
||||
return new Carbon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Same as sum of tag but substracts income instead of adding it as well.
|
||||
*
|
||||
* @param Tag $tag
|
||||
* @param Carbon|null $start
|
||||
* @param Carbon|null $end
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function resultOfTag(Tag $tag, ?Carbon $start, ?Carbon $end): string
|
||||
{
|
||||
/** @var JournalCollectorInterface $collector */
|
||||
$collector = app(JournalCollectorInterface::class);
|
||||
|
||||
if (!is_null($start) && !is_null($end)) {
|
||||
$collector->setRange($start, $end);
|
||||
}
|
||||
|
||||
$collector->setAllAssetAccounts()->setTag($tag);
|
||||
$journals = $collector->getJournals();
|
||||
$sum = '0';
|
||||
foreach ($journals as $journal) {
|
||||
$sum = bcadd($sum, strval($journal->transaction_amount));
|
||||
}
|
||||
|
||||
return strval($sum);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param User $user
|
||||
*/
|
||||
@ -253,11 +281,71 @@ class TagRepository implements TagRepositoryInterface
|
||||
}
|
||||
|
||||
$collector->setAllAssetAccounts()->setTag($tag);
|
||||
$sum = $collector->getJournals()->sum('transaction_amount');
|
||||
$journals = $collector->getJournals();
|
||||
$sum = '0';
|
||||
foreach ($journals as $journal) {
|
||||
$sum = bcadd($sum, app('steam')->positive(strval($journal->transaction_amount)));
|
||||
}
|
||||
|
||||
return strval($sum);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a tag cloud.
|
||||
*
|
||||
* @param int|null $year
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function tagCloud(?int $year): array
|
||||
{
|
||||
$min = null;
|
||||
$max = 0;
|
||||
$query = $this->user->tags();
|
||||
$return = [];
|
||||
Log::debug('Going to build tag-cloud');
|
||||
if (!is_null($year)) {
|
||||
Log::debug(sprintf('Year is not null: %d', $year));
|
||||
$start = $year . '-01-01';
|
||||
$end = $year . '-12-31';
|
||||
$query->where('date', '>=', $start)->where('date', '<=', $end);
|
||||
}
|
||||
if (is_null($year)) {
|
||||
$query->whereNull('date');
|
||||
Log::debug('Year is NULL');
|
||||
}
|
||||
$tags = $query->orderBy('id', 'desc')->get();
|
||||
$temporary = [];
|
||||
Log::debug(sprintf('Found %d tags', $tags->count()));
|
||||
/** @var Tag $tag */
|
||||
foreach ($tags as $tag) {
|
||||
|
||||
$amount = floatval($this->sumOfTag($tag, null, null));
|
||||
$min = $amount < $min || is_null($min) ? $amount : $min;
|
||||
$max = $amount > $max ? $amount : $max;
|
||||
$temporary[] = [
|
||||
'amount' => $amount,
|
||||
'tag' => $tag,
|
||||
];
|
||||
Log::debug(sprintf('Now working on tag %s with total amount %s', $tag->tag, $amount));
|
||||
Log::debug(sprintf('Minimum is now %f, maximum is %f', $min, $max));
|
||||
}
|
||||
/** @var array $entry */
|
||||
foreach ($temporary as $entry) {
|
||||
$scale = $this->cloudScale([12, 20], $entry['amount'], $min, $max);
|
||||
$tagId = $entry['tag']->id;
|
||||
$return[$tagId] = [
|
||||
'scale' => $scale,
|
||||
'tag' => $entry['tag'],
|
||||
];
|
||||
}
|
||||
|
||||
Log::debug('DONE with tagcloud');
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param Tag $tag
|
||||
* @param array $data
|
||||
@ -277,4 +365,38 @@ class TagRepository implements TagRepositoryInterface
|
||||
return $tag;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $range
|
||||
* @param float $amount
|
||||
* @param float $min
|
||||
* @param float $max
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
private function cloudScale(array $range, float $amount, float $min, float $max): int
|
||||
{
|
||||
Log::debug(sprintf('Now in cloudScale with %s as amount and %f min, %f max', $amount, $min, $max));
|
||||
$amountDiff = $max - $min;
|
||||
Log::debug(sprintf('AmountDiff is %f', $amountDiff));
|
||||
|
||||
// no difference? Every tag same range:
|
||||
if ($amountDiff === 0.0) {
|
||||
Log::debug(sprintf('AmountDiff is zero, return %d', $range[0]));
|
||||
|
||||
return $range[0];
|
||||
}
|
||||
|
||||
$diff = $range[1] - $range[0];
|
||||
$step = 1;
|
||||
if ($diff != 0) {
|
||||
$step = $amountDiff / $diff;
|
||||
}
|
||||
if ($step == 0) {
|
||||
$step = 1;
|
||||
}
|
||||
$extra = round($amount / $step);
|
||||
|
||||
|
||||
return intval($range[0] + $extra);
|
||||
}
|
||||
}
|
||||
|
@ -103,6 +103,15 @@ interface TagRepositoryInterface
|
||||
*/
|
||||
public function lastUseDate(Tag $tag): Carbon;
|
||||
|
||||
/**
|
||||
* @param Tag $tag
|
||||
* @param Carbon|null $start
|
||||
* @param Carbon|null $end
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function resultOfTag(Tag $tag, ?Carbon $start, ?Carbon $end): string;
|
||||
|
||||
/**
|
||||
* @param User $user
|
||||
*/
|
||||
@ -135,6 +144,15 @@ interface TagRepositoryInterface
|
||||
*/
|
||||
public function sumOfTag(Tag $tag, ?Carbon $start, ?Carbon $end): string;
|
||||
|
||||
/**
|
||||
* Generates a tag cloud.
|
||||
*
|
||||
* @param int|null $year
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function tagCloud(?int $year): array;
|
||||
|
||||
/**
|
||||
* Update a tag.
|
||||
*
|
||||
|
@ -118,7 +118,8 @@ final class Processor
|
||||
{
|
||||
$self = new self;
|
||||
foreach ($triggers as $entry) {
|
||||
$trigger = TriggerFactory::makeTriggerFromStrings($entry['type'], $entry['value'], $entry['stopProcessing']);
|
||||
$entry['value'] = is_null($entry['value']) ? '' : $entry['value'];
|
||||
$trigger = TriggerFactory::makeTriggerFromStrings($entry['type'], $entry['value'], $entry['stopProcessing']);
|
||||
$self->triggers->push($trigger);
|
||||
}
|
||||
|
||||
|
65
app/Rules/Triggers/HasAnyBudget.php
Normal file
65
app/Rules/Triggers/HasAnyBudget.php
Normal file
@ -0,0 +1,65 @@
|
||||
<?php
|
||||
/**
|
||||
* HasAnyBudget.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Rules\Triggers;
|
||||
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use Log;
|
||||
|
||||
/**
|
||||
* Class HasAnyBudget
|
||||
*
|
||||
* @package FireflyIII\Rules\Triggers
|
||||
*/
|
||||
final class HasAnyBudget extends AbstractTrigger implements TriggerInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* A trigger is said to "match anything", or match any given transaction,
|
||||
* when the trigger value is very vague or has no restrictions. Easy examples
|
||||
* are the "AmountMore"-trigger combined with an amount of 0: any given transaction
|
||||
* has an amount of more than zero! Other examples are all the "Description"-triggers
|
||||
* which have hard time handling empty trigger values such as "" or "*" (wild cards).
|
||||
*
|
||||
* If the user tries to create such a trigger, this method MUST return true so Firefly III
|
||||
* can stop the storing / updating the trigger. If the trigger is in any way restrictive
|
||||
* (even if it will still include 99.9% of the users transactions), this method MUST return
|
||||
* false.
|
||||
*
|
||||
* @param null $value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function willMatchEverything($value = null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TransactionJournal $journal
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function triggered(TransactionJournal $journal): bool
|
||||
{
|
||||
$count = $journal->budgets()->count();
|
||||
if ($count > 0) {
|
||||
Log::debug(sprintf('RuleTrigger HasAnyBudget for journal #%d: count is %d, return true.', $journal->id, $count));
|
||||
|
||||
return true;
|
||||
}
|
||||
Log::debug(sprintf('RuleTrigger HasAnyBudget for journal #%d: count is %d, return false.', $journal->id, $count));
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
}
|
65
app/Rules/Triggers/HasAnyCategory.php
Normal file
65
app/Rules/Triggers/HasAnyCategory.php
Normal file
@ -0,0 +1,65 @@
|
||||
<?php
|
||||
/**
|
||||
* HasAnyCategory.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Rules\Triggers;
|
||||
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use Log;
|
||||
|
||||
/**
|
||||
* Class HasAnyCategory
|
||||
*
|
||||
* @package FireflyIII\Rules\Triggers
|
||||
*/
|
||||
final class HasAnyCategory extends AbstractTrigger implements TriggerInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* A trigger is said to "match anything", or match any given transaction,
|
||||
* when the trigger value is very vague or has no restrictions. Easy examples
|
||||
* are the "AmountMore"-trigger combined with an amount of 0: any given transaction
|
||||
* has an amount of more than zero! Other examples are all the "Description"-triggers
|
||||
* which have hard time handling empty trigger values such as "" or "*" (wild cards).
|
||||
*
|
||||
* If the user tries to create such a trigger, this method MUST return true so Firefly III
|
||||
* can stop the storing / updating the trigger. If the trigger is in any way restrictive
|
||||
* (even if it will still include 99.9% of the users transactions), this method MUST return
|
||||
* false.
|
||||
*
|
||||
* @param null $value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function willMatchEverything($value = null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TransactionJournal $journal
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function triggered(TransactionJournal $journal): bool
|
||||
{
|
||||
$count = $journal->categories()->count();
|
||||
if ($count > 0) {
|
||||
Log::debug(sprintf('RuleTrigger HasAnyCategory for journal #%d: count is %d, return true.', $journal->id, $count));
|
||||
|
||||
return true;
|
||||
}
|
||||
Log::debug(sprintf('RuleTrigger HasAnyCategory for journal #%d: count is %d, return false.', $journal->id, $count));
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
}
|
65
app/Rules/Triggers/HasAnyTag.php
Normal file
65
app/Rules/Triggers/HasAnyTag.php
Normal file
@ -0,0 +1,65 @@
|
||||
<?php
|
||||
/**
|
||||
* HasAnyTag.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Rules\Triggers;
|
||||
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use Log;
|
||||
|
||||
/**
|
||||
* Class HasAnyTag
|
||||
*
|
||||
* @package FireflyIII\Rules\Triggers
|
||||
*/
|
||||
final class HasAnyTag extends AbstractTrigger implements TriggerInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* A trigger is said to "match anything", or match any given transaction,
|
||||
* when the trigger value is very vague or has no restrictions. Easy examples
|
||||
* are the "AmountMore"-trigger combined with an amount of 0: any given transaction
|
||||
* has an amount of more than zero! Other examples are all the "Description"-triggers
|
||||
* which have hard time handling empty trigger values such as "" or "*" (wild cards).
|
||||
*
|
||||
* If the user tries to create such a trigger, this method MUST return true so Firefly III
|
||||
* can stop the storing / updating the trigger. If the trigger is in any way restrictive
|
||||
* (even if it will still include 99.9% of the users transactions), this method MUST return
|
||||
* false.
|
||||
*
|
||||
* @param null $value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function willMatchEverything($value = null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TransactionJournal $journal
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function triggered(TransactionJournal $journal): bool
|
||||
{
|
||||
$count = $journal->tags()->count();
|
||||
if ($count > 0) {
|
||||
Log::debug(sprintf('RuleTrigger HasAnyTag for journal #%d: count is %d, return true.', $journal->id, $count));
|
||||
|
||||
return true;
|
||||
}
|
||||
Log::debug(sprintf('RuleTrigger HasAnyTag for journal #%d: count is %d, return false.', $journal->id, $count));
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
}
|
65
app/Rules/Triggers/HasNoBudget.php
Normal file
65
app/Rules/Triggers/HasNoBudget.php
Normal file
@ -0,0 +1,65 @@
|
||||
<?php
|
||||
/**
|
||||
* HasNoBudget.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Rules\Triggers;
|
||||
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use Log;
|
||||
|
||||
/**
|
||||
* Class HasNoBudget
|
||||
*
|
||||
* @package FireflyIII\Rules\Triggers
|
||||
*/
|
||||
final class HasNoBudget extends AbstractTrigger implements TriggerInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* A trigger is said to "match anything", or match any given transaction,
|
||||
* when the trigger value is very vague or has no restrictions. Easy examples
|
||||
* are the "AmountMore"-trigger combined with an amount of 0: any given transaction
|
||||
* has an amount of more than zero! Other examples are all the "Description"-triggers
|
||||
* which have hard time handling empty trigger values such as "" or "*" (wild cards).
|
||||
*
|
||||
* If the user tries to create such a trigger, this method MUST return true so Firefly III
|
||||
* can stop the storing / updating the trigger. If the trigger is in any way restrictive
|
||||
* (even if it will still include 99.9% of the users transactions), this method MUST return
|
||||
* false.
|
||||
*
|
||||
* @param null $value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function willMatchEverything($value = null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TransactionJournal $journal
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function triggered(TransactionJournal $journal): bool
|
||||
{
|
||||
$count = $journal->budgets()->count();
|
||||
if ($count === 0) {
|
||||
Log::debug(sprintf('RuleTrigger HasNoBudget for journal #%d: count is %d, return true.', $journal->id, $count));
|
||||
|
||||
return true;
|
||||
}
|
||||
Log::debug(sprintf('RuleTrigger HasNoBudget for journal #%d: count is %d, return false.', $journal->id, $count));
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
}
|
65
app/Rules/Triggers/HasNoCategory.php
Normal file
65
app/Rules/Triggers/HasNoCategory.php
Normal file
@ -0,0 +1,65 @@
|
||||
<?php
|
||||
/**
|
||||
* HasNoCategory.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Rules\Triggers;
|
||||
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use Log;
|
||||
|
||||
/**
|
||||
* Class HasNoCategory
|
||||
*
|
||||
* @package FireflyIII\Rules\Triggers
|
||||
*/
|
||||
final class HasNoCategory extends AbstractTrigger implements TriggerInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* A trigger is said to "match anything", or match any given transaction,
|
||||
* when the trigger value is very vague or has no restrictions. Easy examples
|
||||
* are the "AmountMore"-trigger combined with an amount of 0: any given transaction
|
||||
* has an amount of more than zero! Other examples are all the "Description"-triggers
|
||||
* which have hard time handling empty trigger values such as "" or "*" (wild cards).
|
||||
*
|
||||
* If the user tries to create such a trigger, this method MUST return true so Firefly III
|
||||
* can stop the storing / updating the trigger. If the trigger is in any way restrictive
|
||||
* (even if it will still include 99.9% of the users transactions), this method MUST return
|
||||
* false.
|
||||
*
|
||||
* @param null $value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function willMatchEverything($value = null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TransactionJournal $journal
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function triggered(TransactionJournal $journal): bool
|
||||
{
|
||||
$count = $journal->categories()->count();
|
||||
if ($count === 0) {
|
||||
Log::debug(sprintf('RuleTrigger HasNoCategory for journal #%d: count is %d, return true.', $journal->id, $count));
|
||||
|
||||
return true;
|
||||
}
|
||||
Log::debug(sprintf('RuleTrigger HasNoCategory for journal #%d: count is %d, return false.', $journal->id, $count));
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
}
|
65
app/Rules/Triggers/HasNoTag.php
Normal file
65
app/Rules/Triggers/HasNoTag.php
Normal file
@ -0,0 +1,65 @@
|
||||
<?php
|
||||
/**
|
||||
* HasNoTag.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Rules\Triggers;
|
||||
|
||||
use FireflyIII\Models\TransactionJournal;
|
||||
use Log;
|
||||
|
||||
/**
|
||||
* Class HasNoTag
|
||||
*
|
||||
* @package FireflyIII\Rules\Triggers
|
||||
*/
|
||||
final class HasNoTag extends AbstractTrigger implements TriggerInterface
|
||||
{
|
||||
|
||||
/**
|
||||
* A trigger is said to "match anything", or match any given transaction,
|
||||
* when the trigger value is very vague or has no restrictions. Easy examples
|
||||
* are the "AmountMore"-trigger combined with an amount of 0: any given transaction
|
||||
* has an amount of more than zero! Other examples are all the "Description"-triggers
|
||||
* which have hard time handling empty trigger values such as "" or "*" (wild cards).
|
||||
*
|
||||
* If the user tries to create such a trigger, this method MUST return true so Firefly III
|
||||
* can stop the storing / updating the trigger. If the trigger is in any way restrictive
|
||||
* (even if it will still include 99.9% of the users transactions), this method MUST return
|
||||
* false.
|
||||
*
|
||||
* @param null $value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function willMatchEverything($value = null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param TransactionJournal $journal
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function triggered(TransactionJournal $journal): bool
|
||||
{
|
||||
$count = $journal->tags()->count();
|
||||
if ($count === 0) {
|
||||
Log::debug(sprintf('RuleTrigger HasNoTag for journal #%d: count is %d, return true.', $journal->id, $count));
|
||||
|
||||
return true;
|
||||
}
|
||||
Log::debug(sprintf('RuleTrigger HasNoTag for journal #%d: count is %d, return false.', $journal->id, $count));
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
}
|
43
app/Services/Bunq/Id/BunqId.php
Normal file
43
app/Services/Bunq/Id/BunqId.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* BunqId.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Services\Bunq\Id;
|
||||
|
||||
/**
|
||||
* Class BunqId
|
||||
*
|
||||
* @package FireflyIII\Services\Bunq\Id
|
||||
*/
|
||||
class BunqId
|
||||
{
|
||||
/** @var int */
|
||||
private $id = 0;
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getId(): int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $id
|
||||
*/
|
||||
public function setId(int $id)
|
||||
{
|
||||
$this->id = $id;
|
||||
}
|
||||
|
||||
|
||||
}
|
25
app/Services/Bunq/Id/DeviceServerId.php
Normal file
25
app/Services/Bunq/Id/DeviceServerId.php
Normal file
@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* DeviceServerId.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Services\Bunq\Id;
|
||||
|
||||
|
||||
/**
|
||||
* Class DeviceServerId
|
||||
*
|
||||
* @package Bunq\Id
|
||||
*/
|
||||
class DeviceServerId extends BunqId
|
||||
{
|
||||
|
||||
}
|
24
app/Services/Bunq/Id/DeviceSessionId.php
Normal file
24
app/Services/Bunq/Id/DeviceSessionId.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
/**
|
||||
* DeviceSessionId.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Services\Bunq\Id;
|
||||
|
||||
|
||||
/**
|
||||
* Class DeviceSessionId
|
||||
*
|
||||
* @package Bunq\Id
|
||||
*/
|
||||
class DeviceSessionId extends BunqId
|
||||
{
|
||||
|
||||
}
|
24
app/Services/Bunq/Id/InstallationId.php
Normal file
24
app/Services/Bunq/Id/InstallationId.php
Normal file
@ -0,0 +1,24 @@
|
||||
<?php
|
||||
/**
|
||||
* InstallationId.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Services\Bunq\Id;
|
||||
|
||||
|
||||
/**
|
||||
* Class InstallationId
|
||||
*
|
||||
* @package Bunq\Id
|
||||
*/
|
||||
class InstallationId extends BunqId
|
||||
{
|
||||
|
||||
}
|
68
app/Services/Bunq/Object/Alias.php
Normal file
68
app/Services/Bunq/Object/Alias.php
Normal file
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
/**
|
||||
* Alias.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Services\Bunq\Object;
|
||||
|
||||
/**
|
||||
* Class Alias
|
||||
*
|
||||
* @package FireflyIII\Services\Bunq\Object
|
||||
*/
|
||||
class Alias extends BunqObject
|
||||
{
|
||||
/** @var string */
|
||||
private $name = '';
|
||||
/** @var string */
|
||||
private $type = '';
|
||||
/** @var string */
|
||||
private $value = '';
|
||||
|
||||
/**
|
||||
* Alias constructor.
|
||||
*
|
||||
* @param array $data
|
||||
*/
|
||||
public function __construct(array $data)
|
||||
{
|
||||
$this->type = $data['type'];
|
||||
$this->name = $data['name'];
|
||||
$this->value = $data['value'];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getType(): string
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getValue(): string
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
|
||||
}
|
57
app/Services/Bunq/Object/Amount.php
Normal file
57
app/Services/Bunq/Object/Amount.php
Normal file
@ -0,0 +1,57 @@
|
||||
<?php
|
||||
/**
|
||||
* Currency.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Services\Bunq\Object;
|
||||
|
||||
/**
|
||||
* Class Amount
|
||||
*
|
||||
* @package FireflyIII\Services\Bunq\Object
|
||||
*/
|
||||
class Amount extends BunqObject
|
||||
{
|
||||
/** @var string */
|
||||
private $currency = '';
|
||||
/** @var string */
|
||||
private $value = '';
|
||||
|
||||
/**
|
||||
* Amount constructor.
|
||||
*
|
||||
* @param array $data
|
||||
*/
|
||||
public function __construct(array $data)
|
||||
{
|
||||
$this->currency = $data['currency'];
|
||||
$this->value = $data['value'];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getCurrency(): string
|
||||
{
|
||||
return $this->currency;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getValue(): string
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
|
||||
}
|
23
app/Services/Bunq/Object/Avatar.php
Normal file
23
app/Services/Bunq/Object/Avatar.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
/**
|
||||
* Avatar.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Services\Bunq\Object;
|
||||
|
||||
/**
|
||||
* Class Avatar
|
||||
*
|
||||
* @package FireflyIII\Services\Bunq\Object
|
||||
*/
|
||||
class Avatar extends BunqObject
|
||||
{
|
||||
|
||||
}
|
23
app/Services/Bunq/Object/BunqObject.php
Normal file
23
app/Services/Bunq/Object/BunqObject.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
/**
|
||||
* BunqObject.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Services\Bunq\Object;
|
||||
|
||||
/**
|
||||
* Class BunqObject
|
||||
*
|
||||
* @package FireflyIII\Services\Bunq\Object
|
||||
*/
|
||||
class BunqObject
|
||||
{
|
||||
|
||||
}
|
63
app/Services/Bunq/Object/DeviceServer.php
Normal file
63
app/Services/Bunq/Object/DeviceServer.php
Normal file
@ -0,0 +1,63 @@
|
||||
<?php
|
||||
/**
|
||||
* DeviceServer.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Services\Bunq\Object;
|
||||
|
||||
|
||||
use Carbon\Carbon;
|
||||
use FireflyIII\Services\Bunq\Id\DeviceServerId;
|
||||
|
||||
class DeviceServer extends BunqObject
|
||||
{
|
||||
/** @var Carbon */
|
||||
private $created;
|
||||
/** @var string */
|
||||
private $description;
|
||||
/** @var DeviceServerId */
|
||||
private $id;
|
||||
/** @var string */
|
||||
private $ip;
|
||||
/** @var string */
|
||||
private $status;
|
||||
/** @var Carbon */
|
||||
private $updated;
|
||||
|
||||
public function __construct(array $data)
|
||||
{
|
||||
$id = new DeviceServerId();
|
||||
$id->setId($data['id']);
|
||||
$this->id = $id;
|
||||
$this->created = Carbon::createFromFormat('Y-m-d H:i:s.u', $data['created']);
|
||||
$this->updated = Carbon::createFromFormat('Y-m-d H:i:s.u', $data['updated']);
|
||||
$this->ip = $data['ip'];
|
||||
$this->description = $data['description'];
|
||||
$this->status = $data['status'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return DeviceServerId
|
||||
*/
|
||||
public function getId(): DeviceServerId
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getIp(): string
|
||||
{
|
||||
return $this->ip;
|
||||
}
|
||||
|
||||
|
||||
}
|
151
app/Services/Bunq/Object/MonetaryAccountBank.php
Normal file
151
app/Services/Bunq/Object/MonetaryAccountBank.php
Normal file
@ -0,0 +1,151 @@
|
||||
<?php
|
||||
/**
|
||||
* MonetaryAccountBank.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Services\Bunq\Object;
|
||||
|
||||
use Carbon\Carbon;
|
||||
|
||||
/**
|
||||
* Class MonetaryAccountBank
|
||||
*
|
||||
* @package FireflyIII\Services\Bunq\Object
|
||||
*/
|
||||
class MonetaryAccountBank extends BunqObject
|
||||
{
|
||||
/** @var array */
|
||||
private $aliases = [];
|
||||
/** @var Avatar */
|
||||
private $avatar;
|
||||
/** @var Amount */
|
||||
private $balance;
|
||||
/** @var Carbon */
|
||||
private $created;
|
||||
/** @var string */
|
||||
private $currency = '';
|
||||
/** @var Amount */
|
||||
private $dailyLimit;
|
||||
/** @var Amount */
|
||||
private $dailySpent;
|
||||
/** @var string */
|
||||
private $description = '';
|
||||
/** @var int */
|
||||
private $id = 0;
|
||||
/** @var MonetaryAccountProfile */
|
||||
private $monetaryAccountProfile;
|
||||
/** @var array */
|
||||
private $notificationFilters = [];
|
||||
/** @var Amount */
|
||||
private $overdraftLimit;
|
||||
/** @var string */
|
||||
private $publicUuid = '';
|
||||
/** @var string */
|
||||
private $reason = '';
|
||||
/** @var string */
|
||||
private $reasonDescription = '';
|
||||
/** @var MonetaryAccountSetting */
|
||||
private $setting;
|
||||
/** @var string */
|
||||
private $status = '';
|
||||
/** @var string */
|
||||
private $subStatus = '';
|
||||
/** @var Carbon */
|
||||
private $updated;
|
||||
/** @var int */
|
||||
private $userId = 0;
|
||||
|
||||
/**
|
||||
* MonetaryAccountBank constructor.
|
||||
*
|
||||
* @param array $data
|
||||
*/
|
||||
public function __construct(array $data)
|
||||
{
|
||||
$this->id = $data['id'];
|
||||
$this->created = Carbon::createFromFormat('Y-m-d H:i:s.u', $data['created']);
|
||||
$this->updated = Carbon::createFromFormat('Y-m-d H:i:s.u', $data['updated']);
|
||||
$this->balance = new Amount($data['balance']);
|
||||
$this->currency = $data['currency'];
|
||||
$this->dailyLimit = new Amount($data['daily_limit']);
|
||||
$this->dailySpent = new Amount($data['daily_spent']);
|
||||
$this->description = $data['description'];
|
||||
$this->publicUuid = $data['public_uuid'];
|
||||
$this->status = $data['status'];
|
||||
$this->subStatus = $data['sub_status'];
|
||||
$this->userId = $data['user_id'];
|
||||
$this->status = $data['status'];
|
||||
$this->subStatus = $data['sub_status'];
|
||||
$this->monetaryAccountProfile = new MonetaryAccountProfile($data['monetary_account_profile']);
|
||||
$this->setting = new MonetaryAccountSetting($data['setting']);
|
||||
$this->overdraftLimit = new Amount($data['overdraft_limit']);
|
||||
|
||||
$this->publicUuid = $data['public_uuid'];
|
||||
|
||||
// create aliases:
|
||||
foreach ($data['alias'] as $alias) {
|
||||
$this->aliases[] = new Alias($alias);
|
||||
}
|
||||
foreach ($data['notification_filters'] as $filter) {
|
||||
$this->notificationFilters = new NotificationFilter($filter);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getAliases(): array
|
||||
{
|
||||
return $this->aliases;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Amount
|
||||
*/
|
||||
public function getBalance(): Amount
|
||||
{
|
||||
return $this->balance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getCurrency(): string
|
||||
{
|
||||
return $this->currency;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getDescription(): string
|
||||
{
|
||||
return $this->description;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getId(): int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return MonetaryAccountSetting
|
||||
*/
|
||||
public function getSetting(): MonetaryAccountSetting
|
||||
{
|
||||
return $this->setting;
|
||||
}
|
||||
|
||||
}
|
44
app/Services/Bunq/Object/MonetaryAccountProfile.php
Normal file
44
app/Services/Bunq/Object/MonetaryAccountProfile.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
/**
|
||||
* MonetaryAccountProfile.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Services\Bunq\Object;
|
||||
|
||||
/**
|
||||
* Class MonetaryAccountProfile
|
||||
*
|
||||
* @package FireflyIII\Services\Bunq\Object
|
||||
*/
|
||||
class MonetaryAccountProfile extends BunqObject
|
||||
{
|
||||
/** @var string */
|
||||
private $profileActionRequired = '';
|
||||
/** @var Amount */
|
||||
private $profileAmountRequired;
|
||||
private $profileDrain;
|
||||
private $profileFill;
|
||||
|
||||
/**
|
||||
* MonetaryAccountProfile constructor.
|
||||
*
|
||||
* @param array $data
|
||||
*/
|
||||
public function __construct(array $data)
|
||||
{
|
||||
$this->profileDrain = null;
|
||||
$this->profileFill = null;
|
||||
$this->profileActionRequired = $data['profile_action_required'];
|
||||
$this->profileAmountRequired = new Amount($data['profile_amount_required']);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
68
app/Services/Bunq/Object/MonetaryAccountSetting.php
Normal file
68
app/Services/Bunq/Object/MonetaryAccountSetting.php
Normal file
@ -0,0 +1,68 @@
|
||||
<?php
|
||||
/**
|
||||
* MonetaryAccountSetting.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Services\Bunq\Object;
|
||||
|
||||
/**
|
||||
* Class MonetaryAccountSetting
|
||||
*
|
||||
* @package FireflyIII\Services\Bunq\Object
|
||||
*/
|
||||
class MonetaryAccountSetting extends BunqObject
|
||||
{
|
||||
/** @var string */
|
||||
private $color = '';
|
||||
/** @var string */
|
||||
private $defaultAvatarStatus = '';
|
||||
/** @var string */
|
||||
private $restrictionChat = '';
|
||||
|
||||
/**
|
||||
* MonetaryAccountSetting constructor.
|
||||
*
|
||||
* @param array $data
|
||||
*/
|
||||
public function __construct(array $data)
|
||||
{
|
||||
$this->color = $data['color'];
|
||||
$this->defaultAvatarStatus = $data['default_avatar_status'];
|
||||
$this->restrictionChat = $data['restriction_chat'];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getColor(): string
|
||||
{
|
||||
return $this->color;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getDefaultAvatarStatus(): string
|
||||
{
|
||||
return $this->defaultAvatarStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getRestrictionChat(): string
|
||||
{
|
||||
return $this->restrictionChat;
|
||||
}
|
||||
|
||||
|
||||
}
|
31
app/Services/Bunq/Object/NotificationFilter.php
Normal file
31
app/Services/Bunq/Object/NotificationFilter.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
/**
|
||||
* NotificationFilter.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Services\Bunq\Object;
|
||||
|
||||
/**
|
||||
* Class NotificationFilter
|
||||
*
|
||||
* @package FireflyIII\Services\Bunq\Object
|
||||
*/
|
||||
class NotificationFilter extends BunqObject
|
||||
{
|
||||
/**
|
||||
* NotificationFilter constructor.
|
||||
*
|
||||
* @param array $data
|
||||
*/
|
||||
public function __construct(array $data)
|
||||
{
|
||||
}
|
||||
|
||||
}
|
52
app/Services/Bunq/Object/ServerPublicKey.php
Normal file
52
app/Services/Bunq/Object/ServerPublicKey.php
Normal file
@ -0,0 +1,52 @@
|
||||
<?php
|
||||
/**
|
||||
* ServerPublicKey.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Services\Bunq\Object;
|
||||
|
||||
/**
|
||||
* Class ServerPublicKey
|
||||
*
|
||||
* @package Bunq\Object
|
||||
*/
|
||||
class ServerPublicKey extends BunqObject
|
||||
{
|
||||
/** @var string */
|
||||
private $publicKey = '';
|
||||
|
||||
/**
|
||||
* ServerPublicKey constructor.
|
||||
*
|
||||
* @param array $response
|
||||
*/
|
||||
public function __construct(array $response)
|
||||
{
|
||||
$this->publicKey = $response['server_public_key'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getPublicKey(): string
|
||||
{
|
||||
return $this->publicKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $publicKey
|
||||
*/
|
||||
public function setPublicKey(string $publicKey)
|
||||
{
|
||||
$this->publicKey = $publicKey;
|
||||
}
|
||||
|
||||
|
||||
}
|
106
app/Services/Bunq/Object/UserCompany.php
Normal file
106
app/Services/Bunq/Object/UserCompany.php
Normal file
@ -0,0 +1,106 @@
|
||||
<?php
|
||||
/**
|
||||
* UserCompany.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Services\Bunq\Object;
|
||||
|
||||
use Carbon\Carbon;
|
||||
|
||||
/**
|
||||
* Class UserCompany
|
||||
*
|
||||
* @package FireflyIII\Services\Bunq\Object
|
||||
*/
|
||||
class UserCompany extends BunqObject
|
||||
{
|
||||
private $addressMain;
|
||||
private $addressPostal;
|
||||
/** @var array */
|
||||
private $aliases = [];
|
||||
private $avatar;
|
||||
/** @var string */
|
||||
private $cocNumber = '';
|
||||
/** @var string */
|
||||
private $counterBankIban = '';
|
||||
/** @var Carbon */
|
||||
private $created;
|
||||
private $dailyLimit;
|
||||
private $directorAlias;
|
||||
/** @var string */
|
||||
private $displayName = '';
|
||||
/** @var int */
|
||||
private $id = 0;
|
||||
/** @var string */
|
||||
private $language = '';
|
||||
/** @var string */
|
||||
private $name = '';
|
||||
/** @var array */
|
||||
private $notificationFilters = [];
|
||||
/** @var string */
|
||||
private $publicNickName = '';
|
||||
/** @var string */
|
||||
private $publicUuid = '';
|
||||
/** @var string */
|
||||
private $region = '';
|
||||
/** @var string */
|
||||
private $sectorOfIndustry = '';
|
||||
/** @var int */
|
||||
private $sessionTimeout = 0;
|
||||
/** @var string */
|
||||
private $status = '';
|
||||
/** @var string */
|
||||
private $subStatus = '';
|
||||
/** @var string */
|
||||
private $typeOfBusinessEntity = '';
|
||||
/** @var array */
|
||||
private $ubos = [];
|
||||
/** @var Carbon */
|
||||
private $updated;
|
||||
/** @var int */
|
||||
private $versionTos = 0;
|
||||
|
||||
/**
|
||||
* UserCompany constructor.
|
||||
*
|
||||
* @param array $data
|
||||
*/
|
||||
public function __construct(array $data)
|
||||
{
|
||||
$this->id = intval($data['id']);
|
||||
$this->created = Carbon::createFromFormat('Y-m-d H:i:s.u', $data['created']);
|
||||
$this->updated = Carbon::createFromFormat('Y-m-d H:i:s.u', $data['updated']);
|
||||
$this->status = $data['status'];
|
||||
$this->subStatus = $data['sub_status'];
|
||||
$this->publicUuid = $data['public_uuid'];
|
||||
$this->displayName = $data['display_name'];
|
||||
$this->publicNickName = $data['public_nick_name'];
|
||||
$this->language = $data['language'];
|
||||
$this->region = $data['region'];
|
||||
$this->sessionTimeout = intval($data['session_timeout']);
|
||||
$this->versionTos = intval($data['version_terms_of_service']);
|
||||
$this->cocNumber = $data['chamber_of_commerce_number'];
|
||||
$this->typeOfBusinessEntity = $data['type_of_business_entity'] ?? '';
|
||||
$this->sectorOfIndustry = $data['sector_of_industry'] ?? '';
|
||||
$this->counterBankIban = $data['counter_bank_iban'];
|
||||
$this->name = $data['name'];
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getId(): int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
|
||||
}
|
70
app/Services/Bunq/Object/UserLight.php
Normal file
70
app/Services/Bunq/Object/UserLight.php
Normal file
@ -0,0 +1,70 @@
|
||||
<?php
|
||||
/**
|
||||
* UserLight.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Services\Bunq\Object;
|
||||
|
||||
use Carbon\Carbon;
|
||||
|
||||
/**
|
||||
* Class UserLight
|
||||
*
|
||||
* @package FireflyIII\Services\Bunq\Object
|
||||
*/
|
||||
class UserLight extends BunqObject
|
||||
{
|
||||
/** @var array */
|
||||
private $aliases = [];
|
||||
/** @var Carbon */
|
||||
private $created;
|
||||
/** @var string */
|
||||
private $displayName = '';
|
||||
/** @var string */
|
||||
private $firstName = '';
|
||||
/** @var int */
|
||||
private $id = 0;
|
||||
/** @var string */
|
||||
private $lastName = '';
|
||||
/** @var string */
|
||||
private $legalName = '';
|
||||
/** @var string */
|
||||
private $middleName = '';
|
||||
/** @var string */
|
||||
private $publicNickName = '';
|
||||
/** @var string */
|
||||
private $publicUuid = '';
|
||||
/** @var Carbon */
|
||||
private $updated;
|
||||
|
||||
/**
|
||||
* UserLight constructor.
|
||||
*
|
||||
* @param array $data
|
||||
*/
|
||||
public function __construct(array $data)
|
||||
{
|
||||
if (count($data) === 0) {
|
||||
return;
|
||||
}
|
||||
$this->id = intval($data['id']);
|
||||
$this->created = Carbon::createFromFormat('Y-m-d H:i:s.u', $data['created']);
|
||||
$this->updated = Carbon::createFromFormat('Y-m-d H:i:s.u', $data['updated']);
|
||||
$this->publicUuid = $data['public_uuid'];
|
||||
$this->displayName = $data['display_name'];
|
||||
$this->publicNickName = $data['public_nick_name'];
|
||||
$this->firstName = $data['first_name'];
|
||||
$this->middleName = $data['middle_name'];
|
||||
$this->lastName = $data['last_name'];
|
||||
$this->legalName = $data['legal_name'];
|
||||
// aliases
|
||||
}
|
||||
|
||||
}
|
141
app/Services/Bunq/Object/UserPerson.php
Normal file
141
app/Services/Bunq/Object/UserPerson.php
Normal file
@ -0,0 +1,141 @@
|
||||
<?php
|
||||
/**
|
||||
* UserPerson.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Services\Bunq\Object;
|
||||
|
||||
use Carbon\Carbon;
|
||||
|
||||
/**
|
||||
* Class UserPerson
|
||||
*
|
||||
* @package Bunq\Object
|
||||
*/
|
||||
class UserPerson extends BunqObject
|
||||
{
|
||||
private $addressMain;
|
||||
private $addressPostal;
|
||||
/** @var array */
|
||||
private $aliases = [];
|
||||
private $avatar;
|
||||
/** @var array */
|
||||
private $billingContracts = [];
|
||||
/** @var string */
|
||||
private $countryOfBirth = '';
|
||||
/** @var Carbon */
|
||||
private $created;
|
||||
private $customer;
|
||||
private $customerLimit;
|
||||
private $dailyLimit;
|
||||
/** @var Carbon */
|
||||
private $dateOfBirth;
|
||||
/** @var string */
|
||||
private $displayName = '';
|
||||
/** @var string */
|
||||
private $documentCountry = '';
|
||||
/** @var string */
|
||||
private $documentNumber = '';
|
||||
/** @var string */
|
||||
private $documentType = '';
|
||||
/** @var string */
|
||||
private $firstName = '';
|
||||
/** @var string */
|
||||
private $gender = '';
|
||||
/** @var int */
|
||||
private $id = 0;
|
||||
/** @var string */
|
||||
private $language = '';
|
||||
/** @var string */
|
||||
private $lastName = '';
|
||||
/** @var string */
|
||||
private $legalName = '';
|
||||
/** @var string */
|
||||
private $middleName = '';
|
||||
/** @var string */
|
||||
private $nationality = '';
|
||||
/** @var array */
|
||||
private $notificationFilters = [];
|
||||
/** @var string */
|
||||
private $placeOfBirth = '';
|
||||
/** @var string */
|
||||
private $publicNickName = '';
|
||||
/** @var string */
|
||||
private $publicUuid = '';
|
||||
private $region;
|
||||
/** @var int */
|
||||
private $sessionTimeout = 0;
|
||||
/** @var string */
|
||||
private $status = '';
|
||||
/** @var string */
|
||||
private $subStatus = '';
|
||||
/** @var string */
|
||||
private $taxResident = '';
|
||||
/** @var Carbon */
|
||||
private $updated;
|
||||
/** @var int */
|
||||
private $versionTos = 0;
|
||||
|
||||
/**
|
||||
* UserPerson constructor.
|
||||
*
|
||||
* @param array $data
|
||||
*/
|
||||
public function __construct(array $data)
|
||||
{
|
||||
if (count($data) === 0) {
|
||||
return;
|
||||
}
|
||||
$this->id = intval($data['id']);
|
||||
$this->created = Carbon::createFromFormat('Y-m-d H:i:s.u', $data['created']);
|
||||
$this->updated = Carbon::createFromFormat('Y-m-d H:i:s.u', $data['updated']);
|
||||
$this->status = $data['status'];
|
||||
$this->subStatus = $data['sub_status'];
|
||||
$this->publicUuid = $data['public_uuid'];
|
||||
$this->displayName = $data['display_name'];
|
||||
$this->publicNickName = $data['public_nick_name'];
|
||||
$this->language = $data['language'];
|
||||
$this->region = $data['region'];
|
||||
$this->sessionTimeout = intval($data['session_timeout']);
|
||||
$this->firstName = $data['first_name'];
|
||||
$this->middleName = $data['middle_name'];
|
||||
$this->lastName = $data['last_name'];
|
||||
$this->legalName = $data['legal_name'];
|
||||
$this->taxResident = $data['tax_resident'];
|
||||
$this->dateOfBirth = Carbon::createFromFormat('Y-m-d', $data['date_of_birth']);
|
||||
$this->placeOfBirth = $data['place_of_birth'];
|
||||
$this->countryOfBirth = $data['country_of_birth'];
|
||||
$this->nationality = $data['nationality'];
|
||||
$this->gender = $data['gender'];
|
||||
$this->versionTos = intval($data['version_terms_of_service']);
|
||||
$this->documentNumber = $data['document_number'];
|
||||
$this->documentType = $data['document_type'];
|
||||
$this->documentCountry = $data['document_country_of_issuance'];
|
||||
|
||||
// create aliases
|
||||
// create avatar
|
||||
// create daily limit
|
||||
// create notification filters
|
||||
// create address main, postal
|
||||
// document front, back attachment
|
||||
// customer, customer_limit
|
||||
// billing contracts
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getId(): int
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
|
||||
}
|
451
app/Services/Bunq/Request/BunqRequest.php
Normal file
451
app/Services/Bunq/Request/BunqRequest.php
Normal file
@ -0,0 +1,451 @@
|
||||
<?php
|
||||
/**
|
||||
* BunqRequest.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Services\Bunq\Request;
|
||||
|
||||
use Exception;
|
||||
use FireflyIII\Exceptions\FireflyException;
|
||||
use FireflyIII\Services\Bunq\Object\ServerPublicKey;
|
||||
use Log;
|
||||
use Requests;
|
||||
use Requests_Exception;
|
||||
|
||||
/**
|
||||
* Class BunqRequest
|
||||
*
|
||||
* @package Bunq\Request
|
||||
*/
|
||||
abstract class BunqRequest
|
||||
{
|
||||
/** @var string */
|
||||
protected $secret = '';
|
||||
/** @var string */
|
||||
private $privateKey = '';
|
||||
/** @var string */
|
||||
private $server = '';
|
||||
/** @var ServerPublicKey */
|
||||
private $serverPublicKey;
|
||||
private $upperCaseHeaders
|
||||
= [
|
||||
'x-bunq-client-response-id' => 'X-Bunq-Client-Response-Id',
|
||||
'x-bunq-client-request-id' => 'X-Bunq-Client-Request-Id',
|
||||
];
|
||||
|
||||
/**
|
||||
* BunqRequest constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->server = config('firefly.bunq.server');
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
abstract public function call(): void;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getServer(): string
|
||||
{
|
||||
return $this->server;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $privateKey
|
||||
*/
|
||||
public function setPrivateKey(string $privateKey)
|
||||
{
|
||||
$this->privateKey = $privateKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $secret
|
||||
*/
|
||||
public function setSecret(string $secret)
|
||||
{
|
||||
$this->secret = $secret;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ServerPublicKey $serverPublicKey
|
||||
*/
|
||||
public function setServerPublicKey(ServerPublicKey $serverPublicKey)
|
||||
{
|
||||
$this->serverPublicKey = $serverPublicKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $method
|
||||
* @param string $uri
|
||||
* @param array $headers
|
||||
* @param string $data
|
||||
*
|
||||
* @return string
|
||||
* @throws FireflyException
|
||||
*/
|
||||
protected function generateSignature(string $method, string $uri, array $headers, string $data): string
|
||||
{
|
||||
if (strlen($this->privateKey) === 0) {
|
||||
throw new FireflyException('No private key present.');
|
||||
}
|
||||
if (strtolower($method) === 'get' || strtolower($method) === 'delete') {
|
||||
$data = '';
|
||||
}
|
||||
|
||||
$uri = str_replace(['https://api.bunq.com', 'https://sandbox.public.api.bunq.com'], '', $uri);
|
||||
$toSign = sprintf("%s %s\n", strtoupper($method), $uri);
|
||||
$headersToSign = ['Cache-Control', 'User-Agent'];
|
||||
ksort($headers);
|
||||
foreach ($headers as $name => $value) {
|
||||
if (in_array($name, $headersToSign) || substr($name, 0, 7) === 'X-Bunq-') {
|
||||
$toSign .= sprintf("%s: %s\n", $name, $value);
|
||||
}
|
||||
}
|
||||
$toSign .= "\n" . $data;
|
||||
$signature = '';
|
||||
|
||||
openssl_sign($toSign, $signature, $this->privateKey, OPENSSL_ALGO_SHA256);
|
||||
$signature = base64_encode($signature);
|
||||
|
||||
return $signature;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @param array $response
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getArrayFromResponse(string $key, array $response): array
|
||||
{
|
||||
$result = [];
|
||||
if (isset($response['Response'])) {
|
||||
foreach ($response['Response'] as $entry) {
|
||||
$currentKey = key($entry);
|
||||
$data = current($entry);
|
||||
if ($currentKey === $key) {
|
||||
$result[] = $data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
protected function getDefaultHeaders(): array
|
||||
{
|
||||
$userAgent = sprintf('FireflyIII v%s', config('firefly.version'));
|
||||
|
||||
return [
|
||||
'X-Bunq-Client-Request-Id' => uniqid('FFIII'),
|
||||
'Cache-Control' => 'no-cache',
|
||||
'User-Agent' => $userAgent,
|
||||
'X-Bunq-Language' => 'en_US',
|
||||
'X-Bunq-Region' => 'nl_NL',
|
||||
'X-Bunq-Geolocation' => '0 0 0 0 NL',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @param array $response
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function getKeyFromResponse(string $key, array $response): array
|
||||
{
|
||||
if (isset($response['Response'])) {
|
||||
foreach ($response['Response'] as $entry) {
|
||||
$currentKey = key($entry);
|
||||
$data = current($entry);
|
||||
if ($currentKey === $key) {
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $uri
|
||||
* @param array $headers
|
||||
*
|
||||
* @return array
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function sendSignedBunqDelete(string $uri, array $headers): array
|
||||
{
|
||||
if (strlen($this->server) === 0) {
|
||||
throw new FireflyException('No bunq server defined');
|
||||
}
|
||||
|
||||
$fullUri = $this->server . $uri;
|
||||
$signature = $this->generateSignature('delete', $uri, $headers, '');
|
||||
$headers['X-Bunq-Client-Signature'] = $signature;
|
||||
try {
|
||||
$response = Requests::delete($fullUri, $headers);
|
||||
} catch (Requests_Exception $e) {
|
||||
return ['Error' => [0 => ['error_description' => $e->getMessage(), 'error_description_translated' => $e->getMessage()],]];
|
||||
}
|
||||
|
||||
$body = $response->body;
|
||||
$array = json_decode($body, true);
|
||||
$responseHeaders = $response->headers->getAll();
|
||||
$statusCode = $response->status_code;
|
||||
$array['ResponseHeaders'] = $responseHeaders;
|
||||
$array['ResponseStatusCode'] = $statusCode;
|
||||
|
||||
Log::debug(sprintf('Response to DELETE %s is %s', $fullUri, $body));
|
||||
if ($this->isErrorResponse($array)) {
|
||||
$this->throwResponseError($array);
|
||||
}
|
||||
|
||||
if (!$this->verifyServerSignature($body, $responseHeaders, $statusCode)) {
|
||||
throw new FireflyException(sprintf('Could not verify signature for request to "%s"', $uri));
|
||||
}
|
||||
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $uri
|
||||
* @param array $data
|
||||
* @param array $headers
|
||||
*
|
||||
* @return array
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function sendSignedBunqGet(string $uri, array $data, array $headers): array
|
||||
{
|
||||
if (strlen($this->server) === 0) {
|
||||
throw new FireflyException('No bunq server defined');
|
||||
}
|
||||
|
||||
$body = json_encode($data);
|
||||
$fullUri = $this->server . $uri;
|
||||
$signature = $this->generateSignature('get', $uri, $headers, $body);
|
||||
$headers['X-Bunq-Client-Signature'] = $signature;
|
||||
try {
|
||||
$response = Requests::get($fullUri, $headers);
|
||||
} catch (Requests_Exception $e) {
|
||||
return ['Error' => [0 => ['error_description' => $e->getMessage(), 'error_description_translated' => $e->getMessage()],]];
|
||||
}
|
||||
|
||||
$body = $response->body;
|
||||
$array = json_decode($body, true);
|
||||
$responseHeaders = $response->headers->getAll();
|
||||
$statusCode = $response->status_code;
|
||||
$array['ResponseHeaders'] = $responseHeaders;
|
||||
$array['ResponseStatusCode'] = $statusCode;
|
||||
|
||||
if ($this->isErrorResponse($array)) {
|
||||
$this->throwResponseError($array);
|
||||
}
|
||||
|
||||
if (!$this->verifyServerSignature($body, $responseHeaders, $statusCode)) {
|
||||
throw new FireflyException(sprintf('Could not verify signature for request to "%s"', $uri));
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $uri
|
||||
* @param array $data
|
||||
* @param array $headers
|
||||
*
|
||||
* @return array
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function sendSignedBunqPost(string $uri, array $data, array $headers): array
|
||||
{
|
||||
$body = json_encode($data);
|
||||
$fullUri = $this->server . $uri;
|
||||
$signature = $this->generateSignature('post', $uri, $headers, $body);
|
||||
$headers['X-Bunq-Client-Signature'] = $signature;
|
||||
try {
|
||||
$response = Requests::post($fullUri, $headers, $body);
|
||||
} catch (Requests_Exception $e) {
|
||||
return ['Error' => [0 => ['error_description' => $e->getMessage(), 'error_description_translated' => $e->getMessage()],]];
|
||||
}
|
||||
|
||||
$body = $response->body;
|
||||
$array = json_decode($body, true);
|
||||
$responseHeaders = $response->headers->getAll();
|
||||
$statusCode = $response->status_code;
|
||||
$array['ResponseHeaders'] = $responseHeaders;
|
||||
$array['ResponseStatusCode'] = $statusCode;
|
||||
|
||||
if ($this->isErrorResponse($array)) {
|
||||
$this->throwResponseError($array);
|
||||
}
|
||||
|
||||
if (!$this->verifyServerSignature($body, $responseHeaders, $statusCode)) {
|
||||
throw new FireflyException(sprintf('Could not verify signature for request to "%s"', $uri));
|
||||
}
|
||||
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $uri
|
||||
* @param array $headers
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function sendUnsignedBunqDelete(string $uri, array $headers): array
|
||||
{
|
||||
$fullUri = $this->server . $uri;
|
||||
try {
|
||||
$response = Requests::delete($fullUri, $headers);
|
||||
} catch (Requests_Exception $e) {
|
||||
return ['Error' => [0 => ['error_description' => $e->getMessage(), 'error_description_translated' => $e->getMessage()],]];
|
||||
}
|
||||
$body = $response->body;
|
||||
$array = json_decode($body, true);
|
||||
$responseHeaders = $response->headers->getAll();
|
||||
$statusCode = $response->status_code;
|
||||
$array['ResponseHeaders'] = $responseHeaders;
|
||||
$array['ResponseStatusCode'] = $statusCode;
|
||||
|
||||
if ($this->isErrorResponse($array)) {
|
||||
$this->throwResponseError($array);
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $uri
|
||||
* @param array $data
|
||||
* @param array $headers
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function sendUnsignedBunqPost(string $uri, array $data, array $headers): array
|
||||
{
|
||||
$body = json_encode($data);
|
||||
$fullUri = $this->server . $uri;
|
||||
try {
|
||||
$response = Requests::post($fullUri, $headers, $body);
|
||||
} catch (Requests_Exception $e) {
|
||||
return ['Error' => [0 => ['error_description' => $e->getMessage(), 'error_description_translated' => $e->getMessage()],]];
|
||||
}
|
||||
$body = $response->body;
|
||||
$array = json_decode($body, true);
|
||||
$responseHeaders = $response->headers->getAll();
|
||||
$statusCode = $response->status_code;
|
||||
$array['ResponseHeaders'] = $responseHeaders;
|
||||
$array['ResponseStatusCode'] = $statusCode;
|
||||
|
||||
if ($this->isErrorResponse($array)) {
|
||||
$this->throwResponseError($array);
|
||||
}
|
||||
|
||||
|
||||
return $array;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $response
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function isErrorResponse(array $response): bool
|
||||
{
|
||||
$key = key($response);
|
||||
if ($key === 'Error') {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $response
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
private function throwResponseError(array $response)
|
||||
{
|
||||
$message = [];
|
||||
if (isset($response['Error'])) {
|
||||
foreach ($response['Error'] as $error) {
|
||||
$message[] = $error['error_description'];
|
||||
}
|
||||
}
|
||||
throw new FireflyException('Bunq ERROR ' . $response['ResponseStatusCode'] . ': ' . join(', ', $message));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $body
|
||||
* @param array $headers
|
||||
* @param int $statusCode
|
||||
*
|
||||
* @return bool
|
||||
* @throws Exception
|
||||
*/
|
||||
private function verifyServerSignature(string $body, array $headers, int $statusCode): bool
|
||||
{
|
||||
Log::debug('Going to verify signature for body+headers+status');
|
||||
$dataToVerify = $statusCode . "\n";
|
||||
$verifyHeaders = [];
|
||||
|
||||
// false when no public key is present
|
||||
if (is_null($this->serverPublicKey)) {
|
||||
Log::error('No public key present in class, so return FALSE.');
|
||||
|
||||
return false;
|
||||
}
|
||||
foreach ($headers as $header => $value) {
|
||||
|
||||
// skip non-bunq headers or signature
|
||||
if (substr($header, 0, 7) !== 'x-bunq-' || $header === 'x-bunq-server-signature') {
|
||||
continue;
|
||||
}
|
||||
// need to have upper case variant of header:
|
||||
if (!isset($this->upperCaseHeaders[$header])) {
|
||||
throw new FireflyException(sprintf('No upper case variant for header "%s"', $header));
|
||||
}
|
||||
$header = $this->upperCaseHeaders[$header];
|
||||
$verifyHeaders[$header] = $value[0];
|
||||
}
|
||||
// sort verification headers:
|
||||
ksort($verifyHeaders);
|
||||
|
||||
// add them to data to sign:
|
||||
foreach ($verifyHeaders as $header => $value) {
|
||||
$dataToVerify .= $header . ': ' . trim($value) . "\n";
|
||||
}
|
||||
|
||||
$signature = $headers['x-bunq-server-signature'][0];
|
||||
$dataToVerify .= "\n" . $body;
|
||||
$result = openssl_verify($dataToVerify, base64_decode($signature), $this->serverPublicKey->getPublicKey(), OPENSSL_ALGO_SHA256);
|
||||
|
||||
if (is_int($result) && $result < 1) {
|
||||
Log::error(sprintf('Result of verification is %d, return false.', $result));
|
||||
|
||||
return false;
|
||||
}
|
||||
if (!is_int($result)) {
|
||||
Log::error(sprintf('Result of verification is a boolean (%d), return false.', $result));
|
||||
}
|
||||
Log::info('Signature is a match, return true.');
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
49
app/Services/Bunq/Request/DeleteDeviceSessionRequest.php
Normal file
49
app/Services/Bunq/Request/DeleteDeviceSessionRequest.php
Normal file
@ -0,0 +1,49 @@
|
||||
<?php
|
||||
/**
|
||||
* DeleteDeviceSessionRequest.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Services\Bunq\Request;
|
||||
|
||||
use FireflyIII\Services\Bunq\Token\SessionToken;
|
||||
use Log;
|
||||
|
||||
/**
|
||||
* Class DeleteDeviceSessionRequest
|
||||
*
|
||||
* @package FireflyIII\Services\Bunq\Request
|
||||
*/
|
||||
class DeleteDeviceSessionRequest extends BunqRequest
|
||||
{
|
||||
/** @var SessionToken */
|
||||
private $sessionToken;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function call(): void
|
||||
{
|
||||
Log::debug('Going to send bunq delete session request.');
|
||||
$uri = sprintf('/v1/session/%d', $this->sessionToken->getId());
|
||||
$headers = $this->getDefaultHeaders();
|
||||
$headers['X-Bunq-Client-Authentication'] = $this->sessionToken->getToken();
|
||||
$this->sendSignedBunqDelete($uri, $headers);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param SessionToken $sessionToken
|
||||
*/
|
||||
public function setSessionToken(SessionToken $sessionToken)
|
||||
{
|
||||
$this->sessionToken = $sessionToken;
|
||||
}
|
||||
}
|
82
app/Services/Bunq/Request/DeviceServerRequest.php
Normal file
82
app/Services/Bunq/Request/DeviceServerRequest.php
Normal file
@ -0,0 +1,82 @@
|
||||
<?php
|
||||
/**
|
||||
* DeviceServerRequest.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Services\Bunq\Request;
|
||||
|
||||
use FireflyIII\Services\Bunq\Id\DeviceServerId;
|
||||
use FireflyIII\Services\Bunq\Token\InstallationToken;
|
||||
|
||||
/**
|
||||
* Class DeviceServerRequest
|
||||
*
|
||||
* @package Bunq\Request
|
||||
*/
|
||||
class DeviceServerRequest extends BunqRequest
|
||||
{
|
||||
/** @var string */
|
||||
private $description = '';
|
||||
/** @var DeviceServerId */
|
||||
private $deviceServerId;
|
||||
/** @var InstallationToken */
|
||||
private $installationToken;
|
||||
/** @var array */
|
||||
private $permittedIps = [];
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function call(): void
|
||||
{
|
||||
$uri = '/v1/device-server';
|
||||
$data = ['description' => $this->description, 'secret' => $this->secret, 'permitted_ips' => $this->permittedIps];
|
||||
$headers = $this->getDefaultHeaders();
|
||||
$headers['X-Bunq-Client-Authentication'] = $this->installationToken->getToken();
|
||||
$response = $this->sendSignedBunqPost($uri, $data, $headers);
|
||||
$deviceServerId = new DeviceServerId;
|
||||
$deviceServerId->setId(intval($response['Response'][0]['Id']['id']));
|
||||
$this->deviceServerId = $deviceServerId;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return DeviceServerId
|
||||
*/
|
||||
public function getDeviceServerId(): DeviceServerId
|
||||
{
|
||||
return $this->deviceServerId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $description
|
||||
*/
|
||||
public function setDescription(string $description)
|
||||
{
|
||||
$this->description = $description;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InstallationToken $installationToken
|
||||
*/
|
||||
public function setInstallationToken(InstallationToken $installationToken)
|
||||
{
|
||||
$this->installationToken = $installationToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $permittedIps
|
||||
*/
|
||||
public function setPermittedIps(array $permittedIps)
|
||||
{
|
||||
$this->permittedIps = $permittedIps;
|
||||
}
|
||||
}
|
149
app/Services/Bunq/Request/DeviceSessionRequest.php
Normal file
149
app/Services/Bunq/Request/DeviceSessionRequest.php
Normal file
@ -0,0 +1,149 @@
|
||||
<?php
|
||||
/**
|
||||
* DeviceSessionRequest.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Services\Bunq\Request;
|
||||
|
||||
|
||||
use FireflyIII\Services\Bunq\Id\DeviceSessionId;
|
||||
use FireflyIII\Services\Bunq\Object\UserCompany;
|
||||
use FireflyIII\Services\Bunq\Object\UserPerson;
|
||||
use FireflyIII\Services\Bunq\Token\InstallationToken;
|
||||
use FireflyIII\Services\Bunq\Token\SessionToken;
|
||||
use Log;
|
||||
|
||||
/**
|
||||
* Class DeviceSessionRequest
|
||||
*
|
||||
* @package FireflyIII\Services\Bunq\Request
|
||||
*/
|
||||
class DeviceSessionRequest extends BunqRequest
|
||||
{
|
||||
/** @var DeviceSessionId */
|
||||
private $deviceSessionId;
|
||||
/** @var InstallationToken */
|
||||
private $installationToken;
|
||||
/** @var SessionToken */
|
||||
private $sessionToken;
|
||||
/** @var UserCompany */
|
||||
private $userCompany;
|
||||
/** @var UserPerson */
|
||||
private $userPerson;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function call(): void
|
||||
{
|
||||
$uri = '/v1/session-server';
|
||||
$data = ['secret' => $this->secret];
|
||||
$headers = $this->getDefaultHeaders();
|
||||
$headers['X-Bunq-Client-Authentication'] = $this->installationToken->getToken();
|
||||
$response = $this->sendSignedBunqPost($uri, $data, $headers);
|
||||
|
||||
|
||||
$this->deviceSessionId = $this->extractDeviceSessionId($response);
|
||||
$this->sessionToken = $this->extractSessionToken($response);
|
||||
$this->userPerson = $this->extractUserPerson($response);
|
||||
$this->userCompany = $this->extractUserCompany($response);
|
||||
|
||||
Log::debug(sprintf('Session ID: %s', serialize($this->deviceSessionId)));
|
||||
Log::debug(sprintf('Session token: %s', serialize($this->sessionToken)));
|
||||
Log::debug(sprintf('Session user person: %s', serialize($this->userPerson)));
|
||||
Log::debug(sprintf('Session user company: %s', serialize($this->userCompany)));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return DeviceSessionId
|
||||
*/
|
||||
public function getDeviceSessionId(): DeviceSessionId
|
||||
{
|
||||
return $this->deviceSessionId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return SessionToken
|
||||
*/
|
||||
public function getSessionToken(): SessionToken
|
||||
{
|
||||
return $this->sessionToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return UserPerson
|
||||
*/
|
||||
public function getUserPerson(): UserPerson
|
||||
{
|
||||
return $this->userPerson;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InstallationToken $installationToken
|
||||
*/
|
||||
public function setInstallationToken(InstallationToken $installationToken)
|
||||
{
|
||||
$this->installationToken = $installationToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $response
|
||||
*
|
||||
* @return DeviceSessionId
|
||||
*/
|
||||
private function extractDeviceSessionId(array $response): DeviceSessionId
|
||||
{
|
||||
$data = $this->getKeyFromResponse('Id', $response);
|
||||
$deviceSessionId = new DeviceSessionId;
|
||||
$deviceSessionId->setId(intval($data['id']));
|
||||
|
||||
return $deviceSessionId;
|
||||
}
|
||||
|
||||
private function extractSessionToken(array $response): SessionToken
|
||||
{
|
||||
$data = $this->getKeyFromResponse('Token', $response);
|
||||
$sessionToken = new SessionToken($data);
|
||||
|
||||
return $sessionToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $response
|
||||
*
|
||||
* @return UserCompany
|
||||
*/
|
||||
private function extractUserCompany($response): UserCompany
|
||||
{
|
||||
$data = $this->getKeyFromResponse('UserCompany', $response);
|
||||
$userCompany = new UserCompany($data);
|
||||
|
||||
|
||||
return $userCompany;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $response
|
||||
*
|
||||
* @return UserPerson
|
||||
*/
|
||||
private function extractUserPerson($response): UserPerson
|
||||
{
|
||||
$data = $this->getKeyFromResponse('UserPerson', $response);
|
||||
$userPerson = new UserPerson($data);
|
||||
|
||||
|
||||
return $userPerson;
|
||||
}
|
||||
|
||||
|
||||
}
|
147
app/Services/Bunq/Request/InstallationTokenRequest.php
Normal file
147
app/Services/Bunq/Request/InstallationTokenRequest.php
Normal file
@ -0,0 +1,147 @@
|
||||
<?php
|
||||
/**
|
||||
* InstallationTokenRequest.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Services\Bunq\Request;
|
||||
|
||||
use FireflyIII\Services\Bunq\Id\InstallationId;
|
||||
use FireflyIII\Services\Bunq\Object\ServerPublicKey;
|
||||
use FireflyIII\Services\Bunq\Token\InstallationToken;
|
||||
use Log;
|
||||
|
||||
/**
|
||||
* Class InstallationTokenRequest
|
||||
*
|
||||
* @package FireflyIII\Services\Bunq\Request
|
||||
*/
|
||||
class InstallationTokenRequest extends BunqRequest
|
||||
{
|
||||
/** @var InstallationId */
|
||||
private $installationId;
|
||||
/** @var InstallationToken */
|
||||
private $installationToken;
|
||||
/** @var string */
|
||||
private $publicKey = '';
|
||||
/** @var ServerPublicKey */
|
||||
private $serverPublicKey;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function call(): void
|
||||
{
|
||||
$uri = '/v1/installation';
|
||||
$data = ['client_public_key' => $this->publicKey,];
|
||||
$headers = $this->getDefaultHeaders();
|
||||
$response = $this->sendUnsignedBunqPost($uri, $data, $headers);
|
||||
Log::debug('Installation request response', $response);
|
||||
|
||||
$this->installationId = $this->extractInstallationId($response);
|
||||
$this->serverPublicKey = $this->extractServerPublicKey($response);
|
||||
$this->installationToken = $this->extractInstallationToken($response);
|
||||
|
||||
Log::debug(sprintf('Installation ID: %s', serialize($this->installationId)));
|
||||
Log::debug(sprintf('Installation token: %s', serialize($this->installationToken)));
|
||||
Log::debug(sprintf('server public key: %s', serialize($this->serverPublicKey)));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return InstallationId
|
||||
*/
|
||||
public function getInstallationId(): InstallationId
|
||||
{
|
||||
return $this->installationId;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return InstallationToken
|
||||
*/
|
||||
public function getInstallationToken(): InstallationToken
|
||||
{
|
||||
return $this->installationToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getPublicKey(): string
|
||||
{
|
||||
return $this->publicKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $publicKey
|
||||
*/
|
||||
public function setPublicKey(string $publicKey)
|
||||
{
|
||||
$this->publicKey = $publicKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ServerPublicKey
|
||||
*/
|
||||
public function getServerPublicKey(): ServerPublicKey
|
||||
{
|
||||
return $this->serverPublicKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $fake
|
||||
*/
|
||||
public function setFake(bool $fake)
|
||||
{
|
||||
$this->fake = $fake;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $response
|
||||
*
|
||||
* @return InstallationId
|
||||
*/
|
||||
private function extractInstallationId(array $response): InstallationId
|
||||
{
|
||||
$installationId = new InstallationId;
|
||||
$data = $this->getKeyFromResponse('Id', $response);
|
||||
$installationId->setId(intval($data['id']));
|
||||
|
||||
return $installationId;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $response
|
||||
*
|
||||
* @return InstallationToken
|
||||
*/
|
||||
private function extractInstallationToken(array $response): InstallationToken
|
||||
{
|
||||
|
||||
$data = $this->getKeyFromResponse('Token', $response);
|
||||
$installationToken = new InstallationToken($data);
|
||||
|
||||
return $installationToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $response
|
||||
*
|
||||
* @return ServerPublicKey
|
||||
*/
|
||||
private function extractServerPublicKey(array $response): ServerPublicKey
|
||||
{
|
||||
$data = $this->getKeyFromResponse('ServerPublicKey', $response);
|
||||
$serverPublicKey = new ServerPublicKey($data);
|
||||
|
||||
return $serverPublicKey;
|
||||
}
|
||||
}
|
73
app/Services/Bunq/Request/ListDeviceServerRequest.php
Normal file
73
app/Services/Bunq/Request/ListDeviceServerRequest.php
Normal file
@ -0,0 +1,73 @@
|
||||
<?php
|
||||
/**
|
||||
* ListDeviceServerRequest.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Services\Bunq\Request;
|
||||
|
||||
use FireflyIII\Services\Bunq\Object\DeviceServer;
|
||||
use FireflyIII\Services\Bunq\Token\InstallationToken;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
* Class ListDeviceServerRequest
|
||||
*
|
||||
* @package FireflyIII\Services\Bunq\Request
|
||||
*/
|
||||
class ListDeviceServerRequest extends BunqRequest
|
||||
{
|
||||
/** @var Collection */
|
||||
private $devices;
|
||||
/** @var InstallationToken */
|
||||
private $installationToken;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
$this->devices = new Collection;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function call(): void
|
||||
{
|
||||
$uri = '/v1/device-server';
|
||||
$data = [];
|
||||
$headers = $this->getDefaultHeaders();
|
||||
$headers['X-Bunq-Client-Authentication'] = $this->installationToken->getToken();
|
||||
$response = $this->sendSignedBunqGet($uri, $data, $headers);
|
||||
|
||||
// create device server objects:
|
||||
$raw = $this->getArrayFromResponse('DeviceServer', $response);
|
||||
/** @var array $entry */
|
||||
foreach ($raw as $entry) {
|
||||
$this->devices->push(new DeviceServer($entry));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection
|
||||
*/
|
||||
public function getDevices(): Collection
|
||||
{
|
||||
return $this->devices;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param InstallationToken $installationToken
|
||||
*/
|
||||
public function setInstallationToken(InstallationToken $installationToken)
|
||||
{
|
||||
$this->installationToken = $installationToken;
|
||||
}
|
||||
}
|
79
app/Services/Bunq/Request/ListMonetaryAccountRequest.php
Normal file
79
app/Services/Bunq/Request/ListMonetaryAccountRequest.php
Normal file
@ -0,0 +1,79 @@
|
||||
<?php
|
||||
/**
|
||||
* ListMonetaryAccountRequest.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Services\Bunq\Request;
|
||||
|
||||
use FireflyIII\Services\Bunq\Object\MonetaryAccountBank;
|
||||
use FireflyIII\Services\Bunq\Token\SessionToken;
|
||||
use Illuminate\Support\Collection;
|
||||
|
||||
/**
|
||||
* Class ListMonetaryAccountRequest
|
||||
*
|
||||
* @package FireflyIII\Services\Bunq\Request
|
||||
*/
|
||||
class ListMonetaryAccountRequest extends BunqRequest
|
||||
{
|
||||
/** @var Collection */
|
||||
private $monetaryAccounts;
|
||||
/** @var SessionToken */
|
||||
private $sessionToken;
|
||||
/** @var int */
|
||||
private $userId = 0;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function call(): void
|
||||
{
|
||||
$this->monetaryAccounts = new Collection;
|
||||
$uri = sprintf('/v1/user/%d/monetary-account', $this->userId);
|
||||
$data = [];
|
||||
$headers = $this->getDefaultHeaders();
|
||||
$headers['X-Bunq-Client-Authentication'] = $this->sessionToken->getToken();
|
||||
$response = $this->sendSignedBunqGet($uri, $data, $headers);
|
||||
|
||||
// create device server objects:
|
||||
$raw = $this->getArrayFromResponse('MonetaryAccountBank', $response);
|
||||
foreach ($raw as $entry) {
|
||||
$account = new MonetaryAccountBank($entry);
|
||||
$this->monetaryAccounts->push($account);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Collection
|
||||
*/
|
||||
public function getMonetaryAccounts(): Collection
|
||||
{
|
||||
return $this->monetaryAccounts;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param SessionToken $sessionToken
|
||||
*/
|
||||
public function setSessionToken(SessionToken $sessionToken)
|
||||
{
|
||||
$this->sessionToken = $sessionToken;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $userId
|
||||
*/
|
||||
public function setUserId(int $userId)
|
||||
{
|
||||
$this->userId = $userId;
|
||||
}
|
||||
|
||||
}
|
90
app/Services/Bunq/Request/ListUserRequest.php
Normal file
90
app/Services/Bunq/Request/ListUserRequest.php
Normal file
@ -0,0 +1,90 @@
|
||||
<?php
|
||||
/**
|
||||
* ListUserRequest.php
|
||||
* Copyright (c) 2017 thegrumpydictator@gmail.com
|
||||
* This software may be modified and distributed under the terms of the
|
||||
* Creative Commons Attribution-ShareAlike 4.0 International License.
|
||||
*
|
||||
* See the LICENSE file for details.
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace FireflyIII\Services\Bunq\Request;
|
||||
|
||||
use FireflyIII\Services\Bunq\Object\UserCompany;
|
||||
use FireflyIII\Services\Bunq\Object\UserLight;
|
||||
use FireflyIII\Services\Bunq\Object\UserPerson;
|
||||
use FireflyIII\Services\Bunq\Token\SessionToken;
|
||||
|
||||
/**
|
||||
* Class ListUserRequest
|
||||
*
|
||||
* @package FireflyIII\Services\Bunq\Request
|
||||
*/
|
||||
class ListUserRequest extends BunqRequest
|
||||
{
|
||||
/** @var SessionToken */
|
||||
private $sessionToken;
|
||||
/** @var UserCompany */
|
||||
private $userCompany;
|
||||
/** @var UserLight */
|
||||
private $userLight;
|
||||
/** @var UserPerson */
|
||||
private $userPerson;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
public function call(): void
|
||||
{
|
||||
$uri = '/v1/user';
|
||||
$data = [];
|
||||
$headers = $this->getDefaultHeaders();
|
||||
$headers['X-Bunq-Client-Authentication'] = $this->sessionToken->getToken();
|
||||
$response = $this->sendSignedBunqGet($uri, $data, $headers);
|
||||
|
||||
// create user objects:
|
||||
$light = $this->getKeyFromResponse('UserLight', $response);
|
||||
$company = $this->getKeyFromResponse('UserCompany', $response);
|
||||
$person = $this->getKeyFromResponse('UserPerson', $response);
|
||||
$this->userLight = new UserLight($light);
|
||||
$this->userCompany = new UserCompany($company);
|
||||
$this->userPerson = new UserPerson($person);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return UserCompany
|
||||
*/
|
||||
public function getUserCompany(): UserCompany
|
||||
{
|
||||
return $this->userCompany;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return UserLight
|
||||
*/
|
||||
public function getUserLight(): UserLight
|
||||
{
|
||||
return $this->userLight;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return UserPerson
|
||||
*/
|
||||
public function getUserPerson(): UserPerson
|
||||
{
|
||||
return $this->userPerson;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @param SessionToken $sessionToken
|
||||
*/
|
||||
public function setSessionToken(SessionToken $sessionToken)
|
||||
{
|
||||
$this->sessionToken = $sessionToken;
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user