diff --git a/app/Handlers/Events/AuditEventHandler.php b/app/Handlers/Events/AuditEventHandler.php index b5a0471aff..a0edd222bd 100644 --- a/app/Handlers/Events/AuditEventHandler.php +++ b/app/Handlers/Events/AuditEventHandler.php @@ -24,16 +24,21 @@ declare(strict_types=1); namespace FireflyIII\Handlers\Events; +use Carbon\Carbon; use FireflyIII\Events\TriggeredAuditLog; use FireflyIII\Repositories\AuditLogEntry\ALERepositoryInterface; +/** + * Class AuditEventHandler + */ class AuditEventHandler { /** - * @param TriggeredAuditLog $event + * @param TriggeredAuditLog $event + * * @return void */ - public function storeAuditEvent(TriggeredAuditLog $event) + public function storeAuditEvent(TriggeredAuditLog $event): void { $array = [ 'auditable' => $event->auditable, @@ -42,6 +47,24 @@ class AuditEventHandler 'before' => $event->before, 'after' => $event->after, ]; + + if ($event->before === $event->after) { + app('log')->debug('Will not store event log because before and after are the same.'); + + return; + } + if ($event->before instanceof Carbon && $event->after instanceof Carbon && $event->before->eq($event->after)) { + app('log')->debug('Will not store event log because before and after Carbon values are the same.'); + + return; + } + if ($event->before instanceof Carbon && $event->after instanceof Carbon) { + $array['before'] = $event->before->toIso8601String(); + $array['after'] = $event->after->toIso8601String(); + app('log')->debug(sprintf('Converted "before" to "%s".', $event->before)); + app('log')->debug(sprintf('Converted "after" to "%s".', $event->after)); + } + /** @var ALERepositoryInterface $repository */ $repository = app(ALERepositoryInterface::class); $repository->store($array); diff --git a/app/Http/Controllers/Transaction/ShowController.php b/app/Http/Controllers/Transaction/ShowController.php index bb182c7d10..870d871347 100644 --- a/app/Http/Controllers/Transaction/ShowController.php +++ b/app/Http/Controllers/Transaction/ShowController.php @@ -66,7 +66,7 @@ class ShowController extends Controller } /** - * @param TransactionGroup $transactionGroup + * @param TransactionGroup $transactionGroup * * @return JsonResponse */ @@ -76,8 +76,8 @@ class ShowController extends Controller } /** - * @param Request $request - * @param TransactionGroup $transactionGroup + * @param Request $request + * @param TransactionGroup $transactionGroup * * @return Factory|View * @throws FireflyException @@ -106,11 +106,14 @@ class ShowController extends Controller $accounts = $this->getAccounts($groupArray); foreach ($groupArray['transactions'] as $index => $transaction) { - $groupArray['transactions'][$index]['tags'] = $this->repository->getTagObjects($groupArray['transactions'][$index]['transaction_journal_id']); + $groupArray['transactions'][$index]['tags'] = $this->repository->getTagObjects( + $groupArray['transactions'][$index]['transaction_journal_id'] + ); } // get audit log entries: - $logEntries = []; + $groupLogEntries = $this->aleRepository->getForObject($transactionGroup); + $logEntries = []; foreach ($transactionGroup->transactionJournals as $journal) { $logEntries[$journal->id] = $this->aleRepository->getForObject($journal); } @@ -128,6 +131,7 @@ class ShowController extends Controller 'first', 'type', 'logEntries', + 'groupLogEntries', 'subTitle', 'splits', 'groupArray', @@ -140,7 +144,49 @@ class ShowController extends Controller } /** - * @param array $group + * @param array $group + * + * @return array + */ + private function getAmounts(array $group): array + { + $amounts = []; + foreach ($group['transactions'] as $transaction) { + $symbol = $transaction['currency_symbol']; + if (!array_key_exists($symbol, $amounts)) { + $amounts[$symbol] = [ + 'amount' => '0', + 'symbol' => $symbol, + 'decimal_places' => $transaction['currency_decimal_places'], + ]; + } + $amounts[$symbol]['amount'] = bcadd($amounts[$symbol]['amount'], $transaction['amount']); + if (null !== $transaction['foreign_amount'] && '' !== $transaction['foreign_amount'] + && bccomp( + '0', + $transaction['foreign_amount'] + ) !== 0) { + // same for foreign currency: + $foreignSymbol = $transaction['foreign_currency_symbol']; + if (!array_key_exists($foreignSymbol, $amounts)) { + $amounts[$foreignSymbol] = [ + 'amount' => '0', + 'symbol' => $foreignSymbol, + 'decimal_places' => $transaction['foreign_currency_decimal_places'], + ]; + } + $amounts[$foreignSymbol]['amount'] = bcadd( + $amounts[$foreignSymbol]['amount'], + $transaction['foreign_amount'] + ); + } + } + + return $amounts; + } + + /** + * @param array $group * * @return array */ @@ -168,39 +214,4 @@ class ShowController extends Controller return $accounts; } - - /** - * @param array $group - * - * @return array - */ - private function getAmounts(array $group): array - { - $amounts = []; - foreach ($group['transactions'] as $transaction) { - $symbol = $transaction['currency_symbol']; - if (!array_key_exists($symbol, $amounts)) { - $amounts[$symbol] = [ - 'amount' => '0', - 'symbol' => $symbol, - 'decimal_places' => $transaction['currency_decimal_places'], - ]; - } - $amounts[$symbol]['amount'] = bcadd($amounts[$symbol]['amount'], $transaction['amount']); - if (null !== $transaction['foreign_amount'] && '' !== $transaction['foreign_amount'] && bccomp('0', $transaction['foreign_amount']) !== 0) { - // same for foreign currency: - $foreignSymbol = $transaction['foreign_currency_symbol']; - if (!array_key_exists($foreignSymbol, $amounts)) { - $amounts[$foreignSymbol] = [ - 'amount' => '0', - 'symbol' => $foreignSymbol, - 'decimal_places' => $transaction['foreign_currency_decimal_places'], - ]; - } - $amounts[$foreignSymbol]['amount'] = bcadd($amounts[$foreignSymbol]['amount'], $transaction['foreign_amount']); - } - } - - return $amounts; - } } diff --git a/app/Services/Internal/Update/GroupUpdateService.php b/app/Services/Internal/Update/GroupUpdateService.php index ea3c4d9657..acbcba9d09 100644 --- a/app/Services/Internal/Update/GroupUpdateService.php +++ b/app/Services/Internal/Update/GroupUpdateService.php @@ -23,6 +23,7 @@ declare(strict_types=1); namespace FireflyIII\Services\Internal\Update; +use FireflyIII\Events\TriggeredAuditLog; use FireflyIII\Exceptions\DuplicateTransactionException; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Factory\TransactionJournalFactory; @@ -40,8 +41,8 @@ class GroupUpdateService /** * Update a transaction group. * - * @param TransactionGroup $transactionGroup - * @param array $data + * @param TransactionGroup $transactionGroup + * @param array $data * * @return TransactionGroup * @throws DuplicateTransactionException @@ -56,8 +57,18 @@ class GroupUpdateService // update group name. if (array_key_exists('group_title', $data)) { Log::debug(sprintf('Update transaction group #%d title.', $transactionGroup->id)); + $oldTitle = $transactionGroup->title; $transactionGroup->title = $data['group_title']; $transactionGroup->save(); + event( + new TriggeredAuditLog( + $transactionGroup->user, + $transactionGroup, + 'update_group_title', + $oldTitle, + $data['group_title'] + ) + ); } @@ -70,7 +81,9 @@ class GroupUpdateService if (1 === count($transactions) && 1 === $transactionGroup->transactionJournals()->count()) { /** @var TransactionJournal $first */ $first = $transactionGroup->transactionJournals()->first(); - Log::debug(sprintf('Will now update journal #%d (only journal in group #%d)', $first->id, $transactionGroup->id)); + Log::debug( + sprintf('Will now update journal #%d (only journal in group #%d)', $first->id, $transactionGroup->id) + ); $this->updateTransactionJournal($transactionGroup, $first, reset($transactions)); $transactionGroup->refresh(); app('preferences')->mark(); @@ -88,6 +101,7 @@ class GroupUpdateService Log::error('There were no transactions updated or created. Will not delete anything.'); $transactionGroup->refresh(); app('preferences')->mark(); + return $transactionGroup; } @@ -111,8 +125,8 @@ class GroupUpdateService } /** - * @param TransactionGroup $transactionGroup - * @param array $data + * @param TransactionGroup $transactionGroup + * @param array $data * * @return TransactionJournal|null * @@ -135,7 +149,11 @@ class GroupUpdateService } catch (FireflyException $e) { Log::error($e->getMessage()); Log::error($e->getTraceAsString()); - throw new FireflyException(sprintf('Could not create new transaction journal: %s', $e->getMessage()), 0, $e); + throw new FireflyException( + sprintf('Could not create new transaction journal: %s', $e->getMessage()), + 0, + $e + ); } $collection->each( function (TransactionJournal $journal) use ($transactionGroup) { @@ -152,12 +170,15 @@ class GroupUpdateService /** * Update single journal. * - * @param TransactionGroup $transactionGroup - * @param TransactionJournal $journal - * @param array $data + * @param TransactionGroup $transactionGroup + * @param TransactionJournal $journal + * @param array $data */ - private function updateTransactionJournal(TransactionGroup $transactionGroup, TransactionJournal $journal, array $data): void - { + private function updateTransactionJournal( + TransactionGroup $transactionGroup, + TransactionJournal $journal, + array $data + ): void { Log::debug(sprintf('Now in %s', __METHOD__)); if (0 === count($data)) { return; @@ -174,8 +195,8 @@ class GroupUpdateService } /** - * @param TransactionGroup $transactionGroup - * @param array $transactions + * @param TransactionGroup $transactionGroup + * @param array $transactions * * @return array * @throws DuplicateTransactionException @@ -188,7 +209,7 @@ class GroupUpdateService // updated or created transaction journals: $updated = []; /** - * @var int $index + * @var int $index * @var array $transaction */ foreach ($transactions as $index => $transaction) { @@ -203,7 +224,9 @@ class GroupUpdateService if (!array_key_exists('type', $transaction)) { Log::debug('No transaction type is indicated.'); /** @var TransactionJournal|null $randomJournal */ - $randomJournal = $transactionGroup->transactionJournals()->inRandomOrder()->with(['transactionType'])->first(); + $randomJournal = $transactionGroup->transactionJournals()->inRandomOrder()->with( + ['transactionType'] + )->first(); if (null !== $randomJournal) { $transaction['type'] = $randomJournal->transactionType->type; Log::debug(sprintf('Transaction type set to %s.', $transaction['type'])); diff --git a/app/Services/Internal/Update/JournalUpdateService.php b/app/Services/Internal/Update/JournalUpdateService.php index 4203783627..90c37cf052 100644 --- a/app/Services/Internal/Update/JournalUpdateService.php +++ b/app/Services/Internal/Update/JournalUpdateService.php @@ -25,6 +25,7 @@ namespace FireflyIII\Services\Internal\Update; use Carbon\Carbon; use Carbon\Exceptions\InvalidDateException; +use FireflyIII\Events\TriggeredAuditLog; use FireflyIII\Exceptions\FireflyException; use FireflyIII\Factory\TagFactory; use FireflyIII\Factory\TransactionJournalMetaFactory; @@ -97,11 +98,12 @@ class JournalUpdateService 'external_id', 'external_url', ]; - $this->metaDate = ['interest_date', 'book_date', 'process_date', 'due_date', 'payment_date', 'invoice_date',]; + $this->metaDate = ['interest_date', 'book_date', 'process_date', 'due_date', 'payment_date', + 'invoice_date',]; } /** - * @param array $data + * @param array $data */ public function setData(array $data): void { @@ -109,7 +111,7 @@ class JournalUpdateService } /** - * @param TransactionGroup $transactionGroup + * @param TransactionGroup $transactionGroup */ public function setTransactionGroup(TransactionGroup $transactionGroup): void { @@ -126,7 +128,7 @@ class JournalUpdateService } /** - * @param TransactionJournal $transactionJournal + * @param TransactionJournal $transactionJournal */ public function setTransactionJournal(TransactionJournal $transactionJournal): void { @@ -181,76 +183,49 @@ class JournalUpdateService } /** - * @return bool - */ - private function removeReconciliation(): bool - { - if (count($this->data) > 1) { - return true; - } - if (1 === count($this->data) && true === array_key_exists('transaction_journal_id', $this->data)) { - return true; - } - - return false; - } - - /** - * @return bool - */ - private function hasValidAccounts(): bool - { - return $this->hasValidSourceAccount() && $this->hasValidDestinationAccount(); - } - - /** - * @return bool - */ - private function hasValidSourceAccount(): bool - { - Log::debug('Now in hasValidSourceAccount().'); - $sourceId = $this->data['source_id'] ?? null; - $sourceName = $this->data['source_name'] ?? null; - - if (!$this->hasFields(['source_id', 'source_name'])) { - $origSourceAccount = $this->getOriginalSourceAccount(); - $sourceId = $origSourceAccount->id; - $sourceName = $origSourceAccount->name; - } - - // make new account validator. - $expectedType = $this->getExpectedType(); - Log::debug(sprintf('Expected type (new or unchanged) is %s', $expectedType)); - - // make a new validator. - /** @var AccountValidator $validator */ - $validator = app(AccountValidator::class); - $validator->setTransactionType($expectedType); - $validator->setUser($this->transactionJournal->user); - - $result = $validator->validateSource(['id' => $sourceId]); - Log::debug(sprintf('hasValidSourceAccount(%d, "%s") will return %s', $sourceId, $sourceName, var_export($result, true))); - - // TODO typeoverrule the account validator may have a different opinion on the transaction type. - - // validate submitted info: - return $result; - } - - /** - * @param array $fields + * Get destination transaction. * - * @return bool + * @return Transaction */ - private function hasFields(array $fields): bool + private function getDestinationTransaction(): Transaction { - foreach ($fields as $field) { - if (array_key_exists($field, $this->data)) { - return true; - } + if (null === $this->destinationTransaction) { + $this->destinationTransaction = $this->transactionJournal->transactions()->where('amount', '>', 0)->first(); } - return false; + return $this->destinationTransaction; + } + + /** + * This method returns the current or expected type of the journal (in case of a change) based on the data in the + * array. + * + * If the array contains key 'type' and the value is correct, this is returned. Otherwise, the original type is + * returned. + * + * @return string + */ + private function getExpectedType(): string + { + Log::debug('Now in getExpectedType()'); + if ($this->hasFields(['type'])) { + return ucfirst('opening-balance' === $this->data['type'] ? 'opening balance' : $this->data['type']); + } + + return $this->transactionJournal->transactionType->type; + } + + /** + * @return Account + */ + private function getOriginalDestinationAccount(): Account + { + if (null === $this->destinationAccount) { + $destination = $this->getDestinationTransaction(); + $this->destinationAccount = $destination->account; + } + + return $this->destinationAccount; } /** @@ -272,7 +247,11 @@ class JournalUpdateService private function getSourceTransaction(): Transaction { if (null === $this->sourceTransaction) { - $this->sourceTransaction = $this->transactionJournal->transactions()->with(['account'])->where('amount', '<', 0)->first(); + $this->sourceTransaction = $this->transactionJournal->transactions()->with(['account'])->where( + 'amount', + '<', + 0 + )->first(); } Log::debug(sprintf('getSourceTransaction: %s', $this->sourceTransaction->amount)); @@ -280,84 +259,39 @@ class JournalUpdateService } /** - * This method returns the current or expected type of the journal (in case of a change) based on the data in the array. + * Does a validation and returns the destination account. This method will break if the dest isn't really valid. * - * If the array contains key 'type' and the value is correct, this is returned. Otherwise, the original type is returned. - * - * @return string + * @return Account */ - private function getExpectedType(): string + private function getValidDestinationAccount(): Account { - Log::debug('Now in getExpectedType()'); - if ($this->hasFields(['type'])) { - return ucfirst('opening-balance' === $this->data['type'] ? 'opening balance' : $this->data['type']); - } - - return $this->transactionJournal->transactionType->type; - } - - /** - * @return bool - */ - private function hasValidDestinationAccount(): bool - { - Log::debug('Now in hasValidDestinationAccount().'); - $destId = $this->data['destination_id'] ?? null; - $destName = $this->data['destination_name'] ?? null; + Log::debug('Now in getValidDestinationAccount().'); if (!$this->hasFields(['destination_id', 'destination_name'])) { - Log::debug('No destination info submitted, grab the original data.'); - $destination = $this->getOriginalDestinationAccount(); - $destId = $destination->id; - $destName = $destination->name; + return $this->getOriginalDestinationAccount(); } + $destInfo = [ + 'id' => (int)($this->data['destination_id'] ?? null), + 'name' => $this->data['destination_name'] ?? null, + 'iban' => $this->data['destination_iban'] ?? null, + 'number' => $this->data['destination_number'] ?? null, + 'bic' => $this->data['destination_bic'] ?? null, + ]; + // make new account validator. $expectedType = $this->getExpectedType(); Log::debug(sprintf('Expected type (new or unchanged) is %s', $expectedType)); + try { + $result = $this->getAccount($expectedType, 'destination', $destInfo); + } catch (FireflyException $e) { + Log::error(sprintf('getValidDestinationAccount() threw unexpected error: %s', $e->getMessage())); + $result = $this->getOriginalDestinationAccount(); + } - // make a new validator. - /** @var AccountValidator $validator */ - $validator = app(AccountValidator::class); - $validator->setTransactionType($expectedType); - $validator->setUser($this->transactionJournal->user); - $validator->source = $this->getValidSourceAccount(); - $result = $validator->validateDestination(['id' => $destId, 'name' => $destName]); - Log::debug(sprintf('hasValidDestinationAccount(%d, "%s") will return %s', $destId, $destName, var_export($result, true))); - - // TODO typeOverrule: the account validator may have another opinion on the transaction type. - - // validate submitted info: return $result; } - /** - * @return Account - */ - private function getOriginalDestinationAccount(): Account - { - if (null === $this->destinationAccount) { - $destination = $this->getDestinationTransaction(); - $this->destinationAccount = $destination->account; - } - - return $this->destinationAccount; - } - - /** - * Get destination transaction. - * - * @return Transaction - */ - private function getDestinationTransaction(): Transaction - { - if (null === $this->destinationTransaction) { - $this->destinationTransaction = $this->transactionJournal->transactions()->where('amount', '>', 0)->first(); - } - - return $this->destinationTransaction; - } - /** * Does a validation and returns the source account. This method will break if the source isn't really valid. * @@ -393,6 +327,123 @@ class JournalUpdateService return $result; } + /** + * @param array $fields + * + * @return bool + */ + private function hasFields(array $fields): bool + { + foreach ($fields as $field) { + if (array_key_exists($field, $this->data)) { + return true; + } + } + + return false; + } + + /** + * @return bool + */ + private function hasValidAccounts(): bool + { + return $this->hasValidSourceAccount() && $this->hasValidDestinationAccount(); + } + + /** + * @return bool + */ + private function hasValidDestinationAccount(): bool + { + Log::debug('Now in hasValidDestinationAccount().'); + $destId = $this->data['destination_id'] ?? null; + $destName = $this->data['destination_name'] ?? null; + + if (!$this->hasFields(['destination_id', 'destination_name'])) { + Log::debug('No destination info submitted, grab the original data.'); + $destination = $this->getOriginalDestinationAccount(); + $destId = $destination->id; + $destName = $destination->name; + } + + // make new account validator. + $expectedType = $this->getExpectedType(); + Log::debug(sprintf('Expected type (new or unchanged) is %s', $expectedType)); + + // make a new validator. + /** @var AccountValidator $validator */ + $validator = app(AccountValidator::class); + $validator->setTransactionType($expectedType); + $validator->setUser($this->transactionJournal->user); + $validator->source = $this->getValidSourceAccount(); + $result = $validator->validateDestination(['id' => $destId, 'name' => $destName]); + Log::debug( + sprintf( + 'hasValidDestinationAccount(%d, "%s") will return %s', + $destId, + $destName, + var_export($result, true) + ) + ); + + // TODO typeOverrule: the account validator may have another opinion on the transaction type. + + // validate submitted info: + return $result; + } + + /** + * @return bool + */ + private function hasValidSourceAccount(): bool + { + Log::debug('Now in hasValidSourceAccount().'); + $sourceId = $this->data['source_id'] ?? null; + $sourceName = $this->data['source_name'] ?? null; + + if (!$this->hasFields(['source_id', 'source_name'])) { + $origSourceAccount = $this->getOriginalSourceAccount(); + $sourceId = $origSourceAccount->id; + $sourceName = $origSourceAccount->name; + } + + // make new account validator. + $expectedType = $this->getExpectedType(); + Log::debug(sprintf('Expected type (new or unchanged) is %s', $expectedType)); + + // make a new validator. + /** @var AccountValidator $validator */ + $validator = app(AccountValidator::class); + $validator->setTransactionType($expectedType); + $validator->setUser($this->transactionJournal->user); + + $result = $validator->validateSource(['id' => $sourceId]); + Log::debug( + sprintf('hasValidSourceAccount(%d, "%s") will return %s', $sourceId, $sourceName, var_export($result, true)) + ); + + // TODO typeoverrule the account validator may have a different opinion on the transaction type. + + // validate submitted info: + return $result; + } + + /** + * @return bool + */ + private function removeReconciliation(): bool + { + if (count($this->data) > 1) { + return true; + } + if (1 === count($this->data) && true === array_key_exists('transaction_journal_id', $this->data)) { + return true; + } + + return false; + } + /** * Will update the source and destination accounts of this journal. Assumes they are valid. */ @@ -424,70 +475,34 @@ class JournalUpdateService } /** - * Does a validation and returns the destination account. This method will break if the dest isn't really valid. * - * @return Account */ - private function getValidDestinationAccount(): Account + private function updateAmount(): void { - Log::debug('Now in getValidDestinationAccount().'); - - if (!$this->hasFields(['destination_id', 'destination_name'])) { - return $this->getOriginalDestinationAccount(); + Log::debug(sprintf('Now in %s', __METHOD__)); + if (!$this->hasFields(['amount'])) { + return; } - $destInfo = [ - 'id' => (int)($this->data['destination_id'] ?? null), - 'name' => $this->data['destination_name'] ?? null, - 'iban' => $this->data['destination_iban'] ?? null, - 'number' => $this->data['destination_number'] ?? null, - 'bic' => $this->data['destination_bic'] ?? null, - ]; - - // make new account validator. - $expectedType = $this->getExpectedType(); - Log::debug(sprintf('Expected type (new or unchanged) is %s', $expectedType)); + $value = $this->data['amount'] ?? ''; + Log::debug(sprintf('Amount is now "%s"', $value)); try { - $result = $this->getAccount($expectedType, 'destination', $destInfo); + $amount = $this->getAmount($value); } catch (FireflyException $e) { - Log::error(sprintf('getValidDestinationAccount() threw unexpected error: %s', $e->getMessage())); - $result = $this->getOriginalDestinationAccount(); - } - - return $result; - } - - /** - * Updates journal transaction type. - */ - private function updateType(): void - { - Log::debug('Now in updateType()'); - if ($this->hasFields(['type'])) { - $type = 'opening-balance' === $this->data['type'] ? 'opening balance' : $this->data['type']; - Log::debug( - sprintf( - 'Trying to change journal #%d from a %s to a %s.', - $this->transactionJournal->id, - $this->transactionJournal->transactionType->type, - $type - ) - ); - - /** @var TransactionTypeFactory $typeFactory */ - $typeFactory = app(TransactionTypeFactory::class); - $result = $typeFactory->find($this->data['type']); - if (null !== $result) { - Log::debug('Changed transaction type!'); - $this->transactionJournal->transaction_type_id = $result->id; - $this->transactionJournal->save(); - - return; - } + Log::debug(sprintf('getAmount("%s") returns error: %s', $value, $e->getMessage())); return; } - Log::debug('No type field present.'); + $origSourceTransaction = $this->getSourceTransaction(); + $origSourceTransaction->amount = app('steam')->negative($amount); + $origSourceTransaction->save(); + $destTransaction = $this->getDestinationTransaction(); + $destTransaction->amount = app('steam')->positive($amount); + $destTransaction->save(); + // refresh transactions. + $this->sourceTransaction->refresh(); + $this->destinationTransaction->refresh(); + Log::debug(sprintf('Updated amount to "%s"', $amount)); } /** @@ -510,45 +525,6 @@ class JournalUpdateService } } - /** - * Update journal generic field. Cannot be set to NULL. - * - * @param string $fieldName - */ - private function updateField(string $fieldName): void - { - if (array_key_exists($fieldName, $this->data) && '' !== (string)$this->data[$fieldName]) { - $value = $this->data[$fieldName]; - - if ('date' === $fieldName) { - if ($value instanceof Carbon) { - // update timezone. - $value->setTimezone(config('app.timezone')); - } - if (!($value instanceof Carbon)) { - $value = new Carbon($value); - } - // do some parsing. - Log::debug(sprintf('Create date value from string "%s".', $value)); - } - $this->transactionJournal->$fieldName = $value; - Log::debug(sprintf('Updated %s', $fieldName)); - } - } - - /** - * - */ - private function updateCategory(): void - { - // update category - if ($this->hasFields(['category_id', 'category_name'])) { - Log::debug('Will update category.'); - - $this->storeCategory($this->transactionJournal, new NullArrayObject($this->data)); - } - } - /** * */ @@ -568,102 +544,13 @@ class JournalUpdateService /** * */ - private function updateTags(): void + private function updateCategory(): void { - if ($this->hasFields(['tags'])) { - Log::debug('Will update tags.'); - $tags = $this->data['tags'] ?? null; - $this->storeTags($this->transactionJournal, $tags); - } - } + // update category + if ($this->hasFields(['category_id', 'category_name'])) { + Log::debug('Will update category.'); - /** - * - */ - private function updateReconciled(): void - { - if (array_key_exists('reconciled', $this->data) && is_bool($this->data['reconciled'])) { - $this->transactionJournal->transactions()->update(['reconciled' => $this->data['reconciled']]); - } - } - - /** - * - */ - private function updateNotes(): void - { - // update notes. - if ($this->hasFields(['notes'])) { - $notes = '' === (string)$this->data['notes'] ? null : $this->data['notes']; - $this->storeNotes($this->transactionJournal, $notes); - } - } - - /** - * - */ - private function updateMeta(): void - { - // update meta fields. - // first string - if ($this->hasFields($this->metaString)) { - Log::debug('Meta string fields are present.'); - $this->updateMetaFields(); - } - - // then date fields. - if ($this->hasFields($this->metaDate)) { - Log::debug('Meta date fields are present.'); - $this->updateMetaDateFields(); - } - } - - /** - * - */ - private function updateMetaFields(): void - { - /** @var TransactionJournalMetaFactory $factory */ - $factory = app(TransactionJournalMetaFactory::class); - - foreach ($this->metaString as $field) { - if ($this->hasFields([$field])) { - $value = '' === $this->data[$field] ? null : $this->data[$field]; - Log::debug(sprintf('Field "%s" is present ("%s"), try to update it.', $field, $value)); - $set = [ - 'journal' => $this->transactionJournal, - 'name' => $field, - 'data' => $value, - ]; - $factory->updateOrCreate($set); - } - } - } - - /** - * - */ - private function updateMetaDateFields(): void - { - /** @var TransactionJournalMetaFactory $factory */ - $factory = app(TransactionJournalMetaFactory::class); - - foreach ($this->metaDate as $field) { - if ($this->hasFields([$field])) { - try { - $value = '' === (string)$this->data[$field] ? null : new Carbon($this->data[$field]); - } catch (InvalidDateException $e) { - Log::debug(sprintf('%s is not a valid date value: %s', $this->data[$field], $e->getMessage())); - return; - } - Log::debug(sprintf('Field "%s" is present ("%s"), try to update it.', $field, $value)); - $set = [ - 'journal' => $this->transactionJournal, - 'name' => $field, - 'data' => $value, - ]; - $factory->updateOrCreate($set); - } + $this->storeCategory($this->transactionJournal, new NullArrayObject($this->data)); } } @@ -700,34 +587,39 @@ class JournalUpdateService } /** + * Update journal generic field. Cannot be set to NULL. * + * @param string $fieldName */ - private function updateAmount(): void + private function updateField(string $fieldName): void { - Log::debug(sprintf('Now in %s', __METHOD__)); - if (!$this->hasFields(['amount'])) { - return; - } + if (array_key_exists($fieldName, $this->data) && '' !== (string)$this->data[$fieldName]) { + $value = $this->data[$fieldName]; - $value = $this->data['amount'] ?? ''; - Log::debug(sprintf('Amount is now "%s"', $value)); - try { - $amount = $this->getAmount($value); - } catch (FireflyException $e) { - Log::debug(sprintf('getAmount("%s") returns error: %s', $value, $e->getMessage())); + if ('date' === $fieldName) { + if ($value instanceof Carbon) { + // update timezone. + $value->setTimezone(config('app.timezone')); + } + if (!($value instanceof Carbon)) { + $value = new Carbon($value); + } + // do some parsing. + Log::debug(sprintf('Create date value from string "%s".', $value)); + } + event( + new TriggeredAuditLog( + $this->transactionJournal->user, + $this->transactionJournal, + sprintf('update_%s', $fieldName), + $this->transactionJournal->$fieldName, + $value + ) + ); - return; + $this->transactionJournal->$fieldName = $value; + Log::debug(sprintf('Updated %s', $fieldName)); } - $origSourceTransaction = $this->getSourceTransaction(); - $origSourceTransaction->amount = app('steam')->negative($amount); - $origSourceTransaction->save(); - $destTransaction = $this->getDestinationTransaction(); - $destTransaction->amount = app('steam')->positive($amount); - $destTransaction->save(); - // refresh transactions. - $this->sourceTransaction->refresh(); - $this->destinationTransaction->refresh(); - Log::debug(sprintf('Updated amount to "%s"', $amount)); } /** @@ -749,7 +641,8 @@ class JournalUpdateService // find currency in data array $newForeignId = $this->data['foreign_currency_id'] ?? null; $newForeignCode = $this->data['foreign_currency_code'] ?? null; - $foreignCurrency = $this->currencyRepository->findCurrencyNull($newForeignId, $newForeignCode) ?? $foreignCurrency; + $foreignCurrency = $this->currencyRepository->findCurrencyNull($newForeignId, $newForeignCode) ?? + $foreignCurrency; // not the same as normal currency if (null !== $foreignCurrency && $foreignCurrency->id === $this->transactionJournal->transaction_currency_id) { @@ -767,7 +660,14 @@ class JournalUpdateService $dest->foreign_amount = app('steam')->positive($foreignAmount); $dest->save(); - Log::debug(sprintf('Update foreign info to %s (#%d) %s', $foreignCurrency->code, $foreignCurrency->id, $foreignAmount)); + Log::debug( + sprintf( + 'Update foreign info to %s (#%d) %s', + $foreignCurrency->code, + $foreignCurrency->id, + $foreignAmount + ) + ); // refresh transactions. $this->sourceTransaction->refresh(); @@ -791,4 +691,140 @@ class JournalUpdateService $this->sourceTransaction->refresh(); $this->destinationTransaction->refresh(); } + + /** + * + */ + private function updateMeta(): void + { + // update meta fields. + // first string + if ($this->hasFields($this->metaString)) { + Log::debug('Meta string fields are present.'); + $this->updateMetaFields(); + } + + // then date fields. + if ($this->hasFields($this->metaDate)) { + Log::debug('Meta date fields are present.'); + $this->updateMetaDateFields(); + } + } + + /** + * + */ + private function updateMetaDateFields(): void + { + /** @var TransactionJournalMetaFactory $factory */ + $factory = app(TransactionJournalMetaFactory::class); + + foreach ($this->metaDate as $field) { + if ($this->hasFields([$field])) { + try { + $value = '' === (string)$this->data[$field] ? null : new Carbon($this->data[$field]); + } catch (InvalidDateException $e) { + Log::debug(sprintf('%s is not a valid date value: %s', $this->data[$field], $e->getMessage())); + + return; + } + Log::debug(sprintf('Field "%s" is present ("%s"), try to update it.', $field, $value)); + $set = [ + 'journal' => $this->transactionJournal, + 'name' => $field, + 'data' => $value, + ]; + $factory->updateOrCreate($set); + } + } + } + + /** + * + */ + private function updateMetaFields(): void + { + /** @var TransactionJournalMetaFactory $factory */ + $factory = app(TransactionJournalMetaFactory::class); + + foreach ($this->metaString as $field) { + if ($this->hasFields([$field])) { + $value = '' === $this->data[$field] ? null : $this->data[$field]; + Log::debug(sprintf('Field "%s" is present ("%s"), try to update it.', $field, $value)); + $set = [ + 'journal' => $this->transactionJournal, + 'name' => $field, + 'data' => $value, + ]; + $factory->updateOrCreate($set); + } + } + } + + /** + * + */ + private function updateNotes(): void + { + // update notes. + if ($this->hasFields(['notes'])) { + $notes = '' === (string)$this->data['notes'] ? null : $this->data['notes']; + $this->storeNotes($this->transactionJournal, $notes); + } + } + + /** + * + */ + private function updateReconciled(): void + { + if (array_key_exists('reconciled', $this->data) && is_bool($this->data['reconciled'])) { + $this->transactionJournal->transactions()->update(['reconciled' => $this->data['reconciled']]); + } + } + + /** + * + */ + private function updateTags(): void + { + if ($this->hasFields(['tags'])) { + Log::debug('Will update tags.'); + $tags = $this->data['tags'] ?? null; + $this->storeTags($this->transactionJournal, $tags); + } + } + + /** + * Updates journal transaction type. + */ + private function updateType(): void + { + Log::debug('Now in updateType()'); + if ($this->hasFields(['type'])) { + $type = 'opening-balance' === $this->data['type'] ? 'opening balance' : $this->data['type']; + Log::debug( + sprintf( + 'Trying to change journal #%d from a %s to a %s.', + $this->transactionJournal->id, + $this->transactionJournal->transactionType->type, + $type + ) + ); + + /** @var TransactionTypeFactory $typeFactory */ + $typeFactory = app(TransactionTypeFactory::class); + $result = $typeFactory->find($this->data['type']); + if (null !== $result) { + Log::debug('Changed transaction type!'); + $this->transactionJournal->transaction_type_id = $result->id; + $this->transactionJournal->save(); + + return; + } + + return; + } + Log::debug('No type field present.'); + } } diff --git a/app/Support/Twig/General.php b/app/Support/Twig/General.php index d01c1c5a8c..61b00204dd 100644 --- a/app/Support/Twig/General.php +++ b/app/Support/Twig/General.php @@ -109,7 +109,11 @@ class General extends AbstractExtension [, $route, $objectType] = func_get_args(); $activeObjectType = $context['objectType'] ?? false; - if ($objectType === $activeObjectType && false !== stripos(Route::getCurrentRoute()->getName(), $route)) { + if ($objectType === $activeObjectType + && false !== stripos( + Route::getCurrentRoute()->getName(), + $route + )) { return 'active'; } @@ -171,7 +175,7 @@ class General extends AbstractExtension return new TwigFunction( 'carbonize', static function (string $date): Carbon { - return new Carbon($date); + return new Carbon($date, config('app.timezone')); } ); } @@ -205,15 +209,15 @@ class General extends AbstractExtension static function (int $size): string { // less than one GB, more than one MB if ($size < (1024 * 1024 * 2014) && $size >= (1024 * 1024)) { - return round($size / (1024 * 1024), 2).' MB'; + return round($size / (1024 * 1024), 2) . ' MB'; } // less than one MB if ($size < (1024 * 1024)) { - return round($size / 1024, 2).' KB'; + return round($size / 1024, 2) . ' KB'; } - return $size.' bytes'; + return $size . ' bytes'; } ); } @@ -245,6 +249,7 @@ class General extends AbstractExtension 'getRootSearchOperator', static function (string $operator): string { $result = OperatorQuerySearch::getRootOperator($operator); + return str_replace('-', 'not_', $result); } ); diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index d52d72a367..6c838d1dd1 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -2595,6 +2595,9 @@ return [ 'ale_action_log_add' => 'Added :amount to piggy bank ":name"', 'ale_action_log_remove' => 'Removed :amount from piggy bank ":name"', 'ale_action_clear_budget' => 'Removed from budget', + 'ale_action_update_group_title' => 'Updated transaction group title', + 'ale_action_update_date' => 'Updated transaction date', + 'ale_action_update_order' => 'Updated transaction order', 'ale_action_clear_category' => 'Removed from category', 'ale_action_clear_notes' => 'Removed notes', 'ale_action_clear_tag' => 'Cleared tag', diff --git a/resources/views/list/ale.twig b/resources/views/list/ale.twig index 12263e95e7..3632ec3bd1 100644 --- a/resources/views/list/ale.twig +++ b/resources/views/list/ale.twig @@ -4,13 +4,16 @@ {# link to object: #} {% if 'FireflyIII\\Models\\Rule' == logEntry.changer_type %} - - {% endif %} - {{ logEntry.changer_type|replace({"FireflyIII\\Models\\": ""}) }} - #{{ logEntry.changer_id }} + + {% endif %} + {% if 'FireflyIII\\User' == logEntry.changer_type %} + + {% endif %} + {{ logEntry.changer_type|replace({"FireflyIII\\Models\\": ""})|replace({"FireflyIII\\": ""}) }} + #{{ logEntry.changer_id }} - + {{ trans('firefly.ale_action_'~logEntry.action) }} @@ -18,8 +21,11 @@ {% if 'add_tag' == logEntry.action %} {{ logEntry.after }} {% endif %} - {% if 'clear_budget' == logEntry.action %} + + {% if 'update_group_title' == logEntry.action %} {{ logEntry.before }} + → + {{ logEntry.after }} {% endif %} {% if 'clear_category' == logEntry.action %} {{ logEntry.before }} @@ -51,6 +57,11 @@ {% if 'set_destination' == logEntry.action %} {{ logEntry.after }} {% endif %} + {% if 'update_date' == logEntry.action %} + {{ carbonize(logEntry.before).isoFormat(dateTimeFormat) }} + → + {{ carbonize(logEntry.after).isoFormat(dateTimeFormat) }} + {% endif %} {% if 'update_transaction_type' == logEntry.action %} {{ trans('firefly.'~logEntry.before) }} → {{ trans('firefly.'~logEntry.after) }} {% endif %} diff --git a/resources/views/transactions/show.twig b/resources/views/transactions/show.twig index 645de8ea1d..c72cdd4731 100644 --- a/resources/views/transactions/show.twig +++ b/resources/views/transactions/show.twig @@ -11,32 +11,42 @@

{{ 'transaction_journal_information'|_ }}

- @@ -69,6 +79,19 @@
+ {% if groupLogEntries|length > 0 %} +
+
+

+ {{ 'audit_log_entries'|_ }} +

+
+
+ {% include 'list.ale' with {logEntries: groupLogEntries} %} +
+
+ {% endif %} +
@@ -183,38 +206,58 @@
-
@@ -222,6 +265,7 @@ - + {% endif %} {% if null != journal.budget_id and first.transactiontype.type == 'Withdrawal' %} - + {% endif %} {% if null != journal.bill_id and first.transactiontype.type == 'Withdrawal' %} - + {% endif %} @@ -294,7 +346,7 @@ @@ -317,7 +369,7 @@ {% endif %} - {% if journalHasMeta(journal.transaction_journal_id, 'recurring_total') and journalHasMeta(journal.transaction_journal_id, 'recurring_count') %} + {% if journalHasMeta(journal.transaction_journal_id, 'recurring_total') and journalHasMeta(journal.transaction_journal_id, 'recurring_count') %} {% set recurringTotal = journalGetMetaField(journal.transaction_journal_id, 'recurring_total') %} {% if 0 == recurringTotal %} {% set recurringTotal = '∞' %} @@ -332,7 +384,8 @@ + {{ event.piggy }} + {% endfor %} @@ -417,16 +472,16 @@ {% endif %} {% if logEntries[journal.transaction_journal_id]|length > 0 %} -
-
-

- {{ 'audit_log_entries'|_ }} -

+
+
+

+ {{ 'audit_log_entries'|_ }} +

+
+
+ {% include 'list.ale' with {logEntries: logEntries[journal.transaction_journal_id]} %} +
-
- {% include 'list.ale' with {logEntries: logEntries[journal.transaction_journal_id]} %} -
-
{% endif %}
{% endfor %} @@ -444,6 +499,7 @@ $('.switch-link').on('click', switchLink); var switchLinkUrl = '{{ route('transactions.link.switch') }}'; + function switchLink(e) { e.preventDefault(); var obj = $(e.currentTarget); @@ -461,7 +517,9 @@ return false } - - + + {% endblock %}
+ {% if 'Cash account' == journal.source_type %} ({{ 'cash'|_ }}) @@ -230,8 +274,10 @@ title="{{ journal.source_iban|default(journal.source_name) }}">{{ journal.source_name }} → {% endif %} - {% if first.transactiontype.type == 'Withdrawal' or first.transactiontype.type == 'Deposit' %} + {% if first.transactiontype.type == 'Withdrawal' %} {{ formatAmountBySymbol(journal.amount*-1, journal.currency_symbol, journal.currency_decimal_places) }} + {% elseif first.transactiontype.type == 'Deposit' %} + {{ formatAmountBySymbol(journal.amount, journal.currency_symbol, journal.currency_decimal_places) }} {% elseif first.transactiontype.type == 'Transfer' or first.transactiontype.type == 'Opening balance' %} {{ formatAmountBySymbol(journal.amount, journal.currency_symbol, journal.currency_decimal_places, false) }} @@ -265,19 +311,25 @@ {% if null != journal.category_id %}
{{ 'category'|_ }}{{ journal.category_name }} + {{ journal.category_name }} +
{{ 'budget'|_ }}{{ journal.budget_name }} + {{ journal.budget_name }} +
{{ 'bill'|_ }}{{ journal.bill_name }} + {{ journal.bill_name }} +
{{ trans('list.'~metaField) }} - {% if 'external_url' == metaField %} + {% if 'external_url' == metaField %} {% set url = journalGetMetaField(journal.transaction_journal_id, metaField) %} {% if url|length > 60 %} @@ -304,7 +356,7 @@ {% endif %} {% endif %} - {% if 'external_url' != metaField %} + {% if 'external_url' != metaField %} {{ journalGetMetaField(journal.transaction_journal_id, metaField) }} {% endif %} {{ journal.notes|default('')|markdown }}
{{ 'tags'|_ }} {% for tag in journal.tags %} -

+

{{ tag.tag }}

{% endfor %} @@ -359,17 +412,18 @@
- +

{% if link.editable %} - {{ link.link }} + {{ link.link }} {% else %} {{ trans('firefly.'~link.link) }} - {% endif %} + {% endif %} "{{ link.description }}" + title="{{ link.description }}">{{ link.description }}" ({{ link.amount|raw }}) {% if '' != link.foreign_amount %} @@ -408,7 +462,8 @@
{{ event.amount|raw }} - {{ event.piggy }}