Can sort and group bills.

This commit is contained in:
James Cole 2020-07-01 06:33:21 +02:00
parent 029774687c
commit e337bcf8bd
11 changed files with 202 additions and 25 deletions

View File

@ -32,6 +32,8 @@ use FireflyIII\Models\TransactionCurrency;
use FireflyIII\Repositories\Bill\BillRepositoryInterface; use FireflyIII\Repositories\Bill\BillRepositoryInterface;
use FireflyIII\Repositories\ObjectGroup\OrganisesObjectGroups; use FireflyIII\Repositories\ObjectGroup\OrganisesObjectGroups;
use FireflyIII\Transformers\BillTransformer; use FireflyIII\Transformers\BillTransformer;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\ParameterBag; use Symfony\Component\HttpFoundation\ParameterBag;
/** /**
@ -174,4 +176,27 @@ class IndexController extends Controller
return $sums; return $sums;
} }
/**
* Set the order of a bill.
*
* @param Request $request
* @param Bill $bill
*
* @return JsonResponse
*/
public function setOrder(Request $request, Bill $bill): JsonResponse
{
$objectGroupTitle = (string)$request->get('objectGroupTitle');
$newOrder = (int) $request->get('order');
$this->repository->setOrder($bill, $newOrder);
if ('' !== $objectGroupTitle) {
$this->repository->setObjectGroup($bill, $objectGroupTitle);
}
if ('' === $objectGroupTitle) {
$this->repository->removeObjectGroup($bill);
}
return response()->json(['data' => 'OK']);
}
} }

View File

@ -195,7 +195,7 @@ class IndexController extends Controller
*/ */
public function setOrder(Request $request, PiggyBank $piggyBank): JsonResponse public function setOrder(Request $request, PiggyBank $piggyBank): JsonResponse
{ {
$objectGroupTitle = $request->get('objectGroupTitle'); $objectGroupTitle = (string) $request->get('objectGroupTitle');
$newOrder = (int) $request->get('order'); $newOrder = (int) $request->get('order');
$this->piggyRepos->setOrder($piggyBank, $newOrder); $this->piggyRepos->setOrder($piggyBank, $newOrder);
if ('' !== $objectGroupTitle) { if ('' !== $objectGroupTitle) {

View File

@ -32,6 +32,7 @@ use FireflyIII\Models\Note;
use FireflyIII\Models\Transaction; use FireflyIII\Models\Transaction;
use FireflyIII\Models\TransactionJournal; use FireflyIII\Models\TransactionJournal;
use FireflyIII\Repositories\Journal\JournalRepositoryInterface; use FireflyIII\Repositories\Journal\JournalRepositoryInterface;
use FireflyIII\Repositories\ObjectGroup\CreatesObjectGroups;
use FireflyIII\Services\Internal\Destroy\BillDestroyService; use FireflyIII\Services\Internal\Destroy\BillDestroyService;
use FireflyIII\Services\Internal\Update\BillUpdateService; use FireflyIII\Services\Internal\Update\BillUpdateService;
use FireflyIII\Support\CacheProperties; use FireflyIII\Support\CacheProperties;
@ -48,6 +49,7 @@ use Storage;
*/ */
class BillRepository implements BillRepositoryInterface class BillRepository implements BillRepositoryInterface
{ {
use CreatesObjectGroups;
/** @var User */ /** @var User */
private $user; private $user;
@ -730,4 +732,36 @@ class BillRepository implements BillRepositoryInterface
$current++; $current++;
} }
} }
/**
* @inheritDoc
*/
public function setObjectGroup(Bill $bill, string $objectGroupTitle): Bill
{
$objectGroup = $this->findOrCreateObjectGroup($objectGroupTitle);
if (null !== $objectGroup) {
$bill->objectGroups()->sync([$objectGroup->id]);
}
return $bill;
}
/**
* @inheritDoc
*/
public function removeObjectGroup(Bill $bill): Bill
{
$bill->objectGroups()->sync([]);
return $bill;
}
/**
* @inheritDoc
*/
public function setOrder(Bill $bill, int $order): void
{
$bill->order = $order;
$bill->save();
}
} }

View File

@ -35,6 +35,21 @@ use Illuminate\Support\Collection;
interface BillRepositoryInterface interface BillRepositoryInterface
{ {
/**
* @param Bill $bill
* @param string $objectGroupTitle
*
* @return Bill
*/
public function setObjectGroup(Bill $bill, string $objectGroupTitle): Bill;
/**
* @param Bill $bill
*
* @return Bill
*/
public function removeObjectGroup(Bill $bill): Bill;
/** /**
* @param Bill $bill * @param Bill $bill
*/ */
@ -45,6 +60,14 @@ interface BillRepositoryInterface
*/ */
public function correctOrder(): void; public function correctOrder(): void;
/**
* Set specific piggy bank to specific order.
*
* @param Bill $bill
* @param int $order
*/
public function setOrder(Bill $bill, int $order): void;
/** /**
* @param Bill $bill * @param Bill $bill
* *

View File

@ -89,12 +89,13 @@ class BillTransformer extends AbstractTransformer
'currency_symbol' => $currency->symbol, 'currency_symbol' => $currency->symbol,
'currency_decimal_places' => $currency->decimal_places, 'currency_decimal_places' => $currency->decimal_places,
'name' => $bill->name, 'name' => $bill->name,
'amount_min' => round((float)$bill->amount_min, $currency->decimal_places), 'amount_min' => round((float) $bill->amount_min, $currency->decimal_places),
'amount_max' => round((float)$bill->amount_max, $currency->decimal_places), 'amount_max' => round((float) $bill->amount_max, $currency->decimal_places),
'date' => $bill->date->format('Y-m-d'), 'date' => $bill->date->format('Y-m-d'),
'repeat_freq' => $bill->repeat_freq, 'repeat_freq' => $bill->repeat_freq,
'skip' => (int)$bill->skip, 'skip' => (int) $bill->skip,
'active' => $bill->active, 'active' => $bill->active,
'order' => (int) $bill->order,
'notes' => $notes, 'notes' => $notes,
'next_expected_match' => $paidData['next_expected_match'], 'next_expected_match' => $paidData['next_expected_match'],
'pay_dates' => $payDates, 'pay_dates' => $payDates,

30
composer.lock generated
View File

@ -1414,16 +1414,16 @@
}, },
{ {
"name": "laravel/framework", "name": "laravel/framework",
"version": "v7.17.2", "version": "v7.18.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/laravel/framework.git", "url": "https://github.com/laravel/framework.git",
"reference": "d16ff3a0a66d98e04163456b39c4b7302cf50a40" "reference": "116b508bafd81de97b1c09744445d32a91076b60"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/laravel/framework/zipball/d16ff3a0a66d98e04163456b39c4b7302cf50a40", "url": "https://api.github.com/repos/laravel/framework/zipball/116b508bafd81de97b1c09744445d32a91076b60",
"reference": "d16ff3a0a66d98e04163456b39c4b7302cf50a40", "reference": "116b508bafd81de97b1c09744445d32a91076b60",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1567,7 +1567,7 @@
"framework", "framework",
"laravel" "laravel"
], ],
"time": "2020-06-24T13:11:25+00:00" "time": "2020-06-30T13:52:36+00:00"
}, },
{ {
"name": "laravel/passport", "name": "laravel/passport",
@ -1644,16 +1644,16 @@
}, },
{ {
"name": "laravel/ui", "name": "laravel/ui",
"version": "v2.0.3", "version": "v2.1.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/laravel/ui.git", "url": "https://github.com/laravel/ui.git",
"reference": "15368c5328efb7ce94f35ca750acde9b496ab1b1" "reference": "da9350533d0da60d5dc42fb7de9c561c72129bba"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/laravel/ui/zipball/15368c5328efb7ce94f35ca750acde9b496ab1b1", "url": "https://api.github.com/repos/laravel/ui/zipball/da9350533d0da60d5dc42fb7de9c561c72129bba",
"reference": "15368c5328efb7ce94f35ca750acde9b496ab1b1", "reference": "da9350533d0da60d5dc42fb7de9c561c72129bba",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -1695,7 +1695,7 @@
"laravel", "laravel",
"ui" "ui"
], ],
"time": "2020-04-29T15:06:45+00:00" "time": "2020-06-30T20:56:33+00:00"
}, },
{ {
"name": "laravelcollective/html", "name": "laravelcollective/html",
@ -7443,16 +7443,16 @@
}, },
{ {
"name": "myclabs/deep-copy", "name": "myclabs/deep-copy",
"version": "1.10.0", "version": "1.10.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/myclabs/DeepCopy.git", "url": "https://github.com/myclabs/DeepCopy.git",
"reference": "5796d127b0c4ff505b77455148ea9d5269d99758" "reference": "969b211f9a51aa1f6c01d1d2aef56d3bd91598e5"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/5796d127b0c4ff505b77455148ea9d5269d99758", "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/969b211f9a51aa1f6c01d1d2aef56d3bd91598e5",
"reference": "5796d127b0c4ff505b77455148ea9d5269d99758", "reference": "969b211f9a51aa1f6c01d1d2aef56d3bd91598e5",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -7493,7 +7493,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2020-06-28T07:02:41+00:00" "time": "2020-06-29T13:22:24+00:00"
}, },
{ {
"name": "netresearch/jsonmapper", "name": "netresearch/jsonmapper",

View File

@ -93,6 +93,9 @@ p.tagcloud .label {
.rule-handle { .rule-handle {
cursor: move; cursor: move;
} }
.bill-handle {
cursor: move;
}
body.waiting * { body.waiting * {
cursor: progress; cursor: progress;

91
public/v1/js/ff/bills/index.js vendored Normal file
View File

@ -0,0 +1,91 @@
/*
* index.js
* Copyright (c) 2019 james@firefly-iii.org
*
* This file is part of Firefly III (https://github.com/firefly-iii).
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
/** global: token */
var fixBillHelper = function (e, tr) {
"use strict";
var $originals = tr.children();
var $helper = tr.clone();
$helper.children().each(function (index) {
// Set helper cell sizes to match the original sizes
$(this).width($originals.eq(index).width());
});
return $helper;
};
$(function () {
"use strict";
$('#bill-sortable').find('tbody').sortable(
{
helper: fixBillHelper,
stop: stopSorting,
connectWith: '.bill-connected-list',
items: 'tr.bill-sortable',
handle: '.bill-handle',
start: function (event, ui) {
// Build a placeholder cell that spans all the cells in the row
var cellCount = 0;
$('td, th', ui.helper).each(function () {
// For each TD or TH try and get it's colspan attribute, and add that or 1 to the total
var colspan = 1;
var colspanAttr = $(this).attr('colspan');
if (colspanAttr > 1) {
colspan = colspanAttr;
}
cellCount += colspan;
});
// Add the placeholder UI - note that this is the item's content, so TD rather than TR
ui.placeholder.html('<td colspan="' + cellCount + '">&nbsp;</td>');
}
}
);
});
function stopSorting() {
"use strict";
$.each($('#bill-sortable>tbody>tr.bill-sortable'), function (i, v) {
var holder = $(v);
var parentBody = holder.parent();
var objectGroupTitle = parentBody.data('title');
var position = parseInt(holder.data('position'));
var originalOrder = parseInt(holder.data('order'));
var name = holder.data('name');
var id = parseInt(holder.data('id'));
var newOrder;
if (position === i) {
// not changed, position is what it should be.
return;
}
if (position < i) {
// position is less.
console.log('"' + name + '" ("' + objectGroupTitle + '") has moved down from position ' + originalOrder + ' to ' + (i + 1));
}
if (position > i) {
console.log('"' + name + '" ("' + objectGroupTitle + '") has moved up from position ' + originalOrder + ' to ' + (i + 1));
}
// update position:
holder.data('position', i);
newOrder = i+1;
$.post('bills/set-order/' + id, {order: newOrder, objectGroupTitle: objectGroupTitle, _token: token})
});
}

View File

@ -41,8 +41,6 @@
{% endblock %} {% endblock %}
{% block scripts %} {% block scripts %}
<script type="text/javascript" nonce="{{ JS_NONCE }}"> <script src="v1/js/lib/jquery-ui.min.js?v={{ FF_VERSION }}" type="text/javascript" nonce="{{ JS_NONCE }}"></script>
var start = '2018-01-01'; <script type="text/javascript" src="v1/js/ff/bills/index.js?v={{ FF_VERSION }}" nonce="{{ JS_NONCE }}"></script>
var end = '2018-01-31';
</script>
{% endblock %} {% endblock %}

View File

@ -1,7 +1,7 @@
<div style="padding-left:8px;"> <div style="padding-left:8px;">
{{ paginator.render|raw }} {{ paginator.render|raw }}
</div> </div>
<table class="table table-hover"> <table class="table table-hover" id="bill-sortable">
<thead> <thead>
<tr> <tr>
<th class="hidden-sm hidden-xs">&nbsp;</th> <th class="hidden-sm hidden-xs">&nbsp;</th>
@ -23,7 +23,7 @@
<td colspan="6"><small>{{ objectGroup.object_group_title }}</small></td> <td colspan="6"><small>{{ objectGroup.object_group_title }}</small></td>
</tr> </tr>
{% for entry in objectGroup.bills %} {% for entry in objectGroup.bills %}
<tr{% if not entry.active %} data-disablesort="true"{% endif %}> <tr class="bill-sortable" data-id="{{ entry.id }}" data-name="{{ entry.name }}" data-order="{{ entry.order }}" data-position="{{ loop.index0 }}">
<td class="hidden-sm hidden-xs"> <td class="hidden-sm hidden-xs">
<i class="fa fa-fw fa-bars bill-handle"></i> <i class="fa fa-fw fa-bars bill-handle"></i>
</td> </td>

View File

@ -194,6 +194,8 @@ Route::group(
Route::post('store', ['uses' => 'Bill\CreateController@store', 'as' => 'store']); Route::post('store', ['uses' => 'Bill\CreateController@store', 'as' => 'store']);
Route::post('update/{bill}', ['uses' => 'Bill\EditController@update', 'as' => 'update']); Route::post('update/{bill}', ['uses' => 'Bill\EditController@update', 'as' => 'update']);
Route::post('destroy/{bill}', ['uses' => 'Bill\DeleteController@destroy', 'as' => 'destroy']); Route::post('destroy/{bill}', ['uses' => 'Bill\DeleteController@destroy', 'as' => 'destroy']);
Route::post('set-order/{bill}', ['uses' => 'Bill\IndexController@setOrder', 'as' => 'set-order']);
} }
); );