From 4f2498b27b0731cda3bd2f3689180976ce183a20 Mon Sep 17 00:00:00 2001 From: Jon Knight Date: Thu, 23 Aug 2018 13:02:24 +0000 Subject: [PATCH] Bug 20028: Export all patron related personal data in one package Modifies the readingrec.pl script to give the option to generate a JSON encoded version of the data held about the patron. Currently exports basic patron (borrower table) data, patron image, holds (reserves), old holds, checkouts (issues), old checkouts and accounting information. Test plan: Turn on the syspref, select a patron from the staff intranet, and then choose "Export patron's GDPR data" from the "More..." menu. You should then get a JSON file downloaded to the machine running your web browser. Compare the data in that file with the data shown on the intranet displays for that patron. --- members/readingrec.pl | 252 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 252 insertions(+) diff --git a/members/readingrec.pl b/members/readingrec.pl index 015c56c..bb15774 100755 --- a/members/readingrec.pl +++ b/members/readingrec.pl @@ -30,9 +30,12 @@ use C4::Members; use List::MoreUtils qw/any uniq/; use Koha::DateUtils; use C4::Members::Attributes qw(GetBorrowerAttributes); +use JSON; +use Data::Dumper; use Koha::Patrons; use Koha::Patron::Categories; +use Koha::Account::Lines; my $input = CGI->new; @@ -92,6 +95,255 @@ if ( $op eq 'export_barcodes' ) { } } +if($op eq 'export_gdprdata') { + my $exportedData = { + 'patron' => { + 'title' => $patron->title, + 'surname' => $patron->surname, + 'firstname' => $patron->firstname, + 'othernames' => $patron->othernames, + 'initials' => $patron->initials, + 'streetnumber' => $patron->streetnumber, + 'streettype' => $patron->streettype, + 'address' => $patron->address, + 'address2' => $patron->address2, + 'city' => $patron->city, + 'state' => $patron->state, + 'zipcode' => $patron->zipcode, + 'country' => $patron->country, + 'category_type' => $patron->category->category_type, + 'email' => $patron->email, + 'phone' => $patron->phone, + 'mobile' => $patron->mobile, + 'fax' => $patron->fax, + 'emailpro' => $patron->emailpro, + 'phonepro' => $patron->phonepro, + 'B_streetnumber' => $patron->B_streetnumber, + 'B_streettype' => $patron->B_streettype, + 'B_address' => $patron->B_address, + 'B_address2' => $patron->B_address2, + 'B_city' => $patron->B_city, + 'B_state' => $patron->B_state, + 'B_zipcode' => $patron->B_zipcode, + 'B_country' => $patron->B_country, + 'B_email' => $patron->B_email, + 'B_phone' => $patron->B_phone, + 'dateoffbirth' => $patron->dateofbirth, + 'categorycode' => $patron->categorycode, + 'dateenrolled' => $patron->dateenrolled, + 'dateexpiry' => $patron->dateexpiry, + 'date_renewed' => $patron->date_renewed, + 'gonenoaddress' => $patron->gonenoaddress, + 'lost' => $patron->lost, + 'debarred' => $patron->debarred, + 'debarredcomment' => $patron->debarredcomment, + 'contactname' => $patron->contactname, + 'contactfirstname' => $patron->contactfirstname, + 'contacttitle' => $patron->contacttitle, + 'borrowernotes' => $patron->borrowernotes, + 'sex' => $patron->sex, + 'userid' => $patron->userid, + 'opacnote' => $patron->opacnote, + 'contactnote' => $patron->contactnote, + 'altcontactfirstname' => $patron->altcontactfirstname, + 'altcontactsurname' => $patron->altcontactsurname, + 'altcontactaddress1' => $patron->altcontactaddress1, + 'altcontactaddress2' => $patron->altcontactaddress2, + 'altcontactaddress3' => $patron->altcontactaddress3, + 'altcontactstate' => $patron->altcontactstate, + 'altcontactzipcode' => $patron->altcontactzipcode, + 'altcontactcountry' => $patron->altcontactcountry, + 'altcontactphone' => $patron->altcontactphone, + 'smsalertnumber' => $patron->smsalertnumber, + 'updated_on' => $patron->updated_on, + 'lastseen' => $patron->lastseen, + 'lang' => $patron->lang, + 'login_attempts' => $patron->login_attempts, + 'account_locked' => $patron->account_locked, + 'age' => $patron->get_age, + 'has_overdues' => $patron->has_overdues, + 'borrowernumber' => $patron->borrowernumber, + 'cardnumber' => $patron->cardnumber, + 'branchcode' => $patron->branchcode, + }, + }; + + my $guarantor = $patron->guarantor(); + if($guarantor) { + $exportedData->{'patron'}->{'guarantor'} = + $guarantor->{'borrowernumber'}; + } + my @guarantees = $patron->guarantees(); + if(@guarantees) { + foreach my $bod (@guarantees) { + push @{$exportedData->{'patron'}->{'guarantees'}}, $bod->borrowernumber; + } + } + + my $image = $patron->image; + if($image) { + $exportedData->{'image'} = { + 'mimetype' => $image->mimetype, + 'imagefile' => $image->imagefile, + }; + } + + my $holds = $patron->holds; + if($holds) { + while(my $hold = $holds->next()) { + push @{$exportedData->{'holds'}}, { + 'reserve_id' => $hold->reserve_id, + 'reservedate' => $hold->reservedate, + 'biblionumber' => $hold->biblionumber, + 'branchcode' => $hold->branchcode, + 'nofificationdate' => $hold->notificationdate, + 'reminderdate' => $hold->reminderdate, + 'cancellationdate' => $hold->cancellationdate, + 'reservenotes' => $hold->reservenotes, + 'priority' => $hold->priority, + 'found' => $hold->found, + 'timestamp' => $hold->timestamp, + 'itemnumber' => $hold->itemnumber, + 'waitingdate' => $hold->waitingdate, + 'expirationdate' => $hold->expirationdate, + 'lowestPriority' => $hold->lowestPriority, + 'suspend' => $hold->suspend, + 'suspend_until' => $hold->suspend_until, + 'itemtype' => $hold->itemtype, + } + } + } + + my $oldHolds = $patron->old_holds; + if($oldHolds) { + while(my $hold = $oldHolds->next()) { + push @{$exportedData->{'holds'}}, { + 'reserve_id' => $hold->reserve_id, + 'reservedate' => $hold->reservedate, + 'biblionumber' => $hold->biblionumber, + 'branchcode' => $hold->branchcode, + 'nofificationdate' => $hold->notificationdate, + 'reminderdate' => $hold->reminderdate, + 'cancellationdate' => $hold->cancellationdate, + 'reservenotes' => $hold->reservenotes, + 'priority' => $hold->priority, + 'found' => $hold->found, + 'timestamp' => $hold->timestamp, + 'itemnumber' => $hold->itemnumber, + 'waitingdate' => $hold->waitingdate, + 'expirationdate' => $hold->expirationdate, + 'lowestPriority' => $hold->lowestPriority, + 'suspend' => $hold->suspend, + 'suspend_until' => $hold->suspend_until, + 'itemtype' => $hold->itemtype, + } + } + } + + my $checkouts = $patron->checkouts; + if($checkouts) { + while(my $checkout = $checkouts->next()) { + push @{$exportedData->{'checkouts'}}, { + 'issue_id' => $checkout->issue_id, + 'itemnumber' => $checkout->itemnumber, + 'date_due' => $checkout->date_due, + 'branchcode' => $checkout->branchcode, + 'returndate' => $checkout->returndate, + 'lastreneweddate' => $checkout->lastreneweddate, + 'renewals' =>$checkout->renewals, + 'auto_renew' => $checkout->auto_renew, + 'auto_renew_error' => $checkout->auto_renew_error, + 'timestamp' => $checkout->timestamp, + 'issuedate' => $checkout->issuedate, + 'onsite_checkout' => $checkout->onsite_checkout, + 'note' => $checkout->note, + 'notedate' => $checkout->notedate, + } + } + } + + my $oldCheckouts = $patron->old_checkouts; + if($oldCheckouts) { + while(my $checkout = $oldCheckouts->next()) { + push @{$exportedData->{'checkouts'}}, { + 'issue_id' => $checkout->issue_id, + 'itemnumber' => $checkout->itemnumber, + 'date_due' => $checkout->date_due, + 'branchcode' => $checkout->branchcode, + 'returndate' => $checkout->returndate, + 'lastreneweddate' => $checkout->lastreneweddate, + 'renewals' =>$checkout->renewals, + 'auto_renew' => $checkout->auto_renew, + 'auto_renew_error' => $checkout->auto_renew_error, + 'timestamp' => $checkout->timestamp, + 'issuedate' => $checkout->issuedate, + 'onsite_checkout' => $checkout->onsite_checkout, + 'note' => $checkout->note, + 'notedate' => $checkout->notedate, + } + } + } + + my $clubs = $patron->get_club_enrollments; + if($clubs) { + while(my $club = $clubs->next()) { + push @{$exportedData->{'clubs'}}, { + id => $club->id, + date_enrolled => $club->date_enrolled, + date_canceled => $club->date_canceled, + date_created => $club->date_created, + date_updated => $club->date_updated, + branchcode => $club->branchcode, + } + } + } + + my $account = $patron->account; + if($account) { + $exportedData->{'account'} = { + 'balance' => $account->balance, + 'non_issues_charges' => $account->non_issues_charges, + }; + my $acclines = Koha::Account::Lines->search( + { + borrowernumber => $patron->borrowernumber, + }, + { + order_by => 'accountno' + } + ); + while(my $thisLine = $acclines->next()) { + push @{$exportedData->{'account'}->{'accountLines'}}, { + 'accountlines_id' => $thisLine->accountlines_id, + 'payment_type' => $thisLine->payment_type, + 'amountoutstanding' => $thisLine->amountoutstanding, + 'accounttype' => $thisLine->accounttype, + 'timestamp' => $thisLine->timestamp, + 'issue_id' => $thisLine->issue_id, + 'description' => $thisLine->description, + 'dispute' => $thisLine->dispute, + 'lastincrement' => $thisLine->lastincrement, + 'date' => $thisLine->date, + 'amount' => $thisLine->amount, + 'manager_id' => $thisLine->manager_id, + 'note' => $thisLine->note, + } + } + } + + # Generate the JSON file and spit it out + my $today = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 }); + my $borrowercardnumber = $patron->cardnumber; + print $input->header( + -type => 'application/json', + -charset => 'utf-8', + -attachment => "$today-$borrowercardnumber-GDPR-Export.json" + ); + my $json = new JSON; + print $json->pretty->encode($exportedData); + exit; +} + if ( $patron->is_child ) { my $patron_categories = Koha::Patron::Categories->search_limited({ category_type => 'A' }, {order_by => ['categorycode']}); $template->param( 'CATCODE_MULTI' => 1) if $patron_categories->count > 1; -- 2.1.4