Added categories and Filter widget

This commit is contained in:
Florian "flowdy" Heß 2017-01-29 15:57:34 +01:00
parent 6f19437901
commit d7db9a2eb1
23 changed files with 167 additions and 40 deletions

View File

@ -6,7 +6,7 @@ use Carp qw/croak/;
__PACKAGE__->load_classes(qw|
Account Debit Credit Transfer CurrentArrears AvailableCredits
Balance Report ReconstructedBankStatement History User
Category Balance Report ReconstructedBankStatement History User
|);
sub import {

View File

@ -8,6 +8,7 @@ __PACKAGE__->add_column("credId" => { data_type => 'INTEGER' });
__PACKAGE__->add_column("account");
__PACKAGE__->add_column("date" => { data_type => 'DATE' });
__PACKAGE__->add_column("purpose");
__PACKAGE__->add_column("category", { is_nullable => 1 });
__PACKAGE__->add_column("value" => { data_type => 'INTEGER' });
__PACKAGE__->add_column("spent" => { data_type => 'INTEGER', default => 0 });
__PACKAGE__->set_primary_key("credId");
@ -26,6 +27,11 @@ __PACKAGE__->has_many(
{ 'foreign.targetCredit' => 'self.credId' }
);
__PACKAGE__->belongs_to(
category_row => 'TrsrDB::Category',
{ 'foreign.ID' => 'self.category' }
);
__PACKAGE__->many_to_many(
paid_bills => 'outgoings' => 'debit'
);

View File

@ -8,6 +8,7 @@ __PACKAGE__->add_column("billId");
__PACKAGE__->add_column("debtor");
__PACKAGE__->add_column("date" => { data_type => 'DATE' });
__PACKAGE__->add_column("purpose");
__PACKAGE__->add_column("category", { is_nullable => 1 });
__PACKAGE__->add_column("value" => { data_type => 'INTEGER' });
__PACKAGE__->add_column("paid" => { data_type => 'INTEGER', default => 0 });
__PACKAGE__->set_primary_key("billId");
@ -26,6 +27,11 @@ __PACKAGE__->belongs_to(
{ 'foreign.credId' => 'self.targetCredit' }
);
__PACKAGE__->belongs_to(
category_row => 'TrsrDB::Category',
{ 'foreign.ID' => 'self.category' }
);
__PACKAGE__->has_many(
incomings => 'TrsrDB::Transfer', 'billId'
);

View File

@ -58,10 +58,7 @@ sub startup {
$self->helper(sql_trace => \&get_trace);
}
else {
$self->helper(sql_trace => sub {
return "No SQL trace shown here, because environment variable "
. "DBIC_TRACE is not set."
});
$self->helper(sql_trace => \&_get_trace_placeholder);
}
if ( my $l = $ENV{LOG} ) {
@ -90,7 +87,9 @@ sub startup {
})->get('/');
$check->get('/bankStatement' => sub {
my $c = shift;
$c->stash( records => $c->app->db->resultset("ReconstructedBankStatement") );
my $records = $c->app->db->resultset("ReconstructedBankStatement")
->search( process_table_filter_widget($c) );
$c->stash( records => $records );
$c->render('bankStatement');
});
@ -299,6 +298,50 @@ sub render_online_help {
}
sub process_table_filter_widget {
my $c = shift;
my %query = $_[0] ? %{$_[0]} : ();
my %options = $_[1] ? %{$_[1]} : ();
$options{rows} //= $c->param("rows") // 100;
if ( my $category = $c->param('category') ) {
$query{category} = $category;
}
if ( my $purpose = $c->param('purpose') ) {
$query{purpose} //= { -like => "%$purpose%" };
}
my $until;
if ( $until = $c->param('until') ) {
$query{date} = { '<=' => $until };
}
if ( my $from = $c->param('from') ) {
if ( $until ) {
$query{date}{'>='} = $from;
}
elsif ( $from =~ m{ \A \d{4} (-\d\d)? }xms ) {
$query{date} = { -like => "$from%" };
}
else {
$query{date} = { '>=' => $from };
}
}
if ( my $page = $c->param("page") ) {
$options{page} = $page;
}
return \%query, \%options;
}
sub _get_trace_placeholder { \<<'EOF'
In this area, the server can trace SQL commands executed to fulfill your request. If the admin wants that, they can run the server with environment variable DBIC_TRACE=1.
All substantial logic of Treasure DB is done in the scope of the database itself. In consequence, you could do without this HTTP interface and input all SQL commands shown here directly in a general-purpose SQLite3 user interface. Thus you will get roughly the same results.
EOF
}
1;
__END__

View File

@ -59,9 +59,17 @@ sub upsert {
sub history {
my $self = shift;
my $history = $self->app->db->resultset("History")->search({
account => $self->stash("account")
}, { order_by => { -desc => [qw/date/] } });
my %query = ( account => $self->stash("account") );
if ( my $p = $self->param("purpose") ) {
# ...
}
my $history = $self->app->db->resultset("History")->search(
TrsrDB::HTTP::process_table_filter_widget(
$self, \%query,
{ order_by => { -desc => [qw/date/] } }
)
);
$self->stash( history => $history );
}

View File

@ -20,7 +20,9 @@ sub list {
}
my $rs = $account->search_related(
credits => {}, { order_by => { -desc => [qw/date/] } }
credits => TrsrDB::HTTP::process_table_filter_widget(
$self, {}, { order_by => { -desc => [qw/date/] }}
)
);
$self->stash( credits => $rs );
@ -39,14 +41,20 @@ sub upsert {
account => $self->stash("account"),
date => strftime("%Y-%m-%d", localtime)
});
$self->stash( credit => $credit );
$self->stash(
credit => $credit,
categories => $db->resultset("Category")->search_rs({}, {
order_by => { -asc => [qw/ID/] }
})
);
if ( $self->req->method eq 'GET' ) {
return;
}
for my $field ( qw/account date purpose value/ ) {
for my $field ( qw/account date purpose category value/ ) {
my $value = $self->param($field);
$value = undef if !length $value;
$credit->$field($value);
}
$credit->update_or_insert();

View File

@ -20,7 +20,9 @@ sub list {
}
my $rs = $account->search_related(
debits => {}, { order_by => { -desc => [qw/date/] }}
debits => TrsrDB::HTTP::process_table_filter_widget(
$self, {}, { order_by => { -desc => [qw/date/] }}
)
);
$self->stash( debits => $rs );
@ -33,7 +35,7 @@ sub upsert {
my $db = $self->app->db;
my $id = $self->stash("id");
my @FIELDS = qw/billId date purpose value targetCredit/;
my @FIELDS = qw/billId date purpose category value targetCredit/;
my $debtor = $self->stash("account") // $self->param("debtor");
if ( $self->req->method eq 'POST' && $debtor =~ s{^@}{} ) {
@ -74,7 +76,14 @@ sub upsert {
}
);
my @targets = map { [ $_->credId, $_->account->ID, $_->purpose ] } $targets->all;
$self->stash( targets => \@targets, targets_count => $targets->count );
my $categories = $db->resultset("Category")->search_rs({}, {
order_by => { -asc => [qw/ID/] }
});
$self->stash(
categories => $categories,
targets => \@targets,
targets_count => $targets->count
);
return;
}

View File

@ -4,7 +4,7 @@ package TrsrDB::History;
use base qw/DBIx::Class::Core/;
__PACKAGE__->table('History');
__PACKAGE__->add_columns(qw/date purpose account credId credit debit contra billId note/);
__PACKAGE__->add_columns(qw/date purpose category account credId credit debit contra billId note/);
__PACKAGE__->belongs_to(
account => 'TrsrDB::Account',

View File

@ -4,6 +4,6 @@ package TrsrDB::ReconstructedBankStatement;
use base qw/DBIx::Class::Core/;
__PACKAGE__->table('ReconstructedBankStatement');
__PACKAGE__->add_columns(qw/date purpose account credit debit/);
__PACKAGE__->add_columns(qw/date purpose category account credit debit/);
1;

View File

@ -5,6 +5,12 @@ CREATE TABLE Account (
IBAN -- target account for returned payments (set '' to enable
-- outgoing bank transfers to commercial partners from that account).
);
CREATE TABLE Category (
ID INTEGER PRIMARY KEY,
label
);
CREATE TABLE Debit (
billId PRIMARY KEY NOT NULL,
debtor NOT NULL, -- Account charged
@ -13,10 +19,12 @@ CREATE TABLE Debit (
-- NULL when debit is a bank transfer from the club account
date DATE NOT NULL,
purpose NOT NULL, -- description of receipt
category,
value INTEGER NOT NULL, -- Euro-Cent
paid INTEGER DEFAULT 0, -- Euro-Cent, set and changed automatically (Cache)
FOREIGN KEY (debtor) REFERENCES Account(ID),
FOREIGN KEY (targetCredit) REFERENCES Credit(credId),
FOREIGN KEY (category) REFERENCES Category(ID),
CHECK ( abs(cast(value as integer)) == value
AND abs(cast( paid as integer)) == paid
AND value > 0 AND value >= paid
@ -28,11 +36,13 @@ CREATE TABLE Credit (
account NOT NULL, -- Account des Begünstigten
date DATE NOT NULL,
purpose NOT NULL, -- as originally indicated in statement of bank account
category,
value INTEGER NOT NULL, -- Euro-Cent. Caution, two distinct cases need to be considered:
-- Either deposit by bank transfer (>0) or target of internal payments (=0)
spent INTEGER DEFAULT 0, -- Euro-Cent, set and changed automatically (Cache)
-- for later traceability, necessary when revoking transfers
FOREIGN KEY (account) REFERENCES Account(ID),
FOREIGN KEY (category) REFERENCES Category(ID),
CHECK ( abs(cast(value as integer)) == value
AND abs(cast(spent as integer)) == spent
AND value >= spent

View File

@ -4,6 +4,7 @@ CREATE VIEW History AS
-- internal transfers with account as source
SELECT DATE(timestamp) AS date,
d.purpose AS purpose,
d.category AS category,
d.debtor AS account,
t.credId AS credId,
t.amount AS debit,
@ -17,6 +18,7 @@ CREATE VIEW History AS
UNION
SELECT DATE(timestamp) AS date,
d.purpose AS purpose,
d.category AS category,
c.account AS account,
d.targetCredit AS credId,
NULL AS debit,

View File

@ -2,6 +2,7 @@ DROP VIEW IF EXISTS ReconstructedBankStatement;
CREATE VIEW ReconstructedBankStatement AS
SELECT c.date AS date,
c.purpose AS purpose,
c.category AS category,
account,
c.value AS credit,
NULL AS debit
@ -12,6 +13,7 @@ CREATE VIEW ReconstructedBankStatement AS
UNION
SELECT date,
purpose,
category AS category,
debtor AS account,
NULL AS credit,
value AS debit

View File

@ -9,9 +9,9 @@ john: 0 +0 0
Club: 0 +0 -16250
alex: 7200 +16250 0
# Some updates and deletes that could, unless denied, destroy consistency ...
Error: near line 41: paid is set and adjusted automatically according to added Transfer records
Error: near line 42: Debt is involved in transfers to revoke at first
Error: near line 43: FOREIGN KEY constraint failed
Error: near line 42: paid is set and adjusted automatically according to added Transfer records
Error: near line 43: Debt is involved in transfers to revoke at first
Error: near line 44: FOREIGN KEY constraint failed
# After revoking transactions, you are free to change or delete debts and credits ...
Club: 7200 +0 0
alex: 0 +0 0

View File

@ -4,24 +4,25 @@ PRAGMA recursive_triggers = ON;
-- To understand the sql below, see the schema.sql file.
INSERT INTO Account VALUES ("Club", "eV", 1, NULL), ("john", "Member", 44, NULL), ("alex", "Member", 6, "DE1234567890123456");
INSERT INTO Category ("label") VALUES ("Membership fees"), ("Service");
INSERT INTO Credit VALUES (1, "Club", "2016-01-01", "Membership fees May 2016 until incl. April 2017", 0, 0),
(2, "john", "2016-04-23", "Membership fee 2016f.", 7200, 0),
(3, "alex", "2016-01-15", "Payment for Server Hosting 2016", 0, 0);
INSERT INTO Credit VALUES (1, "Club", "2016-01-01", "Membership fees May 2016 until incl. April 2017", 1, 0, 0),
(2, "john", "2016-04-23", "Membership fee 2016f.", 1, 7200, 0),
(3, "alex", "2016-01-15", "Payment for Server Hosting 2016", 1, 0, 0);
INSERT INTO Debit VALUES ("MB1605-john", "john", 1, "2016-05-01", "Membership fee May 2016", 600, 0),
("MB1606-john", "john", 1, "2016-05-01", "Membership fee June 2016", 600, 0),
("MB1607-john", "john", 1, "2016-05-01", "Membership fee July 2016", 600, 0),
("MB1608-john", "john", 1, "2016-05-01", "Membership fee August 2016", 600, 0),
("MB1609-john", "john", 1, "2016-05-01", "Membership fee September 2016", 600, 0),
("MB1610-john", "john", 1, "2016-05-01", "Membership fee October 2016", 600, 0),
("MB1611-john", "john", 1, "2016-05-01", "Membership fee November 2016", 600, 0),
("MB1612-john", "john", 1, "2016-05-01", "Membership fee December 2016", 600, 0),
("MB1701-john", "john", 1, "2016-05-01", "Membership fee January 2017", 600, 0),
("MB1702-john", "john", 1, "2016-05-01", "Membership fee February 2017", 600, 0),
("MB1703-john", "john", 1, "2016-05-01", "Membership fee March 2017", 600, 0),
("MB1704-john", "john", 1, "2016-05-01", "Membership fee April 2017", 600, 0),
("TWX2016/123", "Club", 3, "2016-01-15", "Server Hosting 2016", 23450, 0);
INSERT INTO Debit VALUES ("MB1605-john", "john", 1, "2016-05-01", "Membership fee May 2016", 1, 600, 0),
("MB1606-john", "john", 1, "2016-05-01", "Membership fee June 2016", 1, 600, 0),
("MB1607-john", "john", 1, "2016-05-01", "Membership fee July 2016", 1, 600, 0),
("MB1608-john", "john", 1, "2016-05-01", "Membership fee August 2016", 1, 600, 0),
("MB1609-john", "john", 1, "2016-05-01", "Membership fee September 2016", 1, 600, 0),
("MB1610-john", "john", 1, "2016-05-01", "Membership fee October 2016", 1, 600, 0),
("MB1611-john", "john", 1, "2016-05-01", "Membership fee November 2016", 1, 600, 0),
("MB1612-john", "john", 1, "2016-05-01", "Membership fee December 2016", 1, 600, 0),
("MB1701-john", "john", 1, "2016-05-01", "Membership fee January 2017", 1, 600, 0),
("MB1702-john", "john", 1, "2016-05-01", "Membership fee February 2017", 1, 600, 0),
("MB1703-john", "john", 1, "2016-05-01", "Membership fee March 2017", 1, 600, 0),
("MB1704-john", "john", 1, "2016-05-01", "Membership fee April 2017", 1, 600, 0),
("TWX2016/123", "Club", 3, "2016-01-15", "Server Hosting 2016", 2, 23450, 0);
.separator " "
SELECT "ID: availbl promise debt";

View File

@ -14,3 +14,5 @@
</td><td class="number"><%== money $hr->debit %></td><td class="number"><%== money $hr->credit %></td><td><%= $hr->note %></td></tr>
% }
</table>
%= include 'filter-widget'

View File

@ -7,7 +7,7 @@
% my $inter_header = begin
% my $group = shift;
<tr><th colspan="11"><%= $group || 'Club management accounts' %></th>
<th colspan="2"><a href="/debit?group=<%= $group %>"><img class="icon" src="/add-debit.svg" alt="Charge">all</a></th></tr>
<th colspan="2"><a href="/debit?group=<%= $group %>"><img class="icon" src="/add-debit.svg" alt="Charge"> all at once</a></th></tr>
% end
% while ( my $account = $accounts->next ) {
% my $bal = $account->balance;

View File

@ -1,12 +1,12 @@
% title 'Make transfers for ' . $account;
<p>Check at least one item of each table that belong together.</p>
<p>Check at least one item of both tables. Make sure they correspond in regard to their purposes. <em><strong>Caution:</strong> When you do not check any items in either table, this effectively is to check all in it!</em></p>
<form method="post">
<h2>Available credits</h2>
<p>Check credits you want to <strong>spend for the arrear(s) below</strong> with.</p>
<p>Check credits you want to <strong>spend for the arrear(s) below</strong>:</p>
<table>
<tr><th>?</th><th>date</th><th>purpose</th><th>to spend</th></tr>

View File

@ -10,3 +10,5 @@
% }
<tr><td colspan="3" style="text-align:right;">Current balance:</td><td class="number"><%== $total_debit ? money $total_debit : '' %></td><td class="number"><%== $total_credit ? money $total_credit : '' %></td>
</table>
%= include 'filter-widget'

View File

@ -8,3 +8,5 @@
<tr><td class="number"><a href="/credit/<%= $credit->credId %>"><%= $credit->credId %></a></td><td><%= $credit->date %></td><td><%== nl2br $credit->purpose %></td><td class="number"><%== money $credit->value %></td><td class="number <%= $credit->spent < $credit->value ? "mark" : "" %>"><%== money $credit->spent %></td></tr>
% }
</table>
%= include 'filter-widget'

View File

@ -11,6 +11,17 @@
% $r{purpose} = begin
<input id="purpose" name="purpose" style="width:100%" value="<%= $credit->purpose %>">
% end
% $r{category} = begin
% my $category = $credit->category // '';
<select id="category" name="category">
<option value="">-- Please select --</option>
% while ( my $c = $categories->next ) {
<option value="<%= $c->ID %>" <%== $category == $c->ID ? 'selected="selected"' :'' %>>
<%= $c->label %>
</option>
% }
</select>
% end
% $r{value} = begin
<input id="value" name="value" value="<%= $credit->value %>"> Cent (declare target credit with "0")
% end

View File

@ -9,3 +9,5 @@
<tr><td><a href="/debit/<%= $debit->billId %>"><%= $debit->billId %></a></td><td><%= $debit->date %></td><td><%== nl2br $debit->purpose %></td><td class="number"><%== money $debit->value %></td><td class="number <%= $debit->paid < $debit->value ? "mark" : "" %>"><%== money $debit->paid %><td><a href="/credit/<%= $tgt->credId %>"><%= $tgt->account->ID %></a></tr>
% }
</table>
%= include 'filter-widget'

View File

@ -14,6 +14,17 @@
% $r{purpose} = begin
<input id="purpose" name="purpose" style="width:100%" value="<%= $debit->purpose %>">
% end
% $r{category} = begin
% my $category = $debit->category // '';
<select id="category" name="category">
<option value="">-- Please select --</option>
% while ( my $c = $categories->next ) {
<option value="<%= $c->ID %>" <%== $category == $c->ID ? 'selected="selected"' :'' %>>
<%= $c->label %>
</option>
% }
</select>
% end
% $r{value} = begin
<input id="value" name="value" value="<%= $debit->value %>"> Cent
% end
@ -42,10 +53,12 @@ oops
</optgroup>
</select>
% end
% if ( !stash 'account' ) {
% $r{debtor} = begin
% my $g = param 'group';
<input id="debtor" name="debtor" value="<%= $g ? "\@$g" : $debit->debtor %>">
% end
% }
<dl class="upsert">
% for my $f ( $debit->result_source->columns ) {

View File

@ -39,7 +39,7 @@
% }
% if ( defined $sql ) {
<div class="targettable" id="sql">
<p>In case you are interested, this is what is executed in the database in order to process your request and to render this page. All essential logic and consistency checking is done on database level via triggers and views, so you could execute these SQL statements e.g. in the console and would get the same results. Note that, if any POST requests redirected here, SQL commands of those come first:</p>
<p>Please note that, to run the SQL commands directly in another tool, you might need to resolve the prepared statements manually. Just replace all question marks by the values listed after the colon in each line.</p>
<textarea rows="5" style="width:100%;border:1px inset lightgrey;" readonly="readonly">
%== $$sql
</textarea></div>