2015-12-30 19:09:47 +00:00
|
|
|
# License: AGPLv3-or-later
|
|
|
|
# Copyright info in COPYRIGHT.md, License details in LICENSE.md with this package.
|
2015-12-07 01:20:14 +00:00
|
|
|
package Supporters;
|
|
|
|
|
|
|
|
use 5.020002;
|
|
|
|
use strict;
|
|
|
|
use warnings;
|
|
|
|
|
|
|
|
require Exporter;
|
|
|
|
|
|
|
|
our @ISA = qw(Exporter);
|
|
|
|
|
|
|
|
# Items to export into callers namespace by default. Note: do not export
|
|
|
|
# names by default without a very good reason. Use EXPORT_OK instead.
|
|
|
|
# Do not simply export all your public functions/methods/constants.
|
|
|
|
|
|
|
|
# This allows declaration use Supporters ':all';
|
|
|
|
# If you do not need this, moving things directly into @EXPORT or @EXPORT_OK
|
|
|
|
# will save memory.
|
|
|
|
our %EXPORT_TAGS = ( 'all' => [ qw(
|
|
|
|
|
|
|
|
) ] );
|
|
|
|
|
|
|
|
our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
|
|
|
|
|
|
|
|
our @EXPORT = qw(
|
|
|
|
|
|
|
|
);
|
|
|
|
|
2015-12-07 01:27:25 +00:00
|
|
|
our $VERSION = '0.02';
|
2015-12-07 01:20:14 +00:00
|
|
|
|
2015-12-30 20:08:33 +00:00
|
|
|
use Scalar::Util qw(looks_like_number blessed reftype);
|
2015-12-31 04:11:57 +00:00
|
|
|
use List::Util qw(maxstr);
|
|
|
|
|
2015-12-12 02:32:59 +00:00
|
|
|
use Mail::RFC822::Address;
|
2015-12-21 00:52:49 +00:00
|
|
|
use Carp qw(confess);
|
2015-12-31 04:11:57 +00:00
|
|
|
use Date::Manip::DM5;
|
2015-12-10 02:38:07 +00:00
|
|
|
|
2015-12-07 02:28:49 +00:00
|
|
|
######################################################################
|
2015-12-10 02:37:26 +00:00
|
|
|
|
|
|
|
=begin new
|
|
|
|
|
|
|
|
Create new Supporters object.
|
|
|
|
|
|
|
|
Arguments:
|
|
|
|
|
|
|
|
=over
|
|
|
|
|
|
|
|
=item $dbh
|
|
|
|
|
2015-12-17 03:24:24 +00:00
|
|
|
Scalar references for the database handle from the L<DBI>, already opened
|
|
|
|
and pointing to the right database. This class will take over and control
|
|
|
|
the DBI object after C<new()> completes.
|
2015-12-10 02:37:26 +00:00
|
|
|
|
|
|
|
=item $ledgerCmd
|
|
|
|
|
2015-12-30 20:08:33 +00:00
|
|
|
A list reference that contains the main ledger command with any necessary
|
|
|
|
arguments, for looking up donation data. The options should be presented
|
|
|
|
such that the output is in the form:
|
|
|
|
ProgramTag Date Entity Amount
|
|
|
|
|
|
|
|
=item $programTypeSearch
|
|
|
|
|
|
|
|
This hash should have two keys: "monthly" and "annual". The values of the
|
|
|
|
hash should be a regular expression that matches the ProgramTag lines for
|
|
|
|
categorization of the donations in annual or monthly buckets.
|
|
|
|
|
2015-12-10 02:37:26 +00:00
|
|
|
|
|
|
|
=back
|
|
|
|
|
|
|
|
=cut
|
|
|
|
|
2015-12-30 20:08:33 +00:00
|
|
|
sub new ($$;$) {
|
2015-12-07 02:28:49 +00:00
|
|
|
my $package = shift;
|
2015-12-30 20:08:33 +00:00
|
|
|
my($dbh, $ledgerCmd, $programTypeSearch) = @_;
|
2015-12-07 02:28:49 +00:00
|
|
|
|
2015-12-30 20:08:33 +00:00
|
|
|
die "new: second argument must be a list ref for the ledger command line"
|
|
|
|
unless (defined $ledgerCmd and ref $ledgerCmd and (reftype($ledgerCmd) eq 'ARRAY'));
|
|
|
|
|
|
|
|
die "new: keys annual and monthly must be the only keys in this hash"
|
|
|
|
if defined $programTypeSearch and (not (defined $programTypeSearch->{monthly} and defined $programTypeSearch->{annual}
|
|
|
|
and scalar(keys(%$programTypeSearch) == 2)));
|
2015-12-17 03:24:24 +00:00
|
|
|
|
|
|
|
die "new: first argument must be a database handle"
|
|
|
|
unless (defined $dbh and blessed($dbh) =~ /DBI/);
|
|
|
|
|
2015-12-30 20:08:33 +00:00
|
|
|
my $self = bless({ dbh => $dbh, ledgerCmd => $ledgerCmd },
|
|
|
|
$package);
|
|
|
|
|
|
|
|
$self->{programTypeSearch} = $programTypeSearch if defined $programTypeSearch;
|
|
|
|
|
2015-12-17 03:57:59 +00:00
|
|
|
# Turn off AutoCommit, and create our own handler that resets the
|
|
|
|
# begin_work/commit reference counter.
|
|
|
|
$dbh->{RaiseError} = 0;
|
|
|
|
$dbh->{HandleError} = sub {
|
2015-12-20 20:21:38 +00:00
|
|
|
$self->{__NESTED_TRANSACTION_COUNTER__} = 0;
|
2015-12-21 00:52:49 +00:00
|
|
|
confess $_[0];
|
2015-12-17 03:57:59 +00:00
|
|
|
};
|
2015-12-20 20:21:38 +00:00
|
|
|
$self->{__NESTED_TRANSACTION_COUNTER__} = 0;
|
2015-12-17 03:24:24 +00:00
|
|
|
return $self;
|
2015-12-07 02:28:49 +00:00
|
|
|
}
|
|
|
|
######################################################################
|
2015-12-10 02:37:26 +00:00
|
|
|
|
|
|
|
=begin dbh
|
|
|
|
|
|
|
|
Accessor method, returns the database handle currently used by this
|
|
|
|
Supporters object.
|
|
|
|
|
|
|
|
=cut
|
|
|
|
|
2015-12-07 02:28:49 +00:00
|
|
|
sub dbh ($) {
|
|
|
|
return $_[0]->{dbh};
|
|
|
|
}
|
|
|
|
######################################################################
|
2015-12-10 02:37:26 +00:00
|
|
|
|
|
|
|
=begin ledgerCmd
|
|
|
|
|
|
|
|
Accessor method, returns the ledger command currently used by this Supporters
|
|
|
|
object.
|
|
|
|
|
|
|
|
=cut
|
|
|
|
|
2015-12-07 02:28:49 +00:00
|
|
|
sub ledgerCmd ($) {
|
|
|
|
return $_[0]->{ledgerCmd};
|
|
|
|
}
|
2015-12-07 02:56:59 +00:00
|
|
|
######################################################################
|
2015-12-07 03:24:10 +00:00
|
|
|
sub addSupporter ($$) {
|
2015-12-17 02:07:46 +00:00
|
|
|
my($self, $sp) = @_;
|
2015-12-07 02:56:59 +00:00
|
|
|
|
|
|
|
die "ledger_entity_id required" unless defined $sp->{ledger_entity_id};
|
2015-12-07 03:24:10 +00:00
|
|
|
|
|
|
|
if ($sp->{public_ack}) {
|
|
|
|
die "display_name required if public_ack requested" unless defined $sp->{display_name};
|
|
|
|
}
|
2015-12-17 02:07:46 +00:00
|
|
|
$self->_beginWork;
|
|
|
|
my $sth = $self->dbh->prepare(
|
2015-12-30 11:10:07 +00:00
|
|
|
"INSERT INTO donor(ledger_entity_id, display_name, public_ack, is_supporter)" .
|
|
|
|
" values(?, ?, ?, " .
|
|
|
|
$self->dbh->quote(1, 'SQL_BOOLEAN') . ')');
|
2015-12-09 23:44:05 +00:00
|
|
|
|
|
|
|
$sth->execute($sp->{ledger_entity_id}, $sp->{display_name}, $sp->{public_ack});
|
2015-12-17 02:07:46 +00:00
|
|
|
my $id = $self->dbh->last_insert_id("","","","");
|
2015-12-09 23:44:05 +00:00
|
|
|
$sth->finish();
|
2015-12-10 03:17:46 +00:00
|
|
|
|
2015-12-17 02:07:46 +00:00
|
|
|
$self->addEmailAddress($id, $sp->{email_address}, $sp->{email_address_type})
|
2015-12-10 03:17:46 +00:00
|
|
|
if defined $sp->{email_address};
|
|
|
|
|
2015-12-17 02:07:46 +00:00
|
|
|
$self->_commit;
|
2015-12-09 23:44:05 +00:00
|
|
|
return $id;
|
2015-12-07 02:56:59 +00:00
|
|
|
}
|
2015-12-10 03:17:46 +00:00
|
|
|
######################################################################
|
|
|
|
|
2015-12-10 03:48:59 +00:00
|
|
|
=begin addAddressType
|
|
|
|
|
|
|
|
Adds an address type, or returns the existing one of that name if it already exists.
|
|
|
|
|
|
|
|
Arguments:
|
|
|
|
|
|
|
|
=over
|
|
|
|
|
|
|
|
=item $addressType
|
|
|
|
|
|
|
|
Scalar string that contains the email address type. die() is called if not defined.
|
|
|
|
|
|
|
|
=back
|
|
|
|
|
|
|
|
Returns id of the address type.
|
|
|
|
|
|
|
|
=cut
|
|
|
|
|
|
|
|
sub addAddressType($$) {
|
|
|
|
my($self, $type) = @_;
|
|
|
|
|
|
|
|
die "addAddressType: type argument must be defined" if not defined $type;
|
|
|
|
|
|
|
|
my $val = $self->dbh()->selectall_hashref("SELECT id, name FROM address_type WHERE name = '$type'", 'name');
|
|
|
|
return $val->{$type}{id} if (defined $val and defined $val->{$type} and defined $val->{$type}{id});
|
|
|
|
|
|
|
|
my $sth = $self->dbh->prepare("INSERT INTO address_type(name) VALUES(?)");
|
|
|
|
|
|
|
|
$sth->execute($type);
|
|
|
|
my $id = $self->dbh->last_insert_id("","","","");
|
|
|
|
$sth->finish();
|
|
|
|
|
|
|
|
return $id;
|
|
|
|
}
|
2015-12-15 01:16:35 +00:00
|
|
|
######################################################################
|
2015-12-10 03:48:59 +00:00
|
|
|
|
2015-12-10 03:17:46 +00:00
|
|
|
=begin addEmailAddress
|
|
|
|
|
|
|
|
Arguments:
|
|
|
|
|
|
|
|
=over
|
|
|
|
|
|
|
|
=item $id
|
|
|
|
|
|
|
|
Valid supporter id number currently in the database. die() will occur if
|
|
|
|
the id number is not in the database already as a supporter id.
|
2015-12-07 01:20:14 +00:00
|
|
|
|
2015-12-10 03:17:46 +00:00
|
|
|
=item $emailAddress
|
|
|
|
|
|
|
|
Scalar string that contains an email address. die() will occur if the
|
|
|
|
email address isn't properly formatted.
|
|
|
|
|
|
|
|
=item $emailAddressType
|
|
|
|
|
|
|
|
Scalar string that contains the email address type. This type will be
|
|
|
|
created in the database if it does not already exist, so be careful.
|
|
|
|
|
|
|
|
=back
|
|
|
|
|
2015-12-12 02:23:24 +00:00
|
|
|
Returns the id value of the email_address entry.
|
|
|
|
|
2015-12-10 03:17:46 +00:00
|
|
|
=cut
|
|
|
|
|
|
|
|
sub addEmailAddress($$$$) {
|
|
|
|
my($self, $id, $emailAddress, $emailAddressType) = @_;
|
|
|
|
|
|
|
|
die "addEmailAddress: invalid id, $id" unless $self->_verifyId($id);
|
2015-12-10 03:56:22 +00:00
|
|
|
|
2015-12-17 04:24:24 +00:00
|
|
|
die "addEmailAddress: invalid email address, $emailAddressType"
|
2015-12-12 02:32:59 +00:00
|
|
|
unless defined $emailAddressType and Mail::RFC822::Address::valid($emailAddress);
|
|
|
|
|
2015-12-30 13:49:12 +00:00
|
|
|
my $existingEmail = $self->_lookupEmailAddress($emailAddress);
|
2015-12-12 02:32:59 +00:00
|
|
|
|
2015-12-30 14:00:42 +00:00
|
|
|
if (defined $existingEmail) {
|
|
|
|
die "addEmailAddress: attempt to add email address that exists, using a different type!"
|
|
|
|
if $existingEmail->{type} ne $emailAddressType;
|
|
|
|
|
|
|
|
my $val = $self->dbh()->selectall_hashref("SELECT email_address_id, donor_id " .
|
|
|
|
"FROM donor_email_address_mapping WHERE " .
|
|
|
|
"donor_id = " . $self->dbh->quote($id, 'SQL_INTEGER') . " AND " .
|
|
|
|
"email_address_id = " . $self->dbh->quote($existingEmail->{id}, 'SQL_INTEGER'),
|
|
|
|
'donor_id');
|
|
|
|
return $val->{$id}{email_address_id}
|
|
|
|
if (defined $val and defined $val->{$id} and defined $val->{$id}{email_address_id});
|
|
|
|
}
|
2015-12-30 13:49:12 +00:00
|
|
|
my($sth, $addressId);
|
2015-12-12 02:15:31 +00:00
|
|
|
|
2015-12-30 13:49:12 +00:00
|
|
|
$self->_beginWork();
|
2015-12-17 02:13:28 +00:00
|
|
|
|
2015-12-30 13:49:12 +00:00
|
|
|
if (defined $existingEmail) {
|
|
|
|
$addressId = $existingEmail->{id};
|
|
|
|
} else {
|
|
|
|
my $addressTypeId;
|
|
|
|
eval {
|
|
|
|
$addressTypeId = $self->addAddressType($emailAddressType);
|
|
|
|
};
|
|
|
|
if ($@ or not defined $addressTypeId) {
|
|
|
|
my $err = $@;
|
|
|
|
$err = "addEmailAddress: unable to addAddressType" if (not defined $err);
|
|
|
|
$self->_rollback();
|
|
|
|
die $@ if $@;
|
|
|
|
}
|
|
|
|
$sth = $self->dbh->prepare("INSERT INTO email_address(email_address, type_id, date_encountered)" .
|
|
|
|
"VALUES( ?, ?, date('now'))");
|
|
|
|
|
|
|
|
$sth->execute($emailAddress, $addressTypeId);
|
|
|
|
$addressId = $self->dbh->last_insert_id("","","","");
|
|
|
|
$sth->finish();
|
|
|
|
}
|
2015-12-30 11:10:07 +00:00
|
|
|
$sth = $self->dbh->prepare("INSERT INTO donor_email_address_mapping" .
|
2015-12-30 13:49:12 +00:00
|
|
|
"(donor_id, email_address_id) " .
|
|
|
|
"VALUES( ?, ?)");
|
2015-12-17 02:13:28 +00:00
|
|
|
$sth->execute($id, $addressId);
|
|
|
|
$sth->finish();
|
|
|
|
|
2015-12-15 01:16:35 +00:00
|
|
|
$self->_commit();
|
2015-12-17 02:13:28 +00:00
|
|
|
|
2015-12-12 02:15:31 +00:00
|
|
|
return $addressId;
|
2015-12-10 03:17:46 +00:00
|
|
|
}
|
2015-12-10 02:38:07 +00:00
|
|
|
######################################################################
|
|
|
|
|
2015-12-31 09:59:50 +00:00
|
|
|
=begin getEmailAddresses
|
|
|
|
|
|
|
|
Arguments:
|
|
|
|
|
|
|
|
=over
|
|
|
|
|
|
|
|
=item $id
|
|
|
|
|
|
|
|
Valid supporter id number currently in the database. die() will occur if
|
|
|
|
the id number is not in the database already as a supporter id.
|
|
|
|
|
|
|
|
=back
|
|
|
|
|
|
|
|
Returns a hashes, where the keys are the emailAddreses and values a hash with two keys:
|
|
|
|
|
|
|
|
=over
|
|
|
|
|
|
|
|
=item date_encountered
|
|
|
|
|
|
|
|
=item name
|
|
|
|
|
|
|
|
=back
|
|
|
|
|
|
|
|
=cut
|
|
|
|
|
|
|
|
sub getEmailAddresses($$) {
|
|
|
|
my($self, $id) = @_;
|
|
|
|
|
|
|
|
die "getEmailAddresses: invalid id, $id" unless $self->_verifyId($id);
|
|
|
|
|
|
|
|
my $val = $self->dbh()->selectall_hashref("SELECT ea.email_address, at.name, ea.date_encountered " .
|
|
|
|
"FROM donor_email_address_mapping map, address_type at, email_address ea " .
|
|
|
|
"WHERE at.id = ea.type_id AND ea.id = map.email_address_id AND " .
|
|
|
|
"map.donor_id = " . $self->dbh->quote($id, 'SQL_INTEGER'),
|
|
|
|
'email_address');
|
|
|
|
foreach my $key (keys %{$val}) { delete $val->{$key}{email_address}; }
|
|
|
|
return %{$val};
|
|
|
|
}
|
|
|
|
######################################################################
|
|
|
|
|
2015-12-21 02:09:24 +00:00
|
|
|
=begin setPreferredEmailAddress
|
|
|
|
|
|
|
|
Arguments:
|
|
|
|
|
|
|
|
=over
|
|
|
|
|
2015-12-30 11:13:41 +00:00
|
|
|
=item $donorId
|
2015-12-21 02:09:24 +00:00
|
|
|
|
|
|
|
Valid supporter id number currently in the database. die() will occur if
|
|
|
|
the id number is not in the database already as a supporter id.
|
|
|
|
|
|
|
|
|
|
|
|
=item $emailAddress
|
|
|
|
|
|
|
|
Scalar string that contains an email address. undef is returned if the
|
|
|
|
email address is not already in the database for this supporter.
|
|
|
|
|
|
|
|
=back
|
|
|
|
|
|
|
|
Returns the email_address_id of the preferred email address. undef can be
|
|
|
|
returned; it means the preferred email address wasn't selected for some reason.
|
|
|
|
|
|
|
|
=cut
|
|
|
|
|
|
|
|
sub setPreferredEmailAddress($$$) {
|
2015-12-30 11:13:41 +00:00
|
|
|
my($self, $donorId, $emailAddress) = @_;
|
2015-12-21 02:09:24 +00:00
|
|
|
|
2015-12-30 11:13:41 +00:00
|
|
|
die "setPreferredEmailAddress: invalid supporter id, $donorId" unless $self->_verifyId($donorId);
|
2015-12-21 02:09:24 +00:00
|
|
|
die "setPreferredEmailAddress: email address not defined" unless defined $emailAddress;
|
|
|
|
die "setPreferredEmailAddress: invalid email address, $emailAddress"
|
|
|
|
unless Mail::RFC822::Address::valid($emailAddress);
|
|
|
|
|
|
|
|
my $ems = $self->dbh()->selectall_hashref("SELECT ea.email_address, ea.id, sem.preferred " .
|
2015-12-30 11:10:07 +00:00
|
|
|
"FROM email_address ea, donor_email_address_mapping sem " .
|
2015-12-21 02:09:24 +00:00
|
|
|
"WHERE ea.id = sem.email_address_id AND ".
|
2015-12-30 11:13:41 +00:00
|
|
|
"sem.donor_id = " . $self->dbh->quote($donorId, 'SQL_INTEGER'),
|
2015-12-21 02:09:24 +00:00
|
|
|
'email_address');
|
|
|
|
# Shortcut: it was already set
|
|
|
|
return $ems->{$emailAddress}{id} if (defined $ems->{$emailAddress} and $ems->{$emailAddress}{preferred});
|
|
|
|
|
|
|
|
my $anotherPreferred = 0;
|
|
|
|
my $emailAddressId;
|
|
|
|
# Iterate over email addresses, finding if any were preferred before, and finding outs too.
|
|
|
|
foreach my $em (keys %{$ems}) {
|
|
|
|
$anotherPreferred = 1 if $ems->{$em}{preferred};
|
|
|
|
$emailAddressId = $ems->{$em}{id} if $em eq $emailAddress;
|
|
|
|
}
|
|
|
|
return undef if not defined $emailAddressId;
|
|
|
|
|
|
|
|
$self->_beginWork();
|
|
|
|
if ($anotherPreferred) {
|
2015-12-30 11:10:07 +00:00
|
|
|
$self->dbh->do("UPDATE donor_email_address_mapping " .
|
2015-12-21 02:09:24 +00:00
|
|
|
"SET preferred = " . $self->dbh->quote(0, 'SQL_BOOLEAN') . " " .
|
2015-12-30 11:13:41 +00:00
|
|
|
"WHERE donor_id = " . $self->dbh->quote($donorId, 'SQL_INTEGER'));
|
2015-12-21 02:09:24 +00:00
|
|
|
}
|
2015-12-30 11:10:07 +00:00
|
|
|
$self->dbh->do("UPDATE donor_email_address_mapping " .
|
2015-12-21 02:09:24 +00:00
|
|
|
"SET preferred = " . $self->dbh->quote(1, 'SQL_BOOLEAN') . " " .
|
|
|
|
"WHERE email_address_id = " . $self->dbh->quote($emailAddressId, 'SQL_INTEGER'));
|
2015-12-21 02:22:15 +00:00
|
|
|
$self->_commit;
|
2015-12-21 02:09:24 +00:00
|
|
|
return $emailAddressId;
|
|
|
|
}
|
|
|
|
######################################################################
|
|
|
|
|
2015-12-21 02:22:15 +00:00
|
|
|
=begin getPreferredEmailAddress
|
|
|
|
|
|
|
|
Arguments:
|
|
|
|
|
|
|
|
=over
|
|
|
|
|
2015-12-30 11:13:41 +00:00
|
|
|
=item $donorId
|
2015-12-21 02:22:15 +00:00
|
|
|
|
|
|
|
Valid supporter id number currently in the database. die() will occur if
|
|
|
|
the id number is not in the database already as a supporter id.
|
|
|
|
|
|
|
|
|
|
|
|
=item $emailAddress
|
|
|
|
|
|
|
|
Scalar string that contains an email address. undef is returned if the
|
|
|
|
email address is not already in the database for this supporter.
|
|
|
|
|
|
|
|
=back
|
|
|
|
|
|
|
|
Returns the email_address_id of the preferred email address. undef can be
|
|
|
|
returned; it means the preferred email address wasn't selected for some reason.
|
|
|
|
|
|
|
|
=cut
|
|
|
|
|
|
|
|
sub getPreferredEmailAddress($$) {
|
2015-12-30 11:13:41 +00:00
|
|
|
my($self, $donorId) = @_;
|
2015-12-21 02:22:15 +00:00
|
|
|
|
2015-12-30 11:13:41 +00:00
|
|
|
die "setPreferredEmailAddress: invalid supporter id, $donorId" unless $self->_verifyId($donorId);
|
2015-12-21 02:22:15 +00:00
|
|
|
|
2015-12-30 11:10:07 +00:00
|
|
|
my $ems = $self->dbh()->selectall_hashref("SELECT email_address FROM email_address em, donor_email_address_mapping sem " .
|
2015-12-21 02:22:15 +00:00
|
|
|
"WHERE preferred AND sem.email_address_id = em.id AND " .
|
2015-12-30 11:13:41 +00:00
|
|
|
"sem.donor_id = " . $self->dbh->quote($donorId, 'SQL_INTEGER'),
|
2015-12-21 02:22:15 +00:00
|
|
|
'email_address');
|
|
|
|
my $rowCount = scalar keys %{$ems};
|
2015-12-30 11:13:41 +00:00
|
|
|
die "setPreferredEmailAddress: DATABASE INTEGRITY ERROR: more than one email address is preferred for supporter, \"$donorId\""
|
2015-12-21 02:22:15 +00:00
|
|
|
if $rowCount > 1;
|
|
|
|
|
|
|
|
if ($rowCount != 1) {
|
|
|
|
return undef;
|
|
|
|
} else {
|
|
|
|
my ($emailAddress) = keys %$ems;
|
|
|
|
return $emailAddress;
|
|
|
|
}
|
|
|
|
}
|
2015-12-30 19:30:22 +00:00
|
|
|
######################################################################
|
2015-12-31 01:03:07 +00:00
|
|
|
sub _getDonorField($$$) {
|
|
|
|
my($self, $field, $donorId) = @_;
|
|
|
|
|
|
|
|
die "get$field: invalid supporter id, $donorId" unless $self->_verifyId($donorId);
|
|
|
|
|
|
|
|
my $results = $self->dbh()->selectall_hashref("SELECT id, $field FROM donor WHERE id = " .
|
|
|
|
$self->dbh->quote($donorId, 'SQL_INTEGER'),
|
|
|
|
'id');
|
|
|
|
my $rowCount = scalar keys %{$results};
|
|
|
|
die "get$field: DATABASE INTEGRITY ERROR: more than one row found when looking up supporter, \"$donorId\""
|
|
|
|
if $rowCount > 1;
|
|
|
|
|
|
|
|
if ($rowCount == 1) {
|
|
|
|
my ($val) = $results->{$donorId}{$field};
|
|
|
|
return $val;
|
|
|
|
} else {
|
|
|
|
die "get$field: DATABASE INTEGRITY ERROR: $donorId was valid but non-1 row count returned";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
######################################################################
|
2015-12-30 19:30:22 +00:00
|
|
|
|
|
|
|
=begin getLedgerEntityId
|
|
|
|
|
|
|
|
Arguments:
|
|
|
|
|
|
|
|
=over
|
|
|
|
|
|
|
|
=item $donorId
|
|
|
|
|
|
|
|
Valid donor id number currently in the database. die() will occur if
|
|
|
|
the id number is not in the database already as a donor id.
|
|
|
|
|
|
|
|
=back
|
|
|
|
|
|
|
|
Returns the ledger_entity_id of the donor. Since the method die()s for an
|
|
|
|
invalid donor id, undef should never be returned and callers need not test
|
|
|
|
for it.
|
|
|
|
|
|
|
|
=cut
|
|
|
|
|
2015-12-31 01:03:07 +00:00
|
|
|
sub getLedgerEntityId ($$) {
|
|
|
|
return $_[0]->_getDonorField("ledger_entity_id", $_[1]);
|
2015-12-30 19:31:36 +00:00
|
|
|
}
|
2015-12-21 02:22:15 +00:00
|
|
|
######################################################################
|
|
|
|
|
2015-12-31 00:51:04 +00:00
|
|
|
=begin getPublicAck
|
|
|
|
|
|
|
|
Arguments:
|
|
|
|
|
|
|
|
=over
|
|
|
|
|
|
|
|
=item $donorId
|
|
|
|
|
|
|
|
Valid donor id number currently in the database. die() will occur if
|
|
|
|
the id number is not in the database already as a donor id.
|
|
|
|
|
|
|
|
=back
|
|
|
|
|
|
|
|
Returns the a boolean indicating whether or not the donor seeks to be
|
|
|
|
publicly acknowledged. undef can be returned if the donor has not specified,
|
|
|
|
so callers must check for undef.
|
|
|
|
|
|
|
|
=cut
|
|
|
|
|
2015-12-31 01:03:07 +00:00
|
|
|
sub getPublicAck($$$) {
|
|
|
|
return $_[0]->_getDonorField("public_ack", $_[1]);
|
2015-12-31 00:51:04 +00:00
|
|
|
}
|
|
|
|
######################################################################
|
|
|
|
|
2015-12-31 01:19:10 +00:00
|
|
|
=begin isSupporter
|
|
|
|
|
|
|
|
Arguments:
|
|
|
|
|
|
|
|
=over
|
|
|
|
|
|
|
|
=item $donorId
|
|
|
|
|
|
|
|
Valid donor id number currently in the database. die() will occur if
|
|
|
|
the id number is not in the database already as a donor id.
|
|
|
|
|
|
|
|
=back
|
|
|
|
|
|
|
|
Returns the a boolean indicating whether or not the donor is a Supporter (as
|
|
|
|
opposed to an ordinary donor). undef will not be returned
|
|
|
|
|
|
|
|
|
|
|
|
=cut
|
|
|
|
|
|
|
|
sub isSupporter($$$) {
|
|
|
|
return $_[0]->_getDonorField("is_supporter", $_[1]);
|
|
|
|
}
|
|
|
|
######################################################################
|
|
|
|
|
2015-12-31 01:12:58 +00:00
|
|
|
=begin getDisplayName
|
|
|
|
|
|
|
|
Arguments:
|
|
|
|
|
|
|
|
=over
|
|
|
|
|
|
|
|
=item $donorId
|
|
|
|
|
|
|
|
Valid donor id number currently in the database. die() will occur if
|
|
|
|
the id number is not in the database already as a donor id.
|
|
|
|
|
|
|
|
=back
|
|
|
|
|
|
|
|
Returns the string of the display name for the donor. undef can be returned
|
|
|
|
if the donor has not specified, so callers must check for undef.
|
|
|
|
|
|
|
|
=cut
|
|
|
|
|
|
|
|
sub getDisplayName($$$) {
|
|
|
|
return $_[0]->_getDonorField("display_name", $_[1]);
|
|
|
|
}
|
|
|
|
######################################################################
|
2015-12-31 01:54:37 +00:00
|
|
|
sub _setDonorField($$$) {
|
|
|
|
my($self, $field, $donorId, $value, $type) = @_;
|
|
|
|
|
|
|
|
die "set$field: invalid supporter id, $donorId" unless $self->_verifyId($donorId);
|
|
|
|
|
|
|
|
$self->_beginWork();
|
|
|
|
$self->dbh->do("UPDATE donor " .
|
|
|
|
"SET $field = " . $self->dbh->quote($value, $type) . " " .
|
|
|
|
"WHERE id = " . $self->dbh->quote($donorId, 'SQL_INTEGER'));
|
|
|
|
$self->_commit;
|
|
|
|
return $value;
|
|
|
|
}
|
|
|
|
######################################################################
|
|
|
|
|
|
|
|
=begin setPublicAck
|
|
|
|
|
|
|
|
Arguments:
|
|
|
|
|
|
|
|
=over
|
|
|
|
|
|
|
|
=item $donorId
|
|
|
|
|
|
|
|
Valid donor id number currently in the database. die() will occur if
|
|
|
|
the id number is not in the database already as a donor id.
|
|
|
|
|
|
|
|
=item $publicAck
|
|
|
|
|
|
|
|
Can be true, false, or undef and will update public acknowledgement bit
|
|
|
|
accordingly for donor identified with C<$donorId>.
|
|
|
|
|
|
|
|
=back
|
|
|
|
|
|
|
|
=cut
|
|
|
|
|
|
|
|
sub setPublicAck($$$) {
|
|
|
|
return $_[0]->_setDonorField('public_ack', $_[1], $_[2], 'SQL_BOOLEAN');
|
|
|
|
}
|
|
|
|
######################################################################
|
2015-12-31 01:12:58 +00:00
|
|
|
|
2015-12-17 02:36:47 +00:00
|
|
|
=begin addPostalAddress
|
|
|
|
|
|
|
|
Arguments:
|
|
|
|
|
|
|
|
=over
|
|
|
|
|
|
|
|
=item $id
|
|
|
|
|
|
|
|
Valid supporter id number currently in the database. die() will occur if
|
|
|
|
the id number is not in the database already as a supporter id.
|
|
|
|
|
|
|
|
=item $formattedPostalAddress
|
|
|
|
|
|
|
|
Scalar string that contains a multi-line, fully formatted, postal address.
|
|
|
|
|
|
|
|
=item $addressType
|
|
|
|
|
|
|
|
Scalar string that contains the address type. This type will be created in
|
|
|
|
the database if it does not already exist, so be careful.
|
|
|
|
|
|
|
|
=back
|
|
|
|
|
|
|
|
Returns the id value of the postal_address table entry.
|
|
|
|
|
|
|
|
=cut
|
|
|
|
|
|
|
|
sub addPostalAddress($$$$) {
|
|
|
|
my($self, $id, $formattedPostalAddress, $addressType) = @_;
|
|
|
|
|
2015-12-17 04:24:52 +00:00
|
|
|
die "addPostalAddress: invalid id, $id" unless $self->_verifyId($id);
|
|
|
|
die "addPostalAddress: the formatted postal address must be defined"
|
|
|
|
unless defined $formattedPostalAddress;
|
|
|
|
|
|
|
|
$self->_beginWork();
|
|
|
|
|
|
|
|
my $addressTypeId;
|
|
|
|
eval {
|
2015-12-18 00:59:27 +00:00
|
|
|
$addressTypeId = $self->addAddressType($addressType);
|
2015-12-17 04:24:52 +00:00
|
|
|
};
|
|
|
|
if ($@ or not defined $addressTypeId) {
|
|
|
|
my $err = $@;
|
|
|
|
$err = "addPostalAddress: unable to addAddressType" if (not defined $err);
|
|
|
|
$self->_rollback();
|
|
|
|
die $@ if $@;
|
|
|
|
}
|
|
|
|
my $sth = $self->dbh->prepare("INSERT INTO postal_address(formatted_address, type_id, date_encountered)" .
|
|
|
|
"VALUES( ?, ?, date('now'))");
|
|
|
|
|
|
|
|
$sth->execute($formattedPostalAddress, $addressTypeId);
|
|
|
|
my $addressId = $self->dbh->last_insert_id("","","","");
|
|
|
|
$sth->finish();
|
|
|
|
|
2015-12-30 11:10:07 +00:00
|
|
|
$sth = $self->dbh->prepare("INSERT INTO donor_postal_address_mapping" .
|
|
|
|
"(donor_id, postal_address_id) " .
|
|
|
|
"VALUES( ?, ?)");
|
2015-12-17 04:24:52 +00:00
|
|
|
$sth->execute($id, $addressId);
|
|
|
|
$sth->finish();
|
|
|
|
|
|
|
|
$self->_commit();
|
|
|
|
|
|
|
|
return $addressId;
|
2015-12-17 02:36:47 +00:00
|
|
|
}
|
|
|
|
######################################################################
|
|
|
|
|
2016-01-19 05:20:43 +00:00
|
|
|
=begin getPostalAddresses
|
|
|
|
|
|
|
|
Arguments:
|
|
|
|
|
|
|
|
=over
|
|
|
|
|
|
|
|
=item $id
|
|
|
|
|
|
|
|
Valid supporter id number currently in the database. die() will occur if
|
|
|
|
the id number is not in the database already as a supporter id.
|
|
|
|
|
|
|
|
=item $formattedPostalAddress
|
|
|
|
|
|
|
|
Scalar string that contains a multi-line, fully formatted, postal address.
|
|
|
|
|
|
|
|
=back
|
|
|
|
|
|
|
|
Returns the id value of the postal_address table entry.
|
|
|
|
|
|
|
|
=cut
|
|
|
|
|
|
|
|
sub getPostalAddresses($) {
|
|
|
|
my($self, $id) = @_;
|
|
|
|
|
|
|
|
die "addPostalAddress: invalid id, $id" unless $self->_verifyId($id);
|
|
|
|
|
|
|
|
my $val = $self->dbh()->selectall_hashref("SELECT pa.formatted_address, at.name, pa.date_encountered " .
|
|
|
|
"FROM donor_postal_address_mapping map, address_type at, postal_address pa " .
|
|
|
|
"WHERE at.id = pa.type_id AND pa.id = map.postal_address_id AND " .
|
|
|
|
"map.donor_id = " . $self->dbh->quote($id, 'SQL_INTEGER'),
|
|
|
|
'formatted_address');
|
|
|
|
foreach my $key (keys %{$val}) { delete $val->{$key}{formatted_address}; }
|
|
|
|
return %{$val};
|
|
|
|
|
|
|
|
}
|
|
|
|
######################################################################
|
|
|
|
|
2015-12-13 20:16:14 +00:00
|
|
|
=begin getRequestType
|
|
|
|
|
|
|
|
Arguments:
|
|
|
|
|
|
|
|
=over
|
|
|
|
|
|
|
|
=item type
|
|
|
|
|
|
|
|
A string describing the request.
|
|
|
|
|
|
|
|
=back
|
|
|
|
|
|
|
|
Returns the id value of the request_type entry. undef is returned if there
|
|
|
|
is no request of that type.
|
|
|
|
|
|
|
|
=cut
|
|
|
|
|
|
|
|
sub getRequestType($$) {
|
|
|
|
my($self, $type) = @_;
|
|
|
|
|
|
|
|
return undef if not defined $type;
|
|
|
|
my $val = $self->dbh()->selectall_hashref("SELECT id, type FROM request_type WHERE type = '$type'", 'type');
|
|
|
|
return $val->{$type}{id} if (defined $val and defined $val->{$type} and defined $val->{$type}{id});
|
|
|
|
return undef;
|
|
|
|
}
|
|
|
|
######################################################################
|
|
|
|
|
|
|
|
=begin addRequestType
|
|
|
|
|
|
|
|
Arguments:
|
|
|
|
|
|
|
|
=over
|
|
|
|
|
|
|
|
=item type
|
|
|
|
|
|
|
|
A string describing the request. die()'s if not defined.
|
|
|
|
|
|
|
|
=back
|
|
|
|
|
|
|
|
Returns the id value of the request_type entry. If the type already exists,
|
|
|
|
it is simply returned.
|
|
|
|
|
|
|
|
=cut
|
|
|
|
|
|
|
|
sub addRequestType($$) {
|
|
|
|
my($self, $requestType) = @_;
|
|
|
|
|
|
|
|
die "addRequestType: undefined request type." unless defined $requestType;
|
|
|
|
|
|
|
|
my $requestId = $self->getRequestType($requestType);
|
|
|
|
return $requestId if (defined $requestId);
|
|
|
|
|
2015-12-15 01:16:35 +00:00
|
|
|
$self->_beginWork();
|
2015-12-13 20:16:14 +00:00
|
|
|
|
|
|
|
my $sth = $self->dbh->prepare("INSERT INTO request_type(type) VALUES(?)");
|
|
|
|
|
|
|
|
$sth->execute($requestType);
|
|
|
|
$requestId = $self->dbh->last_insert_id("","","","");
|
|
|
|
$sth->finish();
|
2015-12-15 01:16:35 +00:00
|
|
|
$self->_commit();
|
2015-12-13 20:16:14 +00:00
|
|
|
return $requestId;
|
|
|
|
}
|
|
|
|
######################################################################
|
|
|
|
|
2015-12-13 20:50:08 +00:00
|
|
|
=begin getRequestConfigurations
|
|
|
|
|
|
|
|
Arguments:
|
|
|
|
|
|
|
|
=over
|
|
|
|
|
|
|
|
=item type
|
|
|
|
|
|
|
|
A string describing the request_type.
|
|
|
|
|
|
|
|
=back
|
|
|
|
|
|
|
|
Returns undef if the request_type is not found in the database. If the reuqest type is
|
|
|
|
is no request of that type.
|
|
|
|
|
|
|
|
=cut
|
|
|
|
|
|
|
|
sub getRequestConfigurations($$) {
|
|
|
|
my($self, $type) = @_;
|
|
|
|
|
|
|
|
return undef if not defined $type;
|
|
|
|
my $typeId = $self->getRequestType($type);
|
|
|
|
return undef if not defined $typeId;
|
2015-12-13 22:06:22 +00:00
|
|
|
|
|
|
|
my %descriptions;
|
|
|
|
my $dbData =
|
|
|
|
$self->dbh()->selectall_hashref("SELECT description, id FROM request_configuration " .
|
|
|
|
"WHERE request_type_id = " . $self->dbh->quote($typeId, 'SQL_INTEGER'),
|
|
|
|
'description');
|
|
|
|
foreach my $description (keys %$dbData) {
|
|
|
|
$descriptions{$description} = $dbData->{$description}{id};
|
|
|
|
}
|
|
|
|
return { $typeId => \%descriptions };
|
2015-12-13 20:50:08 +00:00
|
|
|
}
|
|
|
|
######################################################################
|
|
|
|
|
2015-12-13 21:04:27 +00:00
|
|
|
=begin addRequestConfigurations
|
|
|
|
|
|
|
|
Arguments:
|
|
|
|
|
|
|
|
=over
|
|
|
|
|
|
|
|
=item type
|
|
|
|
|
|
|
|
A string describing the request type. This will be created if it does not
|
|
|
|
already exist, so be careful.
|
|
|
|
|
|
|
|
=item descriptionListRef
|
|
|
|
|
|
|
|
A list reference to the list of configuration descriptions to associate
|
2015-12-13 21:37:52 +00:00
|
|
|
with this requestId. Duplicates aren't permitted in this list, and
|
|
|
|
die()'s if duplicates exist.
|
2015-12-13 21:04:27 +00:00
|
|
|
|
|
|
|
=back
|
|
|
|
|
|
|
|
Returns a hash in the form of:
|
|
|
|
|
|
|
|
$requestTypeId => { description => $requestConfigurationId }
|
|
|
|
|
|
|
|
=cut
|
|
|
|
|
|
|
|
sub addRequestConfigurations($$$) {
|
|
|
|
my($self, $requestType, $descriptionListRef) = @_;
|
|
|
|
|
|
|
|
die "addRequestConfigurations: undefined request type." unless defined $requestType;
|
|
|
|
|
2015-12-15 01:16:35 +00:00
|
|
|
$self->_beginWork();
|
2015-12-13 21:04:27 +00:00
|
|
|
|
|
|
|
my $requestId = $self->addRequestType($requestType);
|
|
|
|
|
2015-12-17 04:11:50 +00:00
|
|
|
if (not defined $requestType) {
|
|
|
|
$self->_rollback();
|
|
|
|
die "addRequestConfigurations: unable to create request configurations";
|
|
|
|
}
|
2015-12-13 21:04:27 +00:00
|
|
|
|
|
|
|
my %descriptions;
|
2015-12-13 21:37:52 +00:00
|
|
|
my $sth = $self->dbh->prepare("INSERT INTO request_configuration(request_type_id, description) " .
|
|
|
|
"VALUES(?, ?)");
|
|
|
|
foreach my $description (@{$descriptionListRef}) {
|
|
|
|
if (defined $descriptions{$description}) {
|
2015-12-17 04:11:50 +00:00
|
|
|
$self->_rollback();
|
2015-12-13 21:37:52 +00:00
|
|
|
die "addRequestConfigurations: attempt to create duplicate request_configuration \"$description\" for requestType, \"$requestType\"";
|
|
|
|
}
|
|
|
|
$sth->execute($requestId, $description);
|
|
|
|
$descriptions{$description} = $self->dbh->last_insert_id("","","","");
|
|
|
|
}
|
|
|
|
$sth->finish();
|
2015-12-15 01:16:35 +00:00
|
|
|
$self->_commit();
|
2015-12-13 21:04:27 +00:00
|
|
|
return { $requestId => \%descriptions };
|
|
|
|
}
|
|
|
|
######################################################################
|
|
|
|
|
2015-12-21 00:40:27 +00:00
|
|
|
=begin getRequest
|
|
|
|
|
|
|
|
Arguments:
|
|
|
|
|
2015-12-30 11:58:48 +00:00
|
|
|
=item $parmas
|
|
|
|
|
|
|
|
A hash reference, the following keys are considered:
|
|
|
|
|
2015-12-21 00:40:27 +00:00
|
|
|
=over
|
|
|
|
|
2015-12-30 11:58:48 +00:00
|
|
|
=item donorId
|
2015-12-21 00:40:27 +00:00
|
|
|
|
2015-12-30 11:10:07 +00:00
|
|
|
Valid donor_id number currently in the database. die() will occur if
|
2015-12-21 00:40:27 +00:00
|
|
|
the id number is not in the database already as a supporter id.
|
|
|
|
|
2015-12-30 11:58:48 +00:00
|
|
|
=item requestTypeId
|
2015-12-21 00:40:27 +00:00
|
|
|
|
2015-12-30 11:58:48 +00:00
|
|
|
Numeric id of a request_type entry. This must be a valid id in the
|
|
|
|
request_type table, otherwise the method L<die>()s.
|
|
|
|
|
|
|
|
requestType is ignored if this parameter is set.
|
2015-12-21 00:40:27 +00:00
|
|
|
|
2015-12-30 11:58:48 +00:00
|
|
|
=item requestType
|
|
|
|
|
|
|
|
If requestTypeId is not given, requestType will be used. The type is
|
|
|
|
added to the request_type table if it is not present, so be careful.
|
|
|
|
|
|
|
|
=item ignoreFulfilledRequests
|
2015-12-21 00:40:27 +00:00
|
|
|
|
|
|
|
Optional boolean argument. If true, a request that is found will not be
|
|
|
|
returned if the request has already been fulfilled. In other words, it
|
|
|
|
forces a return of undef for
|
|
|
|
|
|
|
|
=back
|
|
|
|
|
2015-12-30 11:58:48 +00:00
|
|
|
=back
|
|
|
|
|
2015-12-21 00:40:27 +00:00
|
|
|
Returns:
|
|
|
|
|
|
|
|
=over
|
|
|
|
|
|
|
|
=item undef
|
|
|
|
|
2015-12-30 11:13:41 +00:00
|
|
|
if the C<$requestType> is not found for C<$donorId> (or, as above,
|
2015-12-21 00:40:27 +00:00
|
|
|
the C<$requestType> is found but has been fufilled and
|
|
|
|
C<$ignoreFulfilledRequests>.
|
|
|
|
|
|
|
|
=item a hash reference
|
|
|
|
|
|
|
|
If found, the has reference will contain at least the following keys:
|
|
|
|
|
|
|
|
=over
|
|
|
|
|
|
|
|
=item requestType
|
|
|
|
|
|
|
|
Should match the request type in C<$requestType>
|
|
|
|
|
|
|
|
=item requestTypeId
|
|
|
|
|
|
|
|
The id from the request_type entry for C<$requestType>
|
|
|
|
|
|
|
|
=item requestDate
|
|
|
|
|
|
|
|
The date the request was made, in ISO 8601 format.
|
|
|
|
|
|
|
|
=back
|
|
|
|
|
|
|
|
|
|
|
|
Optionally, if these values are not null in the database record, the
|
|
|
|
following fields may also be included:
|
|
|
|
|
|
|
|
=over
|
|
|
|
|
|
|
|
|
|
|
|
=item notes
|
|
|
|
|
|
|
|
Notes made for the request.
|
|
|
|
|
|
|
|
=item requestConfiguration
|
|
|
|
|
|
|
|
any rquest configuration option given with the request.
|
|
|
|
|
|
|
|
=back
|
|
|
|
|
2016-01-20 01:44:34 +00:00
|
|
|
If the request has been fufilled, the following keys will also have values:
|
2015-12-21 00:40:27 +00:00
|
|
|
|
|
|
|
=over
|
|
|
|
|
|
|
|
=item fulfillDate
|
|
|
|
|
|
|
|
The date the request was fufilled, in ISO 8601 format.
|
|
|
|
|
|
|
|
=back
|
|
|
|
|
|
|
|
=back
|
|
|
|
|
|
|
|
=cut
|
|
|
|
|
|
|
|
sub getRequest($$;$) {
|
2015-12-30 11:58:48 +00:00
|
|
|
my($self, $params) = @_;
|
|
|
|
my($donorId, $requestType, $requestTypeId, $ignoreFulfilledRequests) =
|
|
|
|
($params->{donorId}, $params->{requestType}, $params->{requestTypeId}, $params->{ignoreFulfilledRequests});
|
2015-12-21 00:40:27 +00:00
|
|
|
|
2015-12-30 11:13:41 +00:00
|
|
|
die "getRequest: undefined donorId" unless defined $donorId;
|
|
|
|
die "getRequest: donorId, \"$donorId\" not found in supporter database"
|
|
|
|
unless $self->_verifyId($donorId);
|
2015-12-21 00:40:27 +00:00
|
|
|
|
2015-12-30 11:58:48 +00:00
|
|
|
my $requestTypeClause = "";
|
|
|
|
if (defined $requestTypeId) {
|
|
|
|
$requestType = $self->_lookupRequestTypeById($requestTypeId);
|
|
|
|
die "getRequest: invalid requestTypeId, \"$requestTypeId\"" unless defined $requestType;
|
|
|
|
$requestTypeClause = " AND rt.id = " . $self->dbh->quote($requestTypeId, 'SQL_INTEGER');
|
|
|
|
} elsif (defined $requestType) {
|
|
|
|
$requestTypeClause = " AND rt.type = " . $self->dbh->quote($requestType);
|
|
|
|
} else {
|
|
|
|
die "getRequest: undefined requestType" unless defined $requestType;
|
|
|
|
}
|
2015-12-21 00:40:27 +00:00
|
|
|
my $req = $self->dbh()->selectall_hashref("SELECT r.id, r.request_type_id, r.request_configuration_id, r.date_requested, r.notes, rt.type " .
|
|
|
|
"FROM request r, request_type rt WHERE r.request_type_id = rt.id AND " .
|
2015-12-30 11:13:41 +00:00
|
|
|
"r.donor_id = " . $self->dbh->quote($donorId, 'SQL_INTEGER') .
|
2015-12-30 11:58:48 +00:00
|
|
|
$requestTypeClause,
|
2015-12-21 00:40:27 +00:00
|
|
|
'type');
|
2015-12-30 11:58:48 +00:00
|
|
|
if (defined $requestTypeId) {
|
|
|
|
die "getRequest: given requestTypeId, \"$requestTypeId\" was not the one found in the database $req->{$requestType}{'request_type_id'}"
|
|
|
|
unless $req->{$requestType}{'request_type_id'} == $requestTypeId;
|
|
|
|
} else {
|
|
|
|
$requestTypeId = $req->{$requestType}{'request_type_id'};
|
|
|
|
}
|
2015-12-21 00:40:27 +00:00
|
|
|
return undef unless (defined $req and defined $req->{$requestType} and defined $req->{$requestType}{'id'});
|
2015-12-30 11:58:48 +00:00
|
|
|
|
2015-12-21 00:40:27 +00:00
|
|
|
my $requestId = $req->{$requestType}{'id'};
|
|
|
|
|
|
|
|
my $rsp = { requestType => $requestType,
|
|
|
|
requestTypeId => $requestTypeId,
|
|
|
|
requestId => $req->{$requestType}{'id'},
|
|
|
|
requestDate => $req->{$requestType}{'date_requested'},
|
|
|
|
notes => $req->{$requestType}{'notes'},
|
|
|
|
};
|
|
|
|
my $configs = $self->getRequestConfigurations($requestType);
|
|
|
|
my $configName;
|
|
|
|
foreach my $key (keys %{$configs->{$requestTypeId}}) {
|
|
|
|
if ($configs->{$requestTypeId}{$key} == $req->{$requestType}{'request_configuration_id'}) { $configName = $key; last; }
|
|
|
|
}
|
|
|
|
die("getRequest: discovered database integrity error: request_configuration, \"$req->{$requestType}{request_configuration_id} is " .
|
|
|
|
"not valid for requestId, \"$requestId\"") unless defined $configName or (keys %{$configs->{$requestId}} == 0);
|
|
|
|
$rsp->{requestConfiguration} = $configName;
|
|
|
|
|
|
|
|
my $fulfillReq = $self->dbh()->selectall_hashref("SELECT id, request_id, date FROM fulfillment WHERE request_id = " .
|
|
|
|
$self->dbh->quote($requestId, 'SQL_INTEGER'),
|
|
|
|
'request_id');
|
|
|
|
if (defined $fulfillReq and defined $fulfillReq->{$requestId} and defined $fulfillReq->{$requestId}{id}) {
|
|
|
|
return undef if $ignoreFulfilledRequests;
|
|
|
|
$rsp->{fulfillDate} = $fulfillReq->{$requestId}{date};
|
|
|
|
}
|
|
|
|
return $rsp;
|
|
|
|
}
|
|
|
|
######################################################################
|
|
|
|
|
2015-12-20 18:06:30 +00:00
|
|
|
=begin addRequest
|
|
|
|
|
|
|
|
Arguments:
|
|
|
|
|
|
|
|
=over
|
|
|
|
|
|
|
|
=item $parmas
|
|
|
|
|
|
|
|
A hash reference, the following keys are considered:
|
|
|
|
|
|
|
|
=over
|
|
|
|
|
2015-12-30 11:13:41 +00:00
|
|
|
=item donorId
|
2015-12-20 18:06:30 +00:00
|
|
|
|
2015-12-30 11:10:07 +00:00
|
|
|
Valid donor_id number currently in the database. die() will occur if
|
2015-12-20 18:06:30 +00:00
|
|
|
the id number is not in the database already as a supporter id.
|
|
|
|
|
|
|
|
=item requestTypeId
|
|
|
|
|
|
|
|
Numeric id of a request_type entry. This must be a valid id in the
|
|
|
|
request_type table, otherwise the method L<die>()s.
|
|
|
|
|
|
|
|
requestType is ignored if this parameter is set.
|
|
|
|
|
|
|
|
=item requestType
|
|
|
|
|
|
|
|
If requestTypeId is not given, requestType will be used. The type is
|
|
|
|
added to the request_type table if it is not present, so be careful.
|
|
|
|
|
|
|
|
|
|
|
|
=item requestConfigurationId
|
|
|
|
|
|
|
|
Numeric id of a request_configuration entry. This must be a valid id in
|
|
|
|
the request_configuration table, otherwise the method L<die>()s.
|
|
|
|
|
|
|
|
=item requestConfiguration
|
|
|
|
|
|
|
|
If requestConfigurationId is not given, requestConfiguration will be used.
|
|
|
|
This configuration will be added to the request_configuration table if it
|
|
|
|
is not present, so be careful.
|
|
|
|
|
|
|
|
=back
|
|
|
|
|
|
|
|
=back
|
|
|
|
|
|
|
|
Returns the id value of the request entry.
|
|
|
|
|
|
|
|
=cut
|
|
|
|
|
|
|
|
sub addRequest($$) {
|
|
|
|
my($self, $params) = @_;
|
2015-12-30 11:13:41 +00:00
|
|
|
die "addRequest: undefined donorId" unless defined $params->{donorId};
|
|
|
|
my $donorId = $params->{donorId};
|
|
|
|
die "addRequest: donorId, \"$donorId\" not found in supporter database"
|
|
|
|
unless $self->_verifyId($donorId);
|
2015-12-20 20:46:07 +00:00
|
|
|
|
2015-12-20 18:06:30 +00:00
|
|
|
$self->_beginWork;
|
2015-12-21 02:51:53 +00:00
|
|
|
eval {
|
|
|
|
$self->_getOrCreateRequestType($params);
|
|
|
|
$self->_getOrCreateRequestConfiguration($params) if (defined $params->{requestConfiguration} or
|
|
|
|
defined $params->{requestConfigurationId});
|
|
|
|
};
|
|
|
|
if ($@ or not defined $params->{requestTypeId}) {
|
|
|
|
my $err = $@;
|
|
|
|
$err = "addRequest: unable to create requestType" if (not defined $err);
|
|
|
|
$self->_rollback();
|
|
|
|
die $@ if $@;
|
|
|
|
}
|
2015-12-20 20:46:07 +00:00
|
|
|
|
|
|
|
# After those two calls above, I know I have requestTypeId and
|
|
|
|
# requestConfigurationId are accurate. Note that
|
|
|
|
# $params->{requestConfigurationId} can be undef, which is permitted in the
|
|
|
|
# database schema.
|
|
|
|
|
2015-12-30 11:10:07 +00:00
|
|
|
my $sth = $self->dbh->prepare("INSERT INTO request(donor_id, request_type_id, request_configuration_id, notes, date_requested) " .
|
2015-12-20 20:46:07 +00:00
|
|
|
"VALUES(?, ?, ?, ?, date('now'))");
|
2015-12-30 11:13:41 +00:00
|
|
|
$sth->execute($donorId, $params->{requestTypeId}, $params->{requestConfigurationId}, $params->{notes});
|
2015-12-20 20:46:07 +00:00
|
|
|
my $id = $self->dbh->last_insert_id("","","","");
|
2015-12-20 18:06:30 +00:00
|
|
|
$self->_commit;
|
2015-12-20 20:46:07 +00:00
|
|
|
return $id;
|
2015-12-20 18:06:30 +00:00
|
|
|
}
|
|
|
|
######################################################################
|
|
|
|
|
2015-12-20 22:01:15 +00:00
|
|
|
=begin fulfillRequest
|
2015-12-20 21:46:45 +00:00
|
|
|
|
|
|
|
Arguments:
|
|
|
|
|
|
|
|
=over
|
|
|
|
|
|
|
|
=item $parmas
|
|
|
|
|
|
|
|
A hash reference, the following keys are considered:
|
|
|
|
|
|
|
|
=over
|
|
|
|
|
2015-12-30 11:13:41 +00:00
|
|
|
=item donorId
|
2015-12-20 21:46:45 +00:00
|
|
|
|
2015-12-30 11:10:07 +00:00
|
|
|
Valid donor_id number currently in the database. die() will occur if
|
2015-12-20 21:46:45 +00:00
|
|
|
the id number is not in the database already as a supporter id.
|
|
|
|
|
|
|
|
=item requestType
|
|
|
|
|
|
|
|
requestType of the request to be fulfilled. die() will occur if this is
|
2015-12-20 22:01:15 +00:00
|
|
|
undefined. undef is returned if there is no unfulfilled request of
|
2015-12-20 21:46:45 +00:00
|
|
|
requestType in the database for supporter identified by
|
2015-12-30 11:13:41 +00:00
|
|
|
C<$params->{donorId}>
|
2015-12-20 21:46:45 +00:00
|
|
|
|
|
|
|
=item who
|
|
|
|
|
|
|
|
A scalar string representing the person that fulfilled the request. die()
|
|
|
|
will occur if C<$params->{who}> is not defined.
|
|
|
|
|
|
|
|
=item how
|
|
|
|
|
2015-12-20 22:01:15 +00:00
|
|
|
A scalar string describing how the request was fulfilled. It can safely be
|
2015-12-20 21:46:45 +00:00
|
|
|
undefined.
|
|
|
|
|
|
|
|
=back
|
|
|
|
|
|
|
|
=back
|
|
|
|
|
2016-01-20 01:49:20 +00:00
|
|
|
Returns the id value of the fulfillment entry. Note that value may be a
|
|
|
|
fulfillment id from a previous fulfillment (i.e., the request may have
|
|
|
|
already been fulfilled).
|
|
|
|
|
|
|
|
undef can be returned. Currently, undef is only returned if the request is
|
|
|
|
on hold.
|
2015-12-20 21:46:45 +00:00
|
|
|
|
|
|
|
=cut
|
|
|
|
|
2015-12-20 22:01:15 +00:00
|
|
|
sub fulfillRequest($$) {
|
2015-12-20 21:46:45 +00:00
|
|
|
my($self, $params) = @_;
|
2015-12-30 11:13:41 +00:00
|
|
|
die "fulfillRequest: undefined donorId" unless defined $params->{donorId};
|
|
|
|
my $donorId = $params->{donorId};
|
|
|
|
die "fulfillRequest: donorId, \"$donorId\" not found in supporter database"
|
|
|
|
unless $self->_verifyId($donorId);
|
2015-12-20 22:01:15 +00:00
|
|
|
die "fulfillRequest: undefined who" unless defined $params->{who};
|
2015-12-30 12:12:21 +00:00
|
|
|
die "fulfillRequest: both requestType and requestTypeId undefined"
|
|
|
|
unless defined $params->{requestType} or defined $params->{requestTypeId};
|
2015-12-20 21:46:45 +00:00
|
|
|
|
2015-12-30 11:58:48 +00:00
|
|
|
my $req = $self->getRequest($params);
|
2015-12-21 01:02:44 +00:00
|
|
|
return undef if not defined $req;
|
|
|
|
my $requestId = $req->{requestId};
|
2015-12-20 21:46:45 +00:00
|
|
|
return undef if not defined $requestId;
|
2015-12-20 21:59:50 +00:00
|
|
|
|
2015-12-21 00:58:13 +00:00
|
|
|
my $fulfillLookupSql = "SELECT id, request_id FROM fulfillment WHERE request_id = " .
|
2015-12-20 21:59:50 +00:00
|
|
|
$self->dbh->quote($requestId, 'SQL_INTEGER');
|
|
|
|
|
2015-12-20 22:01:15 +00:00
|
|
|
my $fulfillRecord = $self->dbh()->selectall_hashref($fulfillLookupSql, "request_id");
|
2015-12-21 00:58:13 +00:00
|
|
|
if (not defined $fulfillRecord or not defined $fulfillRecord->{$requestId}) {
|
2015-12-20 21:59:50 +00:00
|
|
|
$self->_beginWork;
|
2015-12-21 00:58:13 +00:00
|
|
|
my $sth = $self->dbh->prepare("INSERT INTO fulfillment(request_id, who, how, date) " .
|
|
|
|
"VALUES(? , ? , ? , date('now'))");
|
2015-12-20 21:59:50 +00:00
|
|
|
|
|
|
|
$sth->execute($requestId, $params->{who}, $params->{how});
|
|
|
|
$sth->finish;
|
|
|
|
$self->_commit;
|
2015-12-21 00:58:13 +00:00
|
|
|
$fulfillRecord = $self->dbh()->selectall_hashref($fulfillLookupSql, "request_id");
|
2015-12-20 21:59:50 +00:00
|
|
|
}
|
2015-12-21 00:58:13 +00:00
|
|
|
return $fulfillRecord->{$requestId}{id};
|
2015-12-20 21:46:45 +00:00
|
|
|
}
|
2015-12-30 14:09:43 +00:00
|
|
|
|
|
|
|
######################################################################
|
|
|
|
|
|
|
|
=begin findDonor
|
|
|
|
|
|
|
|
Arguments:
|
|
|
|
|
|
|
|
=over
|
|
|
|
|
|
|
|
=item $parmas
|
|
|
|
|
|
|
|
A hash reference, the following keys are considered, and are "anded" together
|
|
|
|
-- in that the donor sought must have all these criteria to be found.
|
|
|
|
|
2015-12-31 04:53:28 +00:00
|
|
|
If no criteria are given, all donors are returned.
|
|
|
|
|
2015-12-30 14:09:43 +00:00
|
|
|
=over
|
|
|
|
|
|
|
|
=item emailAddress
|
|
|
|
|
|
|
|
A string containing an email_address from email_address table.
|
|
|
|
|
|
|
|
=item ledgerEntityId
|
|
|
|
|
|
|
|
A string containing a ledger_entity_id from the donor table.
|
|
|
|
undefined. undef is returned if there is no unfulfilled request of
|
|
|
|
requestType in the database for supporter identified by
|
|
|
|
C<$params->{donorId}>
|
|
|
|
|
|
|
|
=back
|
|
|
|
|
|
|
|
=back
|
|
|
|
|
|
|
|
Returns a list of donorIds that meets the criteria, or none if not found.
|
|
|
|
|
|
|
|
=cut
|
|
|
|
|
|
|
|
sub findDonor($$) {
|
|
|
|
my($self, $params) = @_;
|
2015-12-31 04:53:28 +00:00
|
|
|
|
|
|
|
unless (defined $params->{ledgerEntityId} or defined $params->{emailAddress}) {
|
|
|
|
my $rr = $self->dbh()->selectall_hashref("SELECT id FROM donor", 'id');
|
|
|
|
return keys %$rr;
|
|
|
|
}
|
2015-12-30 14:09:43 +00:00
|
|
|
|
|
|
|
my @donorIds;
|
|
|
|
if (not defined $params->{emailAddress}) {
|
|
|
|
my $ledgerEntityId = $params->{ledgerEntityId};
|
|
|
|
# Simple case: just lookup without a join.
|
|
|
|
my $val = $self->dbh()->selectall_hashref("SELECT id, ledger_entity_id from donor where ledger_entity_id = " .
|
|
|
|
$self->dbh->quote($ledgerEntityId),
|
|
|
|
"ledger_entity_id");
|
|
|
|
# As Connor MacLeod said, "There can be only one!"
|
|
|
|
# (because of "ledger_entity_id" varchar(300) NOT NULL UNIQUE,)
|
|
|
|
push(@donorIds, $val->{$ledgerEntityId}{id})
|
|
|
|
if (defined $val and defined $val->{$ledgerEntityId} and defined $val->{$ledgerEntityId}{id});
|
|
|
|
} else {
|
|
|
|
my $sql = "SELECT d.id from donor d, email_address ea, donor_email_address_mapping eam " .
|
|
|
|
"WHERE eam.email_address_id = ea.id AND d.id = eam.donor_id AND " .
|
|
|
|
"ea.email_address = " . $self->dbh->quote($params->{emailAddress});
|
|
|
|
|
|
|
|
$sql .= " AND d.ledger_entity_id = " . $self->dbh->quote($params->{ledgerEntityId})
|
|
|
|
if (defined $params->{ledgerEntityId});
|
|
|
|
|
|
|
|
my $val = $self->dbh()->selectall_hashref($sql, 'id');
|
|
|
|
push(@donorIds, keys %{$val}) if (defined $val);
|
|
|
|
}
|
|
|
|
return(@donorIds);
|
|
|
|
}
|
2015-12-20 21:46:45 +00:00
|
|
|
######################################################################
|
|
|
|
|
2015-12-31 03:01:03 +00:00
|
|
|
=begin donorLastGave
|
|
|
|
|
|
|
|
Arguments:
|
|
|
|
|
|
|
|
=over
|
|
|
|
|
|
|
|
=item $self
|
|
|
|
|
|
|
|
Current object.
|
|
|
|
|
|
|
|
=item $donorId
|
|
|
|
|
|
|
|
Valid donor id number currently in the database. die() will occur if
|
|
|
|
the id number is not in the database already as a donor id.
|
|
|
|
|
|
|
|
=back
|
|
|
|
|
|
|
|
Returns an ISO 8601 formatted date of their last donation. undef will be
|
|
|
|
returned if the donor has never given (which should rarely be the case, but
|
|
|
|
it could happen).
|
|
|
|
|
|
|
|
=cut
|
|
|
|
|
|
|
|
sub donorLastGave($$) {
|
|
|
|
my($self, $donorId) = @_;
|
|
|
|
|
|
|
|
confess "lastGave: donorId, \"$donorId\" not found in supporter database"
|
|
|
|
unless $self->_verifyId($donorId);
|
|
|
|
|
|
|
|
$self->_readLedgerData() if not defined $self->{ledgerData};
|
|
|
|
|
|
|
|
my $ledgerEntityId = $self->getLedgerEntityId($donorId);
|
|
|
|
|
|
|
|
if (not defined $self->{ledgerData}{$ledgerEntityId} or
|
|
|
|
not defined $self->{ledgerData}{$ledgerEntityId}{__LAST_GAVE__} or
|
|
|
|
$self->{ledgerData}{$ledgerEntityId}{__LAST_GAVE__} eq '1975-01-01') {
|
|
|
|
return undef;
|
|
|
|
} else {
|
|
|
|
return $self->{ledgerData}{$ledgerEntityId}{__LAST_GAVE__};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
######################################################################
|
|
|
|
|
2015-12-31 03:32:31 +00:00
|
|
|
=begin donorFirstGave
|
|
|
|
|
|
|
|
Arguments:
|
|
|
|
|
|
|
|
=over
|
|
|
|
|
|
|
|
=item $self
|
|
|
|
|
|
|
|
Current object.
|
|
|
|
|
|
|
|
=item $donorId
|
|
|
|
|
|
|
|
Valid donor id number currently in the database. die() will occur if
|
|
|
|
the id number is not in the database already as a donor id.
|
|
|
|
|
|
|
|
=back
|
|
|
|
|
|
|
|
Returns an ISO 8601 formatted date of their first donation. undef will be
|
|
|
|
returned if the donor has never given (which should rarely be the case, but
|
|
|
|
it could happen).
|
|
|
|
|
|
|
|
=cut
|
|
|
|
|
|
|
|
sub donorFirstGave($$) {
|
|
|
|
my($self, $donorId) = @_;
|
|
|
|
|
|
|
|
confess "donorFirstGave: donorId, \"$donorId\" not found in supporter database"
|
|
|
|
unless $self->_verifyId($donorId);
|
|
|
|
|
|
|
|
$self->_readLedgerData() if not defined $self->{ledgerData};
|
|
|
|
|
|
|
|
my $ledgerEntityId = $self->getLedgerEntityId($donorId);
|
|
|
|
|
|
|
|
if (not defined $self->{ledgerData}{$ledgerEntityId} or
|
|
|
|
not defined $self->{ledgerData}{$ledgerEntityId}{__FIRST_GAVE__} or
|
|
|
|
$self->{ledgerData}{$ledgerEntityId}{__FIRST_GAVE__} eq '9999-12-31') {
|
|
|
|
return undef;
|
|
|
|
} else {
|
|
|
|
return $self->{ledgerData}{$ledgerEntityId}{__FIRST_GAVE__};
|
|
|
|
}
|
|
|
|
}
|
2015-12-31 04:11:57 +00:00
|
|
|
|
2016-01-07 21:35:24 +00:00
|
|
|
######################################################################
|
|
|
|
|
|
|
|
=begin donorTotalGaveInPeriod
|
|
|
|
|
|
|
|
Arguments:
|
|
|
|
|
|
|
|
=over
|
|
|
|
|
|
|
|
=item $self
|
|
|
|
|
|
|
|
Current object.
|
|
|
|
|
|
|
|
=item a list of arguments, which must be even and will be interpreted as a
|
|
|
|
hash, with the following keys relevant:
|
|
|
|
|
|
|
|
=item donorId
|
|
|
|
|
|
|
|
This mandatory key must have a value of a Valid donor id number currently
|
|
|
|
in the database. die() will occur if the id number is not in the database
|
|
|
|
already as a donor id.
|
|
|
|
|
|
|
|
=item startDate
|
|
|
|
|
|
|
|
This optional key, if given, must contain an ISO 8601 formatted date for the start
|
|
|
|
date of the period. die() may occur if not in ISO-8601 format.
|
|
|
|
|
|
|
|
=item endDate
|
|
|
|
|
|
|
|
This optional key, if given, must contain an ISO 8601 formatted date for the start
|
|
|
|
date of the period. die() may occur if not in ISO-8601 format.
|
|
|
|
|
|
|
|
=back
|
|
|
|
|
|
|
|
All other hash keys given generate a die().
|
|
|
|
|
|
|
|
=back
|
|
|
|
|
|
|
|
=cut
|
|
|
|
|
|
|
|
sub donorTotalGaveInPeriod($$) {
|
|
|
|
my $self = shift @_;
|
|
|
|
|
|
|
|
confess "donorTotalGaveInPeriod: arguments not in hash format" unless (scalar(@_) % 2) == 0;
|
|
|
|
my(%args) = @_;
|
|
|
|
|
|
|
|
my $donorId = $args{donorId}; delete $args{donorId};
|
|
|
|
|
|
|
|
confess "donorTotalGaveInPeriod: donorId, \"$donorId\" not found in supporter database"
|
|
|
|
unless $self->_verifyId($donorId);
|
|
|
|
|
|
|
|
# FIXME: Does not handle address before the Common Era
|
|
|
|
my $startDate = '0000-01-01';
|
|
|
|
if (defined $args{startDate}) { $startDate = $args{startDate}; delete $args{startDate}; }
|
|
|
|
|
|
|
|
# FIXME: Year 10,000 problem!
|
|
|
|
|
|
|
|
my $endDate = '9999-12-31';
|
|
|
|
if (defined $args{endDate}) { $endDate = $args{endDate}; delete $args{endDate}; }
|
|
|
|
|
|
|
|
my(@argKeys) = keys %args;
|
|
|
|
confess("Unknown arugments: ". join(", ", @argKeys)) if @argKeys > 0;
|
|
|
|
|
|
|
|
foreach my $date ($startDate, $endDate) {
|
|
|
|
confess "donorTotalGaveInPeriod: invalid date in argument list, \"$date\""
|
|
|
|
unless $date =~ /^\d{4,4}-\d{2,2}-\d{2,2}/;
|
|
|
|
# FIXME: check better for ISO-8601.
|
|
|
|
}
|
|
|
|
$self->_readLedgerData() if not defined $self->{ledgerData};
|
|
|
|
|
|
|
|
my $entityId = $self->getLedgerEntityId($donorId);
|
|
|
|
my $amount = 0.00;
|
|
|
|
|
|
|
|
foreach my $date (keys %{$self->{ledgerData}{$entityId}{donations}}) {
|
|
|
|
next if $date =~ /^__/;
|
|
|
|
$amount += $self->{ledgerData}{$entityId}{donations}{$date}
|
|
|
|
if $date ge $startDate and $date le $endDate;
|
|
|
|
}
|
|
|
|
return $amount;
|
|
|
|
}
|
|
|
|
######################################################################
|
|
|
|
|
2015-12-31 04:11:57 +00:00
|
|
|
=begin supporterExpirationDate
|
|
|
|
|
|
|
|
Arguments:
|
|
|
|
|
|
|
|
=over
|
|
|
|
|
|
|
|
=item $self
|
|
|
|
|
|
|
|
Current object.
|
|
|
|
|
|
|
|
=item $donorId
|
|
|
|
|
|
|
|
Valid donor id number currently in the database. die() will occur if
|
|
|
|
the id number is not in the database already as a donor id.
|
|
|
|
|
|
|
|
=back
|
|
|
|
|
|
|
|
Returns an ISO 8601 of the expriation date for the supporter identified by
|
|
|
|
donorId. Returns undef if the donor is not a supporter or if the donor has
|
|
|
|
given no donations at all.
|
|
|
|
|
|
|
|
Formula for expiration dates currently is as follows:
|
|
|
|
|
|
|
|
For annuals, consider donations in the last year only. The expiration date
|
|
|
|
is one year from the last donation if the total in the last year >= $120.00
|
|
|
|
|
|
|
|
For monthlies, see if they gave $10 or more in the last 60 days. If they
|
|
|
|
did, their expiration is 60 days from then.
|
|
|
|
|
|
|
|
=cut
|
|
|
|
|
|
|
|
|
|
|
|
my $ONE_YEAR_AGO = UnixDate(DateCalc(ParseDate("today"), "- 1 year"), '%Y-%m-%d');
|
|
|
|
my $SIXTY_DAYS_AGO = UnixDate(DateCalc(ParseDate("today"), "- 60 days"), '%Y-%m-%d');
|
|
|
|
|
|
|
|
sub supporterExpirationDate($$) {
|
|
|
|
my($self, $donorId) = @_;
|
|
|
|
|
|
|
|
confess "donorFirstGave: donorId, \"$donorId\" not found in supporter database"
|
|
|
|
unless $self->_verifyId($donorId);
|
|
|
|
|
|
|
|
return undef unless $self->isSupporter($donorId);
|
|
|
|
$self->_readLedgerData() if not defined $self->{ledgerData};
|
|
|
|
|
|
|
|
|
|
|
|
my $entityId = $self->getLedgerEntityId($donorId);
|
|
|
|
|
|
|
|
return undef unless defined $self->{ledgerData}{$entityId};
|
|
|
|
|
|
|
|
my $expirationDate;
|
|
|
|
|
|
|
|
my $type = $self->{ledgerData}{$entityId}{__TYPE__};
|
|
|
|
if ($type eq 'Monthly') {
|
|
|
|
my(@tenOrMore);
|
|
|
|
foreach my $date (keys %{$self->{ledgerData}{$entityId}{donations}}) {
|
|
|
|
next if $date =~ /^__/;
|
|
|
|
push(@tenOrMore, $date) unless ($self->{ledgerData}{$entityId}{donations}{$date} < 10.00);
|
|
|
|
}
|
|
|
|
$expirationDate = UnixDate(DateCalc(maxstr(@tenOrMore), "+ 60 days"), '%Y-%m-%d')
|
|
|
|
if (scalar(@tenOrMore) > 0);
|
|
|
|
|
|
|
|
} elsif ($type eq 'Annual') {
|
|
|
|
my($earliest, $total) = (undef, 0.00);
|
2015-12-31 05:26:48 +00:00
|
|
|
foreach my $date (sort { $b cmp $a} keys %{$self->{ledgerData}{$entityId}{donations}}) {
|
2015-12-31 04:11:57 +00:00
|
|
|
next if $date =~ /^__/;
|
|
|
|
$total += $self->{ledgerData}{$entityId}{donations}{$date};
|
|
|
|
unless ($total < 120.00) {
|
|
|
|
$earliest = $date;
|
|
|
|
last;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$expirationDate = UnixDate(DateCalc($earliest, "+ 1 year"), '%Y-%m-%d')
|
|
|
|
if defined $earliest;
|
|
|
|
} else {
|
|
|
|
confess "supporterExpirationDate: does not function on $type";
|
|
|
|
}
|
|
|
|
return $expirationDate;
|
|
|
|
}
|
2015-12-31 03:32:31 +00:00
|
|
|
######################################################################
|
|
|
|
|
2015-12-21 01:11:01 +00:00
|
|
|
=back
|
|
|
|
|
2015-12-10 02:38:07 +00:00
|
|
|
=head1 Non-Public Methods
|
|
|
|
|
|
|
|
These methods are part of the internal implementation are not recommended for
|
|
|
|
use outside of this module.
|
|
|
|
|
|
|
|
=over
|
|
|
|
|
2015-12-31 03:01:03 +00:00
|
|
|
=item _readLedgerData
|
|
|
|
|
|
|
|
=cut
|
|
|
|
|
|
|
|
sub _readLedgerData($) {
|
|
|
|
my($self) = @_;
|
|
|
|
|
|
|
|
my @cmd = @{$self->{ledgerCmd}};
|
|
|
|
my %amountTable;
|
|
|
|
|
|
|
|
open(ALL, "-|", @cmd) or confess "unable to run command ledger command: @cmd: $!";
|
|
|
|
while (my $line = <ALL>) {
|
|
|
|
next if $line =~ /^\s*$/;
|
|
|
|
die "Invalid line in @cmd output:\n $line"
|
2016-01-19 05:10:47 +00:00
|
|
|
unless $line =~ /^\s*([^\d]+)\s+([\d\-]+)\s+(\S*)\s+\$\s*(\-?\s*[\d,\.]+)\s*$/;
|
2015-12-31 03:01:03 +00:00
|
|
|
my($type, $date, $entityId, $amount) = ($1, $2, $3, $4);
|
2016-01-19 05:10:47 +00:00
|
|
|
next unless defined $entityId and $entityId !~ /^\s*$/;
|
2015-12-31 03:01:03 +00:00
|
|
|
if (defined $self->{programTypeSearch}) {
|
|
|
|
if ($type =~ /$self->{programTypeSearch}{annual}/) {
|
|
|
|
$type = 'Annual';
|
|
|
|
} elsif ($type =~ /$self->{programTypeSearch}{monthly}/) {
|
|
|
|
$type = 'Monthly';
|
|
|
|
}
|
|
|
|
}
|
|
|
|
die "Unknown type $type for $entityId from $line" if $type !~ /^(Monthly|Annual)$/ and defined $self->{programTypeSearch};
|
|
|
|
$amount =~ s/,//; $amount = abs($amount);
|
|
|
|
if (defined $amountTable{$entityId}{donations}{$date}) {
|
|
|
|
$amountTable{$entityId}{donations}{$date} += $amount;
|
|
|
|
} else {
|
|
|
|
$amountTable{$entityId}{donations}{$date} = $amount;
|
|
|
|
}
|
|
|
|
unless (defined $amountTable{$entityId}{__TOTAL__}) {
|
|
|
|
$amountTable{$entityId}{__TOTAL__} = 0.00;
|
|
|
|
$amountTable{$entityId}{__LAST_GAVE__} = '1975-01-01';
|
|
|
|
$amountTable{$entityId}{__FIRST_GAVE__} = '9999-12-31';
|
|
|
|
}
|
|
|
|
$amountTable{$entityId}{__TOTAL__} += $amount;
|
|
|
|
if ($date gt $amountTable{$entityId}{__LAST_GAVE__}) {
|
|
|
|
# Consider the "type" of the donor to be whatever type they were at last donation
|
|
|
|
$amountTable{$entityId}{__TYPE__} = $type;
|
|
|
|
$amountTable{$entityId}{__LAST_GAVE__} = $date;
|
|
|
|
}
|
|
|
|
$amountTable{$entityId}{__FIRST_GAVE__} = $date
|
|
|
|
if $date lt $amountTable{$entityId}{__FIRST_GAVE__};
|
|
|
|
}
|
|
|
|
close ALL; die "error($?) running command, @cmd: $!" unless $? == 0;
|
|
|
|
$self->{ledgerData} = \%amountTable;
|
|
|
|
}
|
|
|
|
|
2015-12-21 02:52:08 +00:00
|
|
|
=item DESTROY
|
|
|
|
|
|
|
|
=cut
|
|
|
|
|
|
|
|
sub DESTROY {
|
|
|
|
my $self = shift;
|
|
|
|
return unless defined $self;
|
|
|
|
|
|
|
|
# Force rollback if we somehow get destroy'ed while counter is up
|
|
|
|
if (defined $self->{__NESTED_TRANSACTION_COUNTER__} and $self->{__NESTED_TRANSACTION_COUNTER__} > 0) {
|
|
|
|
my $errorStr = "SUPPORTERS DATABASE ERROR: Mismatched begin_work/commit pair in API implementation";
|
|
|
|
if (not defined $self->{dbh}) {
|
|
|
|
$errorStr .= "... and unable to rollback or commit work. Database may very well be inconsistent!";
|
|
|
|
} else {
|
|
|
|
# Rollback if we didn't call commit enough; commit if we called commit too often.
|
|
|
|
($self->{__NESTED_TRANSACTION_COUNTER__} > 0) ? $self->_rollback() : $self->_commit();
|
|
|
|
$self->{dbh}->disconnect();
|
|
|
|
}
|
|
|
|
$self->{__NESTED_TRANSACTION_COUNTER__} = 0;
|
|
|
|
die $errorStr;
|
|
|
|
}
|
|
|
|
delete $self->{__NESTED_TRANSACTION_COUNTER__};
|
|
|
|
$self->{dbh}->disconnect() if defined $self->{dbh} and blessed($self->{dbh}) =~ /DBI/;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2015-12-10 02:38:07 +00:00
|
|
|
=item _verifyId()
|
|
|
|
|
|
|
|
Parameters:
|
|
|
|
|
|
|
|
=over
|
|
|
|
|
|
|
|
=item $self: current object.
|
|
|
|
|
|
|
|
=item $id: A scalar numeric argument that is the to lookup
|
|
|
|
|
|
|
|
|
|
|
|
=back
|
|
|
|
|
|
|
|
Returns: scalar boolean, which is true iff. the $id is valid and already in the supporter database.
|
|
|
|
|
|
|
|
|
|
|
|
=cut
|
|
|
|
|
|
|
|
|
2015-12-13 20:15:50 +00:00
|
|
|
sub _verifyId($$) {
|
2015-12-10 02:38:07 +00:00
|
|
|
my($self, $id) = @_;
|
|
|
|
|
2015-12-31 00:09:59 +00:00
|
|
|
confess "_verifyId(): called with a non-numeric id" unless defined $id and looks_like_number($id);
|
2015-12-10 02:38:07 +00:00
|
|
|
|
2015-12-30 11:10:07 +00:00
|
|
|
my $val = $self->dbh()->selectall_hashref("SELECT id FROM donor WHERE id = " .
|
2015-12-18 03:54:37 +00:00
|
|
|
$self->dbh->quote($id, 'SQL_INTEGER'), 'id');
|
2015-12-10 02:38:07 +00:00
|
|
|
return (defined $val and defined $val->{$id});
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2015-12-30 11:55:28 +00:00
|
|
|
=item _lookupRequestTypeById()
|
2015-12-20 19:07:06 +00:00
|
|
|
|
|
|
|
Parameters:
|
|
|
|
|
|
|
|
=over
|
|
|
|
|
|
|
|
=item $self: current object.
|
|
|
|
|
|
|
|
=item $requestTypeId: A scalar numeric argument that is the request type id to lookup
|
|
|
|
|
|
|
|
|
|
|
|
=back
|
|
|
|
|
2015-12-30 11:55:28 +00:00
|
|
|
Returns: scalar, which is the request_type found iff. the C<$requestTypeId> is valid and
|
2015-12-20 19:07:06 +00:00
|
|
|
already in the supporter database's request_type table.
|
|
|
|
|
2015-12-30 11:55:28 +00:00
|
|
|
Die if the C<$requestTypeId> isn't a number.
|
2015-12-20 19:07:06 +00:00
|
|
|
|
|
|
|
=cut
|
|
|
|
|
|
|
|
|
2015-12-30 11:55:28 +00:00
|
|
|
sub _lookupRequestTypeById($$) {
|
2015-12-20 19:07:06 +00:00
|
|
|
my($self, $requestTypeId) = @_;
|
|
|
|
|
|
|
|
die "_verifyRequestTypeId() called with a non-numeric id" unless defined $requestTypeId and looks_like_number($requestTypeId);
|
|
|
|
|
2015-12-30 11:55:28 +00:00
|
|
|
my $val = $self->dbh()->selectall_hashref("SELECT id, type FROM request_type WHERE id = " .
|
2015-12-20 19:07:06 +00:00
|
|
|
$self->dbh->quote($requestTypeId, 'SQL_INTEGER'), 'id');
|
2015-12-30 11:55:28 +00:00
|
|
|
if (defined $val and defined $val->{$requestTypeId}) {
|
|
|
|
return $val->{$requestTypeId}{type};
|
|
|
|
} else {
|
|
|
|
return undef;
|
|
|
|
}
|
2015-12-20 19:07:06 +00:00
|
|
|
}
|
2015-12-30 13:33:05 +00:00
|
|
|
######################################################################
|
|
|
|
|
2015-12-30 13:41:10 +00:00
|
|
|
=item _lookupEmailAddress()
|
2015-12-30 13:33:05 +00:00
|
|
|
|
|
|
|
Parameters:
|
|
|
|
|
|
|
|
=over
|
|
|
|
|
|
|
|
=item $self: current object.
|
|
|
|
|
|
|
|
=item $emailAdress: A scalar string argument that is the email_adress
|
|
|
|
|
|
|
|
|
|
|
|
=back
|
|
|
|
|
2015-12-30 13:41:10 +00:00
|
|
|
Returns: undef if the email address is not found, otherwise a hash with the following values:
|
|
|
|
|
|
|
|
=over
|
|
|
|
|
|
|
|
=item emailAddress: The email address as given
|
|
|
|
|
|
|
|
=item id: The email_adress.id
|
|
|
|
|
|
|
|
=item type: The email_adress type
|
|
|
|
|
|
|
|
=item dateEncountered: The date_encountered of this email address.
|
|
|
|
|
|
|
|
=back
|
2015-12-30 13:33:05 +00:00
|
|
|
|
|
|
|
=cut
|
|
|
|
|
|
|
|
|
2015-12-30 13:41:10 +00:00
|
|
|
sub _lookupEmailAddress($$) {
|
2015-12-30 13:33:05 +00:00
|
|
|
my($self, $emailAddress) = @_;
|
|
|
|
|
|
|
|
die "_lookupEmailAddressId() called with undef" unless defined $emailAddress;
|
|
|
|
|
2015-12-30 13:41:10 +00:00
|
|
|
my $val = $self->dbh()->selectall_hashref("SELECT ea.id, ea.email_address, at.name, ea.date_encountered " .
|
|
|
|
"FROM email_address ea, address_type at " .
|
|
|
|
"WHERE ea.type_id = at.id AND " .
|
|
|
|
"email_address = " . $self->dbh->quote($emailAddress),
|
|
|
|
'email_address');
|
2015-12-30 13:33:05 +00:00
|
|
|
if (defined $val and defined $val->{$emailAddress}) {
|
2015-12-30 13:41:10 +00:00
|
|
|
return { id => $val->{$emailAddress}{id}, emailAddress => $val->{$emailAddress}{email_address},
|
|
|
|
type => $val->{$emailAddress}{name}, dateEncountered => $val->{$emailAddress}{date_encountered}};
|
2015-12-30 13:33:05 +00:00
|
|
|
} else {
|
|
|
|
return undef;
|
|
|
|
}
|
|
|
|
}
|
2015-12-20 19:07:06 +00:00
|
|
|
|
2015-12-18 04:15:41 +00:00
|
|
|
=item _getOrCreateRequestType
|
|
|
|
|
|
|
|
Arguments:
|
|
|
|
|
|
|
|
=over
|
|
|
|
|
|
|
|
=item $params (hash reference)
|
|
|
|
|
|
|
|
This hash reference usually contains other paramaters, too, but this method
|
|
|
|
looks only at the keys C<requestType> and C<requestTypeId>. If
|
|
|
|
C<requestTypeId> is set, it simply deletes the C<requestType> parameter and
|
|
|
|
verifies c<reuqestTypeId> is in the request_type table.
|
|
|
|
|
|
|
|
=cut
|
|
|
|
|
|
|
|
sub _getOrCreateRequestType($$) {
|
|
|
|
my($self, $params) = @_;
|
|
|
|
|
|
|
|
if (not defined $params->{requestTypeId}) {
|
|
|
|
$params->{requestTypeId} = $self->addRequestType($params->{requestType});
|
|
|
|
} else {
|
|
|
|
my $id = $params->{requestTypeId};
|
2015-12-30 11:33:36 +00:00
|
|
|
die "_getOrCreateRequestType(): invalid requestTypeId, \"$id\""
|
2015-12-30 11:57:19 +00:00
|
|
|
unless defined $self->_lookupRequestTypeById($id);
|
2015-12-18 04:15:41 +00:00
|
|
|
}
|
|
|
|
delete $params->{requestType};
|
|
|
|
}
|
|
|
|
|
2015-12-20 19:11:42 +00:00
|
|
|
=item _getOrCreateRequestConfiguration
|
|
|
|
|
|
|
|
Arguments:
|
|
|
|
|
|
|
|
=over
|
|
|
|
|
|
|
|
=item $params (hash reference)
|
|
|
|
|
|
|
|
This hash reference usually contains other paramaters, too, but this method
|
|
|
|
looks only at the keys C<requestTypeId>, C<requestConfiguration> and
|
|
|
|
C<requestConfigurationId>. If C<requestConfigurationId> is set, it simply
|
|
|
|
deletes the C<requestConfiguration> parameter and verifies c<reuqestTypeId>
|
|
|
|
is in the request_type table.
|
|
|
|
|
|
|
|
=cut
|
|
|
|
|
|
|
|
sub _getOrCreateRequestConfiguration($$) {
|
|
|
|
my($self, $params) = @_;
|
|
|
|
|
|
|
|
die "_getOrCreateRequestConfiguration: requestTypeId is required" unless defined $params->{requestTypeId};
|
2015-12-20 19:14:13 +00:00
|
|
|
my $requestTypeId = $params->{requestTypeId};
|
|
|
|
die "_getOrCreateRequestConfiguration: requestTypeId must be a number" unless looks_like_number($requestTypeId);
|
2015-12-20 20:10:27 +00:00
|
|
|
|
|
|
|
my $val = $self->dbh()->selectall_hashref("SELECT id, type FROM request_type WHERE id = " .
|
|
|
|
$self->dbh->quote($requestTypeId, 'SQL_INTEGER'), 'id');
|
|
|
|
die "_getOrCreateRequestConfiguration: unknown requestTypeId, \"$requestTypeId\""
|
|
|
|
unless (defined $val and defined $val->{$requestTypeId} and defined $val->{$requestTypeId}{type});
|
|
|
|
my $requestType = $val->{$requestTypeId}{type};
|
|
|
|
|
|
|
|
my $existingRequestConfig = $self->getRequestConfigurations($requestType);
|
|
|
|
|
|
|
|
die "_getOrCreateRequestConfiguration: requestTypeId is unknown" unless (keys(%$existingRequestConfig) == 1);
|
2015-12-20 19:11:42 +00:00
|
|
|
|
2015-12-20 19:20:16 +00:00
|
|
|
if (not defined $params->{requestConfigurationId}) {
|
2015-12-20 20:10:27 +00:00
|
|
|
die "_getOrCreateRequestConfiguration: requestConfiguration is not defined" unless defined $params->{requestConfiguration};
|
|
|
|
if (defined $existingRequestConfig->{$requestTypeId}{$params->{requestConfiguration}}) {
|
|
|
|
$params->{requestConfigurationId} = $existingRequestConfig->{$requestTypeId}{$params->{requestConfiguration}};
|
|
|
|
} else {
|
|
|
|
$existingRequestConfig = $self->addRequestConfigurations($requestType, [ $params->{requestConfiguration} ]);
|
|
|
|
$params->{requestConfigurationId} = $existingRequestConfig->{$requestTypeId}{$params->{requestConfiguration}};
|
|
|
|
}
|
2015-12-20 19:20:16 +00:00
|
|
|
} else {
|
|
|
|
my $id = $params->{requestConfigurationId};
|
|
|
|
die "_getOrCreateRequestConfiguration(): called with a non-numeric requestConfigurationId, \"$id\""
|
|
|
|
unless defined $id and looks_like_number($id);
|
2015-12-20 20:10:27 +00:00
|
|
|
my $found = 0;
|
|
|
|
foreach my $foundId (values %{$existingRequestConfig->{$requestTypeId}}) { if ($foundId == $id) { $found = 1; last; } }
|
2015-12-20 19:20:16 +00:00
|
|
|
die "_getOrCreateRequestType(): given requestConfigurationId, \"$id\", is invalid"
|
2015-12-20 20:10:27 +00:00
|
|
|
unless defined $found;
|
2015-12-20 19:20:16 +00:00
|
|
|
}
|
|
|
|
delete $params->{requestConfiguration};
|
|
|
|
return $params->{requestConfigurationId};
|
2015-12-20 19:11:42 +00:00
|
|
|
}
|
|
|
|
|
2015-12-15 01:16:35 +00:00
|
|
|
=item _beginWork()
|
|
|
|
|
|
|
|
Parameters:
|
|
|
|
|
|
|
|
=over
|
|
|
|
|
|
|
|
=item $self: current object.
|
|
|
|
|
|
|
|
=back
|
|
|
|
|
|
|
|
Returns: None.
|
|
|
|
|
|
|
|
This method is a reference counter to keep track of nested begin_work()/commit().
|
|
|
|
|
|
|
|
|
|
|
|
=cut
|
|
|
|
|
|
|
|
sub _beginWork($) {
|
|
|
|
my($self) = @_;
|
|
|
|
|
2015-12-20 20:21:38 +00:00
|
|
|
if ($self->{__NESTED_TRANSACTION_COUNTER__} < 0) {
|
2015-12-17 03:57:59 +00:00
|
|
|
die "_beginWork: Mismatched begin_work/commit pair in API implementation";
|
2015-12-20 20:21:38 +00:00
|
|
|
$self->{__NESTED_TRANSACTION_COUNTER__} = 0;
|
2015-12-17 03:57:59 +00:00
|
|
|
}
|
2015-12-20 20:21:38 +00:00
|
|
|
$self->dbh->begin_work() if ($self->{__NESTED_TRANSACTION_COUNTER__}++ == 0);
|
2015-12-15 01:16:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
=item _commit()
|
|
|
|
|
|
|
|
Parameters:
|
|
|
|
|
|
|
|
=over
|
|
|
|
|
|
|
|
=item $self: current object.
|
|
|
|
|
|
|
|
=back
|
|
|
|
|
|
|
|
Returns: None.
|
|
|
|
|
|
|
|
This method is a reference counter to keep track of nested begin_work()
|
|
|
|
transactions to verify we don't nest $self->dbh->begin_work()
|
|
|
|
|
|
|
|
=cut
|
|
|
|
|
|
|
|
sub _commit($) {
|
|
|
|
my($self) = @_;
|
|
|
|
|
2015-12-20 20:21:38 +00:00
|
|
|
if ($self->{__NESTED_TRANSACTION_COUNTER__} < 0) {
|
2015-12-17 03:57:59 +00:00
|
|
|
die "_commit: Mismatched begin_work/commit pair in API implementation";
|
2015-12-20 20:21:38 +00:00
|
|
|
$self->{__NESTED_TRANSACTION_COUNTER__} = 0;
|
2015-12-17 03:57:59 +00:00
|
|
|
}
|
2015-12-20 20:21:38 +00:00
|
|
|
$self->dbh->commit() if (--$self->{__NESTED_TRANSACTION_COUNTER__} == 0);
|
2015-12-17 03:57:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
=item _rollback()
|
|
|
|
|
|
|
|
Parameters:
|
|
|
|
|
|
|
|
=over
|
|
|
|
|
|
|
|
=item $self: current object.
|
|
|
|
|
|
|
|
=back
|
|
|
|
|
|
|
|
Returns: None.
|
|
|
|
|
|
|
|
This method resets the reference counter entirely and calls $dbh->rollback.
|
|
|
|
|
|
|
|
=cut
|
|
|
|
|
|
|
|
sub _rollback($) {
|
|
|
|
my($self) = @_;
|
2015-12-15 01:16:35 +00:00
|
|
|
|
2015-12-20 20:21:38 +00:00
|
|
|
$self->{__NESTED_TRANSACTION_COUNTER__} = 0;
|
2015-12-17 03:57:59 +00:00
|
|
|
$self->dbh->rollback();
|
2015-12-15 01:16:35 +00:00
|
|
|
}
|
2015-12-10 02:38:07 +00:00
|
|
|
|
|
|
|
=back
|
|
|
|
|
|
|
|
=cut
|
2015-12-07 01:20:14 +00:00
|
|
|
|
|
|
|
1;
|
|
|
|
__END__
|
|
|
|
|
|
|
|
=head1 NAME
|
|
|
|
|
2015-12-07 01:27:13 +00:00
|
|
|
Supporters - Simple database of supporters of an organation.
|
2015-12-07 01:20:14 +00:00
|
|
|
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
|
|
|
|
use Supporters;
|
|
|
|
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
|
2015-12-07 01:27:13 +00:00
|
|
|
Supporters is an extremely lightweight alternative to larger systems like
|
|
|
|
CiviCRM to manage a database of Supporters. The module assumes a setup that
|
|
|
|
works with Ledger-CLI to find the actual amounts donated.
|
2015-12-07 01:20:14 +00:00
|
|
|
|
|
|
|
=head2 EXPORT
|
|
|
|
|
|
|
|
None by default.
|
|
|
|
|
|
|
|
=head1 AUTHOR
|
|
|
|
|
2015-12-07 01:27:13 +00:00
|
|
|
Bradley M. Kuhn, E<lt>bkuhn@ebb.org<gt>
|
2015-12-07 01:20:14 +00:00
|
|
|
|
|
|
|
=head1 COPYRIGHT AND LICENSE
|
|
|
|
|
2015-12-07 01:27:13 +00:00
|
|
|
See COPYRIGHT.md and LICENSE.md in the main distribution of this software.
|
2015-12-07 01:20:14 +00:00
|
|
|
|
2015-12-07 01:27:13 +00:00
|
|
|
License: AGPLv3-or-later
|
2015-12-07 01:20:14 +00:00
|
|
|
|
|
|
|
=cut
|
2015-12-10 02:36:57 +00:00
|
|
|
|
|
|
|
###############################################################################
|
|
|
|
#
|
|
|
|
# Local variables:
|
|
|
|
# compile-command: "perl -c Supporters.pm"
|
|
|
|
# End:
|