diff --git a/app/Http/Controllers/Admin/LinkController.php b/app/Http/Controllers/Admin/LinkController.php index ca2ce39184..06bf9daf69 100644 --- a/app/Http/Controllers/Admin/LinkController.php +++ b/app/Http/Controllers/Admin/LinkController.php @@ -14,6 +14,11 @@ 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; /** @@ -41,14 +46,166 @@ class LinkController extends Controller } /** - * + * @return \Illuminate\Contracts\View\Factory|\Illuminate\View\View */ - public function index() + 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 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')); + } + + /** + * @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')); + return view('admin.link.index', compact('subTitle', 'subTitleIcon', 'linkTypes')); + } + + /** + * @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')); } } \ No newline at end of file diff --git a/app/Http/Controllers/Json/AutoCompleteController.php b/app/Http/Controllers/Json/AutoCompleteController.php index 28dc7495d8..dbcbf45ab0 100644 --- a/app/Http/Controllers/Json/AutoCompleteController.php +++ b/app/Http/Controllers/Json/AutoCompleteController.php @@ -12,10 +12,13 @@ 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; /** @@ -74,6 +77,39 @@ class AutoCompleteController extends Controller return Response::json($return); } + /** + * @param JournalCollectorInterface $collector + * + * @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 * diff --git a/app/Http/Controllers/Transaction/LinkController.php b/app/Http/Controllers/Transaction/LinkController.php new file mode 100644 index 0000000000..835661022e --- /dev/null +++ b/app/Http/Controllers/Transaction/LinkController.php @@ -0,0 +1,25 @@ +all()); + } + +} \ No newline at end of file diff --git a/app/Http/Controllers/TransactionController.php b/app/Http/Controllers/TransactionController.php index 059a1e3fb7..eb21c44356 100644 --- a/app/Http/Controllers/TransactionController.php +++ b/app/Http/Controllers/TransactionController.php @@ -20,6 +20,7 @@ use FireflyIII\Helpers\Filter\InternalTransferFilter; 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; @@ -155,18 +156,18 @@ class TransactionController extends Controller * * @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(); $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')); } diff --git a/app/Http/Requests/LinkTypeFormRequest.php b/app/Http/Requests/LinkTypeFormRequest.php new file mode 100644 index 0000000000..625e40d0d5 --- /dev/null +++ b/app/Http/Requests/LinkTypeFormRequest.php @@ -0,0 +1,57 @@ +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; + } +} diff --git a/app/Http/Requests/Request.php b/app/Http/Requests/Request.php index 87e1bf2934..30906089c5 100644 --- a/app/Http/Requests/Request.php +++ b/app/Http/Requests/Request.php @@ -85,7 +85,7 @@ class Request extends FormRequest * * @return string */ - protected function string(string $field): string + public function string(string $field): string { $string = $this->get($field) ?? ''; $search = [ diff --git a/app/Http/breadcrumbs.php b/app/Http/breadcrumbs.php index fd3bd27685..98ba0c7234 100644 --- a/app/Http/breadcrumbs.php +++ b/app/Http/breadcrumbs.php @@ -21,6 +21,7 @@ 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; @@ -159,6 +160,26 @@ Breadcrumbs::register( } ); +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.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])); +} +); /** * ATTACHMENTS diff --git a/app/Models/LinkType.php b/app/Models/LinkType.php index 8a86779466..da8d60de5b 100644 --- a/app/Models/LinkType.php +++ b/app/Models/LinkType.php @@ -16,6 +16,7 @@ namespace FireflyIII\Models; use Illuminate\Database\Eloquent\Model; /** + * @property int $journalCount * Class LinkType * * @package FireflyIII\Models @@ -35,4 +36,8 @@ class LinkType extends Model 'editable' => 'boolean', ]; + public function transactionJournalLinks() { + return $this->hasMany(TransactionJournalLink::class); + } + } \ No newline at end of file diff --git a/app/Models/TransactionJournal.php b/app/Models/TransactionJournal.php index 977ef1ea90..d0f1e4ab8e 100644 --- a/app/Models/TransactionJournal.php +++ b/app/Models/TransactionJournal.php @@ -372,6 +372,21 @@ class TransactionJournal extends Model return $entry; } + /** + * @return HasMany + */ + public function sourceJournalLinks(): HasMany + { + return $this->hasMany(TransactionJournalLink::class, 'source_id'); + } + /** + * @return HasMany + */ + public function destinationJournalLinks(): HasMany + { + return $this->hasMany(TransactionJournalLink::class, 'destination_id'); + } + /** * @return \Illuminate\Database\Eloquent\Relations\BelongsToMany */ diff --git a/app/Models/TransactionJournalLink.php b/app/Models/TransactionJournalLink.php new file mode 100644 index 0000000000..a864c66bb2 --- /dev/null +++ b/app/Models/TransactionJournalLink.php @@ -0,0 +1,53 @@ +belongsTo(LinkType::class); + } + + /** + * @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'); + } + + +} \ No newline at end of file diff --git a/app/Providers/AdminServiceProvider.php b/app/Providers/AdminServiceProvider.php new file mode 100644 index 0000000000..a325823f06 --- /dev/null +++ b/app/Providers/AdminServiceProvider.php @@ -0,0 +1,63 @@ +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; + } + ); + } + +} \ No newline at end of file diff --git a/app/Repositories/LinkType/LinkTypeRepository.php b/app/Repositories/LinkType/LinkTypeRepository.php new file mode 100644 index 0000000000..2376dde92d --- /dev/null +++ b/app/Repositories/LinkType/LinkTypeRepository.php @@ -0,0 +1,119 @@ +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 int $id + * + * @return LinkType + */ + public function find(int $id): LinkType + { + $linkType = LinkType::find($id); + if (is_null($linkType)) { + return new LinkType; + } + + return $linkType; + } + + /** + * @return Collection + */ + public function get(): Collection + { + return LinkType::orderBy('name', 'ASC')->get(); + } + + /** + * @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 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; + + } +} \ No newline at end of file diff --git a/app/Repositories/LinkType/LinkTypeRepositoryInterface.php b/app/Repositories/LinkType/LinkTypeRepositoryInterface.php new file mode 100644 index 0000000000..9a35b431e6 --- /dev/null +++ b/app/Repositories/LinkType/LinkTypeRepositoryInterface.php @@ -0,0 +1,67 @@ +string('outward'); $table->string('inward'); $table->boolean('editable'); + + $table->unique(['name']); + $table->unique(['outward','inward']); } ); } @@ -46,11 +50,20 @@ class ChangesForV470 extends Migration Schema::create( 'journal_links', function (Blueprint $table) { $table->increments('id'); + $table->timestamps(); $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->foreign('link_type_id')->references('id')->on('link_types')->onDelete('cascade'); + $table->foreign('source_id')->references('id')->on('transaction_journals')->onDelete('cascade'); + $table->foreign('destination_id')->references('id')->on('transaction_journals')->onDelete('cascade'); + + $table->unique(['link_type_id','source_id','destination_id']); + + } ); } diff --git a/public/js/ff/transactions/show.js b/public/js/ff/transactions/show.js new file mode 100644 index 0000000000..9e797ce3b2 --- /dev/null +++ b/public/js/ff/transactions/show.js @@ -0,0 +1,39 @@ +/* + * show.js + * 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. + */ + +/** global: autoCompleteUri */ + +$(function () { + "use strict"; + + + $.getJSON(autoCompleteUri).done(function (data) { + var $input = $("#link_other"); + $input.typeahead({ + source: data, + autoSelect: true + }); + $input.change(function() { + var current = $input.typeahead("getActive"); + if (current) { + // Some item from your model is active! + if (current.name.toLowerCase() === $input.val().toLowerCase()) { + // This means the exact match is found. Use toLowerCase() if you want case insensitive match. + $('input[name="link_journal_id"]').val(current.id); + } else { + $('input[name="link_journal_id"]').val(0); + } + } else { + $('input[name="link_journal_id"]').val(0); + } + }); + }); + + +}); \ No newline at end of file diff --git a/resources/lang/en_US/firefly.php b/resources/lang/en_US/firefly.php index a72ba1ccc3..69c482ab9e 100644 --- a/resources/lang/en_US/firefly.php +++ b/resources/lang/en_US/firefly.php @@ -937,6 +937,26 @@ return [ 'no_block_code' => 'No reason for block or user not blocked', // links 'journal_link_configuration' => 'Transaction links configuration', + 'create_new_link_type' => 'Create new link type', + 'store_new_link_type' => 'Store new link type', + 'update_link_type' => 'Update link type', + 'edit_link_type' => 'Edit link type ":name"', + 'updated_link_type' => 'Updated link type ":name"', + 'delete_link_type' => 'Delete link type ":name"', + 'deleted_link_type' => 'Deleted link type ":name"', + 'stored_new_link_type' => 'Store new link type ":name"', + 'cannot_edit_link_type' => 'Cannot edit link type ":name"', + 'link_type_help_name' => 'Ie. "Duplicates"', + 'link_type_help_inward' => 'Ie. "duplicates"', + 'link_type_help_outward' => 'Ie. "is duplicated by"', + 'save_connections_by_moving' => 'Save the link between these transaction(s) by moving them to another link type:', + 'do_not_save_connection' => '(do not save connection)', + 'link_transaction' => 'Link transaction', + 'link_to_other_transaction' => 'Link this transaction to another transaction', + 'select_transaction_to_link' => 'Select a transaction to link this transaction to', + 'this_transaction' => 'This transaction', + 'transaction' => 'Transaction', + 'comments' => 'Comments', // split a transaction: diff --git a/resources/lang/en_US/form.php b/resources/lang/en_US/form.php index 1f0d3e0ef0..f47f680876 100644 --- a/resources/lang/en_US/form.php +++ b/resources/lang/en_US/form.php @@ -135,6 +135,7 @@ return [ 'delete_attachment' => 'Delete attachment ":name"', 'delete_rule' => 'Delete rule ":title"', 'delete_rule_group' => 'Delete rule group ":title"', + 'delete_link_type' => 'Delete link type ":name"', 'attachment_areYouSure' => 'Are you sure you want to delete the attachment named ":name"?', 'account_areYouSure' => 'Are you sure you want to delete the account named ":name"?', 'bill_areYouSure' => 'Are you sure you want to delete the bill named ":name"?', @@ -147,11 +148,13 @@ return [ 'journal_areYouSure' => 'Are you sure you want to delete the transaction described ":description"?', 'mass_journal_are_you_sure' => 'Are you sure you want to delete these transactions?', 'tag_areYouSure' => 'Are you sure you want to delete the tag ":tag"?', + 'linkType_areYouSure' => 'Are you sure you want to delete the link type ":name" (":inward" / ":outward")?', 'permDeleteWarning' => 'Deleting stuff from Firely is permanent and cannot be undone.', 'mass_make_selection' => 'You can still prevent items from being deleted by removing the checkbox.', 'delete_all_permanently' => 'Delete selected permanently', 'update_all_journals' => 'Update these transactions', 'also_delete_transactions' => 'The only transaction connected to this account will be deleted as well.|All :count transactions connected to this account will be deleted as well.', + 'also_delete_connections' => 'The only transaction linked with this link type will lose this connection.|All :count transactions linked with this link type will lose their connection.', 'also_delete_rules' => 'The only rule connected to this rule group will be deleted as well.|All :count rules connected to this rule group will be deleted as well.', 'also_delete_piggyBanks' => 'The only piggy bank connected to this account will be deleted as well.|All :count piggy bank connected to this account will be deleted as well.', 'bill_keep_transactions' => 'The only transaction connected to this bill will not be deleted.|All :count transactions connected to this bill will spared deletion.', @@ -189,4 +192,7 @@ return [ 'payment_date' => 'Payment date', 'invoice_date' => 'Invoice date', 'internal_reference' => 'Internal reference', + + 'inward' => 'Inward description', + 'outward' => 'Outward description', ]; diff --git a/resources/lang/en_US/list.php b/resources/lang/en_US/list.php index c44721b8d5..a5a19d981e 100644 --- a/resources/lang/en_US/list.php +++ b/resources/lang/en_US/list.php @@ -78,14 +78,17 @@ return [ 'source_account' => 'Source account', 'destination_account' => 'Destination account', - 'accounts_count' => 'Number of accounts', - 'journals_count' => 'Number of transactions', - 'attachments_count' => 'Number of attachments', - 'bills_count' => 'Number of bills', - 'categories_count' => 'Number of categories', - 'export_jobs_count' => 'Number of export jobs', - 'import_jobs_count' => 'Number of import jobs', - 'budget_count' => 'Number of budgets', - 'rule_and_groups_count' => 'Number of rules and rule groups', - 'tags_count' => 'Number of tags', + 'accounts_count' => 'Number of accounts', + 'journals_count' => 'Number of transactions', + 'attachments_count' => 'Number of attachments', + 'bills_count' => 'Number of bills', + 'categories_count' => 'Number of categories', + 'export_jobs_count' => 'Number of export jobs', + 'import_jobs_count' => 'Number of import jobs', + 'budget_count' => 'Number of budgets', + 'rule_and_groups_count' => 'Number of rules and rule groups', + 'tags_count' => 'Number of tags', + 'inward' => 'Inward description', + 'outward' => 'Outward description', + 'number_of_transactions' => 'Number of transactions', ]; diff --git a/resources/views/admin/link/create.twig b/resources/views/admin/link/create.twig new file mode 100644 index 0000000000..cb69f859df --- /dev/null +++ b/resources/views/admin/link/create.twig @@ -0,0 +1,44 @@ +{% extends "./layout/default" %} + +{% block breadcrumbs %} + {{ Breadcrumbs.renderIfExists(Route.getCurrentRoute.getName) }} +{% endblock %} + +{% block content %} + +
+ +
+
+
+
+

{{ 'mandatoryFields'|_ }}

+
+
+ {{ ExpandedForm.text('name', null, {helpText: trans('firefly.link_type_help_name')}) }} + {{ ExpandedForm.text('inward', null, {helpText: trans('firefly.link_type_help_inward')}) }} + {{ ExpandedForm.text('outward', null, {helpText: trans('firefly.link_type_help_outward')}) }} +
+
+
+
+ +
+
+

{{ 'options'|_ }}

+
+
+ {{ ExpandedForm.optionsList('create','link_type') }} +
+ +
+ +
+ +
+
+ + +{% endblock %} diff --git a/resources/views/admin/link/delete.twig b/resources/views/admin/link/delete.twig new file mode 100644 index 0000000000..7b94903ed9 --- /dev/null +++ b/resources/views/admin/link/delete.twig @@ -0,0 +1,53 @@ +{% extends "./layout/default" %} + +{% block breadcrumbs %} + {{ Breadcrumbs.renderIfExists(Route.getCurrentRoute.getName, linkType) }} +{% endblock %} + +{% block content %} + +
+ +
+
+
+
+

{{ trans('form.delete_link_type', {'name': linkType.name}) }}

+
+
+

+ {{ trans('form.permDeleteWarning') }} +

+ +

+ {{ trans('form.linkType_areYouSure', {'name': linkType.name,'inward': linkType.inward,'outward': linkType.outward}) }} +

+ + {% if count > 0 %} +

+ {{ Lang.choice('form.also_delete_connections', count, {count: count}) }} +

+ {% endif %} + {% if count > 0 %} +

+ {{ 'save_connections_by_moving'|_ }} +

+ +

+ {{ Form.select('move_link_type_before_delete', moveTo, null, {class: 'form-control'}) }} +

+ {% else %} + + {% endif %} + +
+ +
+
+
+ +
+{% endblock %} diff --git a/resources/views/admin/link/edit.twig b/resources/views/admin/link/edit.twig new file mode 100644 index 0000000000..1bd8599f70 --- /dev/null +++ b/resources/views/admin/link/edit.twig @@ -0,0 +1,48 @@ +{% extends "./layout/default" %} + +{% block breadcrumbs %} + {{ Breadcrumbs.renderIfExists(Route.getCurrentRoute.getName, linkType) }} +{% endblock %} + +{% block content %} + + {{ Form.model(linkType, {'class' : 'form-horizontal','id' : 'update','url' : route('admin.links.update', linkType.id) } ) }} + + +
+
+
+
+

{{ 'mandatoryFields'|_ }}

+
+
+ {{ ExpandedForm.text('name', null, {helpText: trans('firefly.link_type_help_name')}) }} + {{ ExpandedForm.text('inward', null, {helpText: trans('firefly.link_type_help_inward')}) }} + {{ ExpandedForm.text('outward', null, {helpText: trans('firefly.link_type_help_outward')}) }} +
+
+
+
+ + +
+
+

{{ 'options'|_ }}

+
+
+ {{ ExpandedForm.optionsList('update','link_type') }} +
+ +
+ +
+ +
+ + + +{% endblock %} diff --git a/resources/views/admin/link/index.twig b/resources/views/admin/link/index.twig index f5ac34ba4a..4aafa5470c 100644 --- a/resources/views/admin/link/index.twig +++ b/resources/views/admin/link/index.twig @@ -10,8 +10,47 @@

{{ 'journal_link_configuration'|_ }}

-
- +
+ + + + + + + + + + + + {% for linkType in linkTypes %} + + + + + + + + {% endfor %} + +
{{ trans('list.name') }}{{ trans('list.number_of_transactions') }}
+ {% if linkType.editable %} +
+ + +
+ {% endif %} +
+ {{ linkType.name }} + + {{ linkType.inward }} + + {{ linkType.outward }} + + {{ linkType.journalCount }} +
+
+
diff --git a/resources/views/transactions/show.twig b/resources/views/transactions/show.twig index 92c9108472..59ba96b99b 100644 --- a/resources/views/transactions/show.twig +++ b/resources/views/transactions/show.twig @@ -383,30 +383,37 @@ {# link journal modal:#} {% endblock %} +{% block scripts %} + + + +{% endblock %} \ No newline at end of file diff --git a/routes/web.php b/routes/web.php index 9e380fb15b..6e25737e6b 100755 --- a/routes/web.php +++ b/routes/web.php @@ -443,6 +443,7 @@ Route::group( Route::get('box/bills-unpaid', ['uses' => 'JsonController@boxBillsUnpaid', 'as' => 'box.unpaid']); Route::get('box/bills-paid', ['uses' => 'JsonController@boxBillsPaid', 'as' => 'box.paid']); Route::get('transaction-journals/all', ['uses' => 'JsonController@allTransactionJournals', 'as' => 'all-transaction-journals']); + Route::get('transaction-journals/with-id/{tj}', ['uses' => 'Json\AutoCompleteController@journalsWithId', 'as' => 'journals-with-id']); Route::get('transaction-journals/{what}', ['uses' => 'JsonController@transactionJournals', 'as' => 'transaction-journals']); Route::get('transaction-types', ['uses' => 'JsonController@transactionTypes', 'as' => 'transaction-types']); Route::get('trigger', ['uses' => 'JsonController@trigger', 'as' => 'trigger']); @@ -724,7 +725,7 @@ Route::group( ); /** - * Convert Controller + * Transaction Convert Controller */ Route::group( ['middleware' => 'user-full-auth', 'namespace' => 'Transaction', 'prefix' => 'transactions/convert', 'as' => 'transactions.convert.'], function () { @@ -733,6 +734,15 @@ Route::group( } ); +/** + * Transaction Link Controller + */ +Route::group( + ['middleware' => 'user-full-auth', 'namespace' => 'Transaction', 'prefix' => 'transactions/link', 'as' => 'transactions.link.'], function () { + Route::post('store/{tj}', ['uses' => 'LinkController@store', 'as' => 'store']); +} +); + /** * Report Popup Controller */ @@ -760,6 +770,14 @@ Route::group( // journal links manager Route::get('links', ['uses' => 'LinkController@index', 'as' => 'links.index']); + Route::get('links/create', ['uses' => 'LinkController@create', 'as' => 'links.create']); + Route::get('links/edit/{linkType}', ['uses' => 'LinkController@edit', 'as' => 'links.edit']); + Route::get('links/delete/{linkType}', ['uses' => 'LinkController@delete', 'as' => 'links.delete']); + + + Route::post('links/store', ['uses' => 'LinkController@store', 'as' => 'links.store']); + Route::post('links/update/{linkType}', ['uses' => 'LinkController@update', 'as' => 'links.update']); + Route::post('links/destroy/{linkType}', ['uses' => 'LinkController@destroy', 'as' => 'links.destroy']); // FF configuration: Route::get('configuration', ['uses' => 'ConfigurationController@index', 'as' => 'configuration.index']);