diff --git a/app/Http/Controllers/Transaction/LinkController.php b/app/Http/Controllers/Transaction/LinkController.php index 835661022e..3f5c1a1a37 100644 --- a/app/Http/Controllers/Transaction/LinkController.php +++ b/app/Http/Controllers/Transaction/LinkController.php @@ -13,13 +13,80 @@ declare(strict_types=1); namespace FireflyIII\Http\Controllers\Transaction; -use Illuminate\Http\Request; +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 Session; class LinkController { - public function store(Request $request) { - var_dump($request->all()); + /** + * @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 + ) { + $linkType = $request->get('link_type'); + $parts = explode('_', $linkType); + if (count($parts) !== 2) { + Session::flash('error', trans('firefly.invalid_link_data')); + + return redirect(route('transactions.show', $journal->id)); + } + if (!in_array($parts[1], ['inward', 'outward'])) { + Session::flash('error', trans('firefly.invalid_link_data')); + + return redirect(route('transactions.show', $journal->id)); + } + $linkTypeId = intval($parts[0]); + $linkType = $repository->find($linkTypeId); + if ($linkType->id !== $linkTypeId) { + Session::flash('error', trans('firefly.invalid_link_data')); + + return redirect(route('transactions.show', $journal->id)); + } + Log::debug('Will link using linktype', $linkType->toArray()); + $linkJournalId = intval($request->get('link_journal_id')); + + if ($linkJournalId === 0 && ctype_digit($request->string('link_other'))) { + $linkJournalId = intval($request->string('link_other')); + } + + $opposing = $journalRepository->find($linkJournalId); + $result = $repository->findLink($journal, $opposing); + if ($result) { + 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, $opposing->id)); + + $journalLink = new TransactionJournalLink; + $journalLink->linkType()->associate($linkType); + if ($parts[1] === 'inward') { + Log::debug(sprintf('Link type is inwards ("%s"), so %d is source and %d is destination.', $linkType->inward, $opposing->id, $journal->id)); + $journalLink->source()->associate($opposing); + $journalLink->destination()->associate($journal); + } + if ($parts[1] === 'outward') { + Log::debug(sprintf('Link type is inwards ("%s"), so %d is source and %d is destination.', $linkType->outward, $journal->id, $opposing->id)); + $journalLink->source()->associate($journal); + $journalLink->destination()->associate($opposing); + } + $journalLink->comment = strlen($request->string('comments')) > 0 ? $request->string('comments') : null; + $journalLink->save(); + Session::flash('success', trans('firefly.journals_linked')); + + return redirect(route('transactions.show', $journal->id)); } } \ No newline at end of file diff --git a/app/Http/Controllers/TransactionController.php b/app/Http/Controllers/TransactionController.php index eb21c44356..8263a1f16f 100644 --- a/app/Http/Controllers/TransactionController.php +++ b/app/Http/Controllers/TransactionController.php @@ -161,13 +161,14 @@ class TransactionController extends Controller if ($this->isOpeningBalance($journal)) { return $this->redirectToAccount($journal); } - $linkTypes = $linkTypeRepository->get(); + $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', 'linkTypes')); + return view('transactions.show', compact('journal', 'events', 'subTitle', 'what', 'transactions', 'linkTypes','links')); } diff --git a/app/Http/Requests/JournalLinkRequest.php b/app/Http/Requests/JournalLinkRequest.php new file mode 100644 index 0000000000..048bc6d28b --- /dev/null +++ b/app/Http/Requests/JournalLinkRequest.php @@ -0,0 +1,40 @@ +check(); + } + + + /** + * @return array + */ + public function rules() + { + return []; + } +} diff --git a/app/Models/TransactionJournalLink.php b/app/Models/TransactionJournalLink.php index a864c66bb2..0bbcb2aa49 100644 --- a/app/Models/TransactionJournalLink.php +++ b/app/Models/TransactionJournalLink.php @@ -13,7 +13,9 @@ declare(strict_types=1); namespace FireflyIII\Models; +use Crypt; use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Relations\BelongsTo; /** * Class TransactionJournalLink @@ -22,31 +24,58 @@ use Illuminate\Database\Eloquent\Model; */ class TransactionJournalLink extends Model { -protected $table = 'journal_links'; - + protected $table = 'journal_links'; /** * @return \Illuminate\Database\Eloquent\Relations\BelongsTo */ - public function linkType() + 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'); - } - - /** - * @return \Illuminate\Database\Eloquent\Relations\BelongsTo - */ - public function destination() - { - return $this->belongsTo(TransactionJournal::class,'destination_id'); + return $this->belongsTo(TransactionJournal::class, 'source_id'); } diff --git a/app/Repositories/LinkType/LinkTypeRepository.php b/app/Repositories/LinkType/LinkTypeRepository.php index 2376dde92d..e2786718ca 100644 --- a/app/Repositories/LinkType/LinkTypeRepository.php +++ b/app/Repositories/LinkType/LinkTypeRepository.php @@ -13,6 +13,7 @@ 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; @@ -49,6 +50,7 @@ class LinkTypeRepository implements LinkTypeRepositoryInterface TransactionJournalLink::where('link_type_id', $linkType->id)->update(['link_type_id' => $moveTo->id]); } $linkType->delete(); + return true; } @@ -67,6 +69,24 @@ class LinkTypeRepository implements LinkTypeRepositoryInterface 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 */ @@ -75,6 +95,21 @@ class LinkTypeRepository implements LinkTypeRepositoryInterface 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 */ diff --git a/app/Repositories/LinkType/LinkTypeRepositoryInterface.php b/app/Repositories/LinkType/LinkTypeRepositoryInterface.php index 9a35b431e6..145a69be35 100644 --- a/app/Repositories/LinkType/LinkTypeRepositoryInterface.php +++ b/app/Repositories/LinkType/LinkTypeRepositoryInterface.php @@ -13,6 +13,7 @@ declare(strict_types=1); namespace FireflyIII\Repositories\LinkType; use FireflyIII\Models\LinkType; +use FireflyIII\Models\TransactionJournal; use Illuminate\Support\Collection; /** @@ -22,18 +23,6 @@ use Illuminate\Support\Collection; */ interface LinkTypeRepositoryInterface { - /** - * @param int $id - * - * @return LinkType - */ - public function find(int $id): LinkType; - - /** - * @return Collection - */ - public function get(): Collection; - /** * @param LinkType $linkType * @@ -49,6 +38,37 @@ interface LinkTypeRepositoryInterface */ public function destroy(LinkType $linkType, LinkType $moveTo): 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 * diff --git a/database/migrations/2017_08_20_062014_changes_for_v470.php b/database/migrations/2017_08_20_062014_changes_for_v470.php index f3735202ad..c548d4fca3 100644 --- a/database/migrations/2017_08_20_062014_changes_for_v470.php +++ b/database/migrations/2017_08_20_062014_changes_for_v470.php @@ -40,8 +40,7 @@ class ChangesForV470 extends Migration $table->string('inward'); $table->boolean('editable'); - $table->unique(['name']); - $table->unique(['outward','inward']); + $table->unique(['name', 'outward','inward']); } ); } @@ -54,8 +53,7 @@ class ChangesForV470 extends Migration $table->integer('link_type_id', false, true); $table->integer('source_id', false, true); $table->integer('destination_id', false, true); - $table->text('comment'); - $table->integer('sequence', false, true); + $table->text('comment')->nullable(); $table->foreign('link_type_id')->references('id')->on('link_types')->onDelete('cascade'); $table->foreign('source_id')->references('id')->on('transaction_journals')->onDelete('cascade'); diff --git a/database/seeds/LinkTypeSeeder.php b/database/seeds/LinkTypeSeeder.php index 179142ad87..3b872e4588 100644 --- a/database/seeds/LinkTypeSeeder.php +++ b/database/seeds/LinkTypeSeeder.php @@ -27,28 +27,28 @@ class LinkTypeSeeder extends Seeder $link = new LinkType; $link->name = 'Related'; $link->inward = 'relates to'; - $link->outward = 'is related to'; + $link->outward = 'relates to'; $link->editable = false; $link->save(); $link = new LinkType; $link->name = 'Refund'; - $link->inward = '(partially) refunds'; - $link->outward = 'is (partially) refunded by'; + $link->inward = 'is (partially) refunded by'; + $link->outward = '(partially) refunds'; $link->editable = false; $link->save(); $link = new LinkType; $link->name = 'Paid'; - $link->inward = '(partially) pays for'; - $link->outward = 'is (partially) paid for by'; + $link->inward = 'is (partially) paid for by'; + $link->outward = '(partially) pays for'; $link->editable = false; $link->save(); $link = new LinkType; $link->name = 'Reimbursement'; - $link->inward = '(partially) reimburses'; - $link->outward = 'is (partially) reimbursed by'; + $link->inward = 'is (partially) reimbursed by'; + $link->outward = '(partially) reimburses'; $link->editable = false; $link->save(); diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index 69c482ab9e..e8c9c2386a 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -957,7 +957,10 @@ return [ 'this_transaction' => 'This transaction', 'transaction' => 'Transaction', 'comments' => 'Comments', - + 'to_link_not_found' => 'If the transaction you want to link to is not listed, simply enter its ID.', + 'invalid_link_data' => 'Invalid link type selected. Cannot link transaction.', + 'journals_linked' => 'Transactions are linked.', + 'journals_error_linked' => 'These journals are already linked.', // split a transaction: 'transaction_meta_data' => 'Transaction meta-data', diff --git a/resources/views/transactions/show.twig b/resources/views/transactions/show.twig index 59ba96b99b..5a0bc35917 100644 --- a/resources/views/transactions/show.twig +++ b/resources/views/transactions/show.twig @@ -54,7 +54,8 @@
+ + | +
+ {{ 'this_transaction'|_ }}
+ (#{{ journal.id }})
+ {% if link.source.id == journal.id %}
+ {{ link.linkType.outward }}
+ #{{ link.destination.id }}:
+ {{ link.destination.description }}
+ ({{ journalAmount(link.destination) }})
+ {% else %}
+ {{ link.linkType.inward }}
+ #{{ link.source.id }}:
+ {{ link.source.description }}
+ ({{ journalAmount(link.source) }})
+ {% endif %}
+ {% if link.comment != "" %}
+ {{ link.comment }} + {% endif %} + |
+
+ +
+ {% endif %}{{ 'to_link_not_found'|_ }}