Various changes and fixes: Added report, etc. Tests pass.

* reworked and used TrsrDB::expand_ids more tightly
  * account item selection in TrsrDB::make_transfers
  * web_auth / User relation for coming HTTP interface
  * income and target inter-relations between TrsrDB::Credit and TrsrDB::Debit
  * Transfer note
  * reactivated enforceFixedCredit to take effect unless _temp table is empty
  * added CreditsInFocus and Report views
This commit is contained in:
Florian "flowdy" Heß 2017-01-14 09:50:16 +01:00
parent 440453e4e6
commit 3773c3f123
7 changed files with 111 additions and 30 deletions

View File

@ -6,7 +6,7 @@ use Carp qw/croak/;
__PACKAGE__->load_classes(qw| __PACKAGE__->load_classes(qw|
Account Debit Credit Transfer CurrentArrears AvailableCredits Account Debit Credit Transfer CurrentArrears AvailableCredits
Balance ReconstructedBankStatement History Balance Report ReconstructedBankStatement History User
|); |);
sub import { sub import {
@ -41,15 +41,15 @@ sub make_transfers {
while ( my ($src, $tgt) = splice @pairs, 0, 2 ) { while ( my ($src, $tgt) = splice @pairs, 0, 2 ) {
$rs = $self->resultset("AvailableCredits")->search({ $rs = $self->resultset("AvailableCredits")->search(
credId => [ -or => _expand_ids($src) ] expand_ids($src => 'credId' )
}); );
$src = [ $rs->get_column('credId')->all ]; $src = [ $rs->get_column('credId')->all ];
$src_total += $rs->get_column('difference')->sum; $src_total += $rs->get_column('difference')->sum;
$rs = $self->resultset("CurrentArrears")->search({ $rs = $self->resultset("CurrentArrears")->search(
billId => [ -or => _expand_ids( $tgt ) ] expand_ids( $tgt => 'billId' )
}); );
$tgt = [ $rs->get_column('billId')->all ]; $tgt = [ $rs->get_column('billId')->all ];
$tgt_total += $rs->get_column('difference')->sum; $tgt_total += $rs->get_column('difference')->sum;
@ -99,31 +99,42 @@ sub make_transfers {
} }
sub _expand_ids { sub expand_ids {
my ($ids) = @_; my ($ids, $default_slot) = @_;
my @ids = map { m{ \A (\d+) - (\d+) \z }xms ? [ $1 .. $2 ] : $_ } my @ids = map { m{ \A (\d+) - (\d+) \z }xms ? [ $1 .. $2 ] : $_ }
ref $ids ? @$ids : split q{,}, $ids ref $ids ? @$ids : split q{,}, $ids
; ;
my (@alternatives, @raws); my (@alternatives, %raws);
for my $id ( @ids ) { for my $id ( @ids ) {
my $slot = ref $id ? $default_slot
: $id =~ s{^p(urpose)?:}{}i ? "purpose"
: $id =~ s{^d(ate)?:}{}i ? "date"
: $id =~ s{^v(alue)?:}{}i ? "value"
: $default_slot
;
if ( ref $id eq 'ARRAY' ) { if ( ref $id eq 'ARRAY' ) {
push @raws, @$ids; $raws{$slot}{'-in'}, @$ids;
} }
elsif ( $id eq '*' ) { elsif ( $id eq '*' ) {
@alternatives = ({ -not_in => [] }); @alternatives = ( $slot => { -not_in => [] });
} }
elsif ( elsif (
$id =~ s{([%*_?])}{ $id =~ s{([%*_?])}{
$1 eq '*' ? '%' : $1 eq '?' ? '_' : $1 $1 eq '*' ? '%' : $1 eq '?' ? '_' : $1
}eg }eg
) { ) {
push @alternatives, { -like => $id }; push @alternatives, { $slot => { -like => $id } };
}
else {
push @raws, $id;
} }
else { push @{ $raws{$slot}{'-like'} }, $id }
} }
push @alternatives, @raws ? { -in => \@raws } : (); while ( my @v = each %raws ) { push @alternatives, { @v } }
return \@alternatives; return \@alternatives;
} }
sub user {
shift->resultset("User")->find(shift);
}
1; 1;

View File

@ -34,5 +34,9 @@ __PACKAGE__->has_many(
history => 'TrsrDB::History', history => 'TrsrDB::History',
{ 'foreign.account' => 'self.ID' } { 'foreign.account' => 'self.ID' }
); );
__PACKAGE__->has_many(
report => 'TrsrDB::Report',
{ 'foreign.account' => 'self.ID' }
);
1; 1;

View File

@ -19,7 +19,12 @@ __PACKAGE__->belongs_to(
__PACKAGE__->has_many( __PACKAGE__->has_many(
outgoings => 'TrsrDB::Transfer', outgoings => 'TrsrDB::Transfer',
{ 'foreign.fromCredit' => 'self.Id' } { 'foreign.fromCredit' => 'self.credId' }
);
__PACKAGE__->has_many(
income => 'TrsrDB::Debit',
{ 'foreign.targetCredit' => 'self.credId' }
); );
__PACKAGE__->many_to_many( __PACKAGE__->many_to_many(

View File

@ -18,6 +18,11 @@ __PACKAGE__->belongs_to(
{ 'foreign.ID' => 'self.debtor' } { 'foreign.ID' => 'self.debtor' }
); );
__PACKAGE__->might_have(
target => 'TrsrDB::Credit',
{ 'foreign.credId' => 'self.targetCredit' }
);
__PACKAGE__->has_many( __PACKAGE__->has_many(
incomings => 'TrsrDB::Transfer', 'billId' incomings => 'TrsrDB::Transfer', 'billId'
); );

View File

@ -8,6 +8,7 @@ __PACKAGE__->add_column("timestamp" => { data_type => 'TIMESTAMP' });
__PACKAGE__->add_column("billId"); __PACKAGE__->add_column("billId");
__PACKAGE__->add_column("credId" => { data_type => 'INTEGER' }); __PACKAGE__->add_column("credId" => { data_type => 'INTEGER' });
__PACKAGE__->add_column("amount" => { data_type => 'INTEGER', nullable => 1 }); __PACKAGE__->add_column("amount" => { data_type => 'INTEGER', nullable => 1 });
__PACKAGE__->add_column("note" => { nullable => 1 });
__PACKAGE__->set_primary_key("billId", "credId"); __PACKAGE__->set_primary_key("billId", "credId");
__PACKAGE__->belongs_to( __PACKAGE__->belongs_to(

View File

@ -43,12 +43,18 @@ CREATE TABLE Transfer (
billId INTEGER NOT NULL, billId INTEGER NOT NULL,
credId INTEGER NOT NULL, credId INTEGER NOT NULL,
amount INTEGER, -- for later traceability, necessary when revoking transfers amount INTEGER, -- for later traceability, necessary when revoking transfers
note,
FOREIGN KEY (billId) REFERENCES Debit(billId), FOREIGN KEY (billId) REFERENCES Debit(billId),
FOREIGN KEY (credId) REFERENCES Credit(credId), FOREIGN KEY (credId) REFERENCES Credit(credId),
UNIQUE (billId, credId) UNIQUE (billId, credId)
); );
CREATE TABLE IF NOT EXISTS _temp (d, c, m); -- For internal purposes: Memory of rebalance triggers
CREATE TABLE _temp (d, c, m);
-- Only for use of HTTP interface
CREATE TABLE web_auth ( user_id primary key, password, grade not null, username, email );
CREATE TRIGGER balanceTransfer CREATE TRIGGER balanceTransfer
AFTER INSERT ON Transfer AFTER INSERT ON Transfer
BEGIN BEGIN
@ -149,7 +155,7 @@ BEGIN
SELECT RAISE(FAIL, "Transfer cannot be updated, but needs to be replaced to make triggers run"); SELECT RAISE(FAIL, "Transfer cannot be updated, but needs to be replaced to make triggers run");
END; END;
CREATE TRIGGER enforceiZeroPaidAtStart CREATE TRIGGER enforceZeroPaidAtStart
BEFORE INSERT ON Debit BEFORE INSERT ON Debit
BEGIN BEGIN
SELECT RAISE(FAIL, "Debt must be initially unpaid") SELECT RAISE(FAIL, "Debt must be initially unpaid")
@ -226,8 +232,8 @@ BEGIN
END; END;
CREATE TRIGGER enforceFixedDebits CREATE TRIGGER enforceFixedDebit
BEFORE UPDATE OF value ON Debit BEFORE UPDATE OF debtor, transferCredit, value ON Debit
WHEN EXISTS (SELECT * FROM Transfer WHERE billId=NEW.billId) WHEN EXISTS (SELECT * FROM Transfer WHERE billId=NEW.billId)
BEGIN BEGIN
SELECT RAISE(FAIL, "Debt is involved in transfers to revoke at first"); SELECT RAISE(FAIL, "Debt is involved in transfers to revoke at first");
@ -250,12 +256,13 @@ BEGIN
WHERE (NEW.spent + IFNULL((SELECT m FROM _temp WHERE c IS NULL AND d IS NULL),0) ) <> OLD.spent; WHERE (NEW.spent + IFNULL((SELECT m FROM _temp WHERE c IS NULL AND d IS NULL),0) ) <> OLD.spent;
END; END;
-- CREATE TRIGGER enforceFixedCredit CREATE TRIGGER enforceFixedCredit
-- BEFORE UPDATE OF value ON Credit BEFORE UPDATE OF account, value ON Credit
-- BEGIN WHEN NOT EXISTS (SELECT * FROM _temp)
-- SELECT RAISE(FAIL, "Credit involved in transactions to revoke at first") BEGIN
-- WHERE EXISTS (SELECT * FROM Transfer WHERE credId=NEW.credId); SELECT RAISE(FAIL, "Credit involved in transactions to revoke at first")
-- END; WHERE EXISTS (SELECT * FROM Transfer WHERE credId=NEW.credId);
END;
CREATE TRIGGER checkIBANatTransfer CREATE TRIGGER checkIBANatTransfer
BEFORE INSERT ON Debit BEFORE INSERT ON Debit
@ -367,7 +374,7 @@ CREATE VIEW ReconstructedBankStatement AS
c.value AS credit, c.value AS credit,
NULL AS debit NULL AS debit
FROM Credit AS c FROM Credit AS c
LEFT OUTER JOIN Debit AS d ON c.credId=d.targetCredit LEFT OUTER JOIN Debit AS d ON c.credId = d.targetCredit
GROUP BY c.credId GROUP BY c.credId
HAVING count(d.billId) == 0 -- exclude internal transfers HAVING count(d.billId) == 0 -- exclude internal transfers
UNION UNION
@ -380,3 +387,51 @@ CREATE VIEW ReconstructedBankStatement AS
WHERE targetCredit IS NULL -- exclude internal transfers WHERE targetCredit IS NULL -- exclude internal transfers
ORDER BY date ASC ORDER BY date ASC
; ;
-- Credits that have not been used yet and any subsequent ones
CREATE VIEW CreditsInFocus AS
SELECT account, date, credId, value, purpose
FROM Credit
WHERE value > spent
UNION
SELECT c.account, date, credId, value, purpose
FROM Credit c
JOIN Balance b ON b.ID = c.account
WHERE c.date >= b.even_until
GROUP BY c.credId
;
-- Report view may be of use in communication with club members who are due
-- of outstanding fees, listing what they have paid and what is yet to pay.
CREATE VIEW Report AS
SELECT *
FROM (
SELECT account, date, credId, value, purpose -- relevant incomes
FROM CreditsInFocus
UNION
SELECT debtor AS account, -- partial payments
DATE(t.timestamp)
AS date,
t.credId AS credId,
t.amount * -1 AS value,
d.purpose || ' [' || d.billId || ']'
|| CASE WHEN t.note IS NULL
THEN ''
ELSE ( x'0a' || '(' || t.note || ')' )
END
AS purpose
FROM Debit d
JOIN Transfer t ON t.billId = d.billId
JOIN CreditsInFocus fc ON fc.credId=t.credId
UNION
SELECT debtor AS account, -- current arrears
date,
NULL AS credId,
difference * -1 AS value,
purpose || ' [' || billId || ']'
|| x'0a' || '(YET TO PAY)'
FROM CurrentArrears
)
ORDER BY account, credId IS NULL, credId,
value < 0, date ASC
;

View File

@ -49,7 +49,7 @@ while ( my ($num, $month) = each %months ) {
billId => "MB$yy$num/john", billId => "MB$yy$num/john",
debtor => "john", debtor => "john",
targetCredit => 1, targetCredit => 1,
date => "16-05-01", date => "$yy-$num-01",
purpose => "Membership fee $month", purpose => "Membership fee $month",
value => 600 value => 600
}); });