[Koha-patches] [PATCH 1/1] But 5742: Batch edit patrons

Jonathan Druart jonathan.druart at biblibre.com
Thu Mar 29 15:57:09 CEST 2012


new permission : edit_patrons

Plan test:
- Go on the page: tools > Patrons modification (modborrowers.pl)
- Enter a list of cardnumbers (or use a file)
- Modify one or more patron's fields (you can modify surname, firstname,
branchname, category, sort1, sort2, date of enrollment, date of expiry,
debarred date, debarred comment and borrower note)
- Save
- Check on the result page (or in database for non-displayed fields) if
modifications have been correctly made.
- re test with different attributes. For each attributes filled with an
authorized value category, you can select a value in a drop-down list.
Else it's a free input text. If your attribute is filled with a patron
category, the modification performs only on patrons belonging to this
category
- Save
- Verify on the result page
---
 C4/Members.pm                                      |  151 +++++----
 C4/Members/Attributes.pm                           |   56 +++-
 .../data/mysql/de-DE/mandatory/userpermissions.sql |    1 +
 .../data/mysql/en/mandatory/userpermissions.sql    |    1 +
 .../data/mysql/es-ES/mandatory/userpermissions.sql |    1 +
 .../mysql/fr-FR/1-Obligatoire/userpermissions.sql  |    1 +
 .../data/mysql/it-IT/necessari/userpermissions.sql |    1 +
 .../mysql/nb-NO/1-Obligatorisk/userpermissions.sql |    1 +
 .../data/mysql/pl-PL/mandatory/userpermissions.sql |    1 +
 installer/data/mysql/updatedatabase.pl             |   10 +
 .../prog/en/modules/tools/modborrowers.tt          |  310 +++++++++++++++++++
 .../prog/en/modules/tools/tools-home.tt            |    5 +
 tools/modborrowers.pl                              |  322 ++++++++++++++++++++
 13 files changed, 789 insertions(+), 72 deletions(-)
 create mode 100644 koha-tmpl/intranet-tmpl/prog/en/modules/tools/modborrowers.tt
 create mode 100755 tools/modborrowers.pl

diff --git a/C4/Members.pm b/C4/Members.pm
index 6161ac9..5290b0e 100644
--- a/C4/Members.pm
+++ b/C4/Members.pm
@@ -42,86 +42,87 @@ use Koha::DateUtils;
 our ($VERSION, at ISA, at EXPORT, at EXPORT_OK,$debug);
 
 BEGIN {
-	$VERSION = 3.02;
-	$debug = $ENV{DEBUG} || 0;
-	require Exporter;
-	@ISA = qw(Exporter);
-	#Get data
-	push @EXPORT, qw(
-		&Search
-		&GetMemberDetails
+    $VERSION = 3.02;
+    $debug = $ENV{DEBUG} || 0;
+    require Exporter;
+    @ISA = qw(Exporter);
+    #Get data
+    push @EXPORT, qw(
+        &Search
+        &GetMemberDetails
         &GetMemberRelatives
-		&GetMember
+        &GetMember
 
-		&GetGuarantees 
+        &GetGuarantees
 
-		&GetMemberIssuesAndFines
-		&GetPendingIssues
-		&GetAllIssues
+        &GetMemberIssuesAndFines
+        &GetPendingIssues
+        &GetAllIssues
 
-		&get_institutions 
-		&getzipnamecity 
-		&getidcity
+        &get_institutions
+        &getzipnamecity
+        &getidcity
 
-                &GetFirstValidEmailAddress
+        &GetFirstValidEmailAddress
 
-		&GetAge 
-		&GetCities 
-		&GetRoadTypes 
-		&GetRoadTypeDetails 
-		&GetSortDetails
-		&GetTitles
+        &GetAge
+        &GetCities
+        &GetRoadTypes
+        &GetRoadTypeDetails
+        &GetSortDetails
+        &GetTitles
 
-    &GetPatronImage
-    &PutPatronImage
-    &RmPatronImage
+        &GetPatronImage
+        &PutPatronImage
+        &RmPatronImage
 
-                &GetHideLostItemsPreference
+        &GetHideLostItemsPreference
 
-		&IsMemberBlocked
-		&GetMemberAccountRecords
-		&GetBorNotifyAcctRecord
+        &IsMemberBlocked
+        &GetMemberAccountRecords
+        &GetBorNotifyAcctRecord
 
-		&GetborCatFromCatType 
-		&GetBorrowercategory
-    &GetBorrowercategoryList
+        &GetborCatFromCatType
+        &GetBorrowercategory
+        GetBorrowerCategorycode
+        &GetBorrowercategoryList
 
-		&GetBorrowersWhoHaveNotBorrowedSince
-		&GetBorrowersWhoHaveNeverBorrowed
-		&GetBorrowersWithIssuesHistoryOlderThan
+        &GetBorrowersWhoHaveNotBorrowedSince
+        &GetBorrowersWhoHaveNeverBorrowed
+        &GetBorrowersWithIssuesHistoryOlderThan
 
-		&GetExpiryDate
+        &GetExpiryDate
 
-		&AddMessage
-		&DeleteMessage
-		&GetMessages
-		&GetMessagesCount
+        &AddMessage
+        &DeleteMessage
+        &GetMessages
+        &GetMessagesCount
 
         &IssueSlip
-		GetBorrowersWithEmail
-	);
+        GetBorrowersWithEmail
+    );
 
-	#Modify data
-	push @EXPORT, qw(
-		&ModMember
-		&changepassword
+    #Modify data
+    push @EXPORT, qw(
+        &ModMember
+        &changepassword
          &ModPrivacy
-	);
-
-	#Delete data
-	push @EXPORT, qw(
-		&DelMember
-	);
-
-	#Insert data
-	push @EXPORT, qw(
-		&AddMember
-		&add_member_orgs
-		&MoveMemberToDeleted
-		&ExtendMemberSubscriptionTo
-	);
-
-	#Check data
+    );
+
+    #Delete data
+    push @EXPORT, qw(
+        &DelMember
+    );
+
+    #Insert data
+    push @EXPORT, qw(
+        &AddMember
+        &add_member_orgs
+        &MoveMemberToDeleted
+        &ExtendMemberSubscriptionTo
+    );
+
+    #Check data
     push @EXPORT, qw(
         &checkuniquemember
         &checkuserpassword
@@ -1409,10 +1410,6 @@ sub GetborCatFromCatType {
 Given the borrower's category code, the function returns the corresponding
 data hashref for a comprehensive information display.
 
-  $arrayref_hashref = &GetBorrowercategory;
-
-If no category code provided, the function returns all the categories.
-
 =cut
 
 sub GetBorrowercategory {
@@ -1433,6 +1430,26 @@ sub GetBorrowercategory {
     return;  
 }    # sub getborrowercategory
 
+
+=head2 GetBorrowerCategorycode
+
+    $categorycode = &GetBorrowerCategoryCode( $borrowernumber );
+
+Given the borrowernumber, the function returns the corresponding categorycode
+=cut
+
+sub GetBorrowerCategorycode {
+    my ( $borrowernumber ) = @_;
+    my $dbh = C4::Context->dbh;
+    my $sth = $dbh->prepare( qq{
+        SELECT categorycode
+        FROM borrowers
+        WHERE borrowernumber = ?
+    } );
+    $sth->execute( $borrowernumber );
+    return $sth->fetchrow;
+}
+
 =head2 GetBorrowercategoryList
 
   $arrayref_hashref = &GetBorrowercategoryList;
diff --git a/C4/Members/Attributes.pm b/C4/Members/Attributes.pm
index 33d2407..b373c94 100644
--- a/C4/Members/Attributes.pm
+++ b/C4/Members/Attributes.pm
@@ -32,8 +32,9 @@ BEGIN {
     $VERSION = 3.01;
     @ISA = qw(Exporter);
     @EXPORT_OK = qw(GetBorrowerAttributes GetBorrowerAttributeValue CheckUniqueness SetBorrowerAttributes
+                    DeleteBorrowerAttribute UpdateBorrowerAttribute
                     extended_attributes_code_value_arrayref extended_attributes_merge
-					SearchIdMatchingAttribute);
+                    SearchIdMatchingAttribute);
     %EXPORT_TAGS = ( all => \@EXPORT_OK );
 }
 
@@ -76,18 +77,19 @@ sub GetBorrowerAttributes {
                  FROM borrower_attributes
                  JOIN borrower_attribute_types USING (code)
                  LEFT JOIN authorised_values ON (category = authorised_value_category AND attribute = authorised_value)
-                 WHERE borrowernumber = ?";
+                 WHERE 1";
+    $query .= "\nAND borrowernumber = ?" if $borrowernumber;
     $query .= "\nAND opac_display = 1" if $opac_only;
     $query .= "\nORDER BY code, attribute";
     my $sth = $dbh->prepare_cached($query);
-    $sth->execute($borrowernumber);
+    $sth->execute($borrowernumber ? $borrowernumber : ());
     my @results = ();
     while (my $row = $sth->fetchrow_hashref()) {
         push @results, {
             code              => $row->{'code'},
             description       => $row->{'description'},
-            value             => $row->{'attribute'},  
-            value_description => $row->{'lib'},  
+            value             => $row->{'attribute'},
+            value_description => $row->{'lib'},
             password          => $row->{'password'},
             display_checkout  => $row->{'display_checkout'},
             category_code     => $row->{'category_code'},
@@ -233,6 +235,50 @@ sub SetBorrowerAttributes {
     return 1; # borower attributes successfully set
 }
 
+=head2 DeleteBorrowerAttribute
+
+  DeleteBorrowerAttribute($borrowernumber, $attribute);
+
+Delete a borrower attribute for the patron identified by C<$borrowernumber> and the attribute code of C<$attribute>
+
+=cut
+sub DeleteBorrowerAttribute {
+    my ( $borrowernumber, $attribute ) = @_;
+
+    my $dbh = C4::Context->dbh;
+    my $sth = $dbh->prepare(qq{
+        DELETE FROM borrower_attributes
+            WHERE borrowernumber = ?
+            AND code = ?
+    } );
+    $sth->execute( $borrowernumber, $attribute->{code} );
+}
+
+=head2 UpdateBorrowerAttribute
+
+  UpdateBorrowerAttribute($borrowernumber, $attribute );
+
+Update a borrower attribute C<$attribute> for the patron identified by C<$borrowernumber>,
+
+=cut
+sub UpdateBorrowerAttribute {
+    my ( $borrowernumber, $attribute ) = @_;
+
+    DeleteBorrowerAttribute $borrowernumber, $attribute;
+
+    my $dbh = C4::Context->dbh;
+    my $query = "INSERT INTO borrower_attributes SET attribute = ?, code = ?, borrowernumber = ?";
+    my @params = ( $attribute->{attribute}, $attribute->{code}, $borrowernumber );
+    if ( defined $attribute->{password} ) {
+        $query .= ", password = ?";
+        push @params, $attribute->{password};
+    }
+    my $sth = $dbh->prepare( $query );
+
+    $sth->execute( @params );
+}
+
+
 =head2 extended_attributes_code_value_arrayref 
 
    my $patron_attributes = "homeroom:1150605,grade:01,extradata:foobar";
diff --git a/installer/data/mysql/de-DE/mandatory/userpermissions.sql b/installer/data/mysql/de-DE/mandatory/userpermissions.sql
index 2302273..28326bd 100644
--- a/installer/data/mysql/de-DE/mandatory/userpermissions.sql
+++ b/installer/data/mysql/de-DE/mandatory/userpermissions.sql
@@ -28,6 +28,7 @@ INSERT INTO permissions (module_bit, code, description) VALUES
    (13, 'manage_staged_marc', 'MARC-Importe verwalten, auch Übernahme in Katalog und Import rückgängig machen'),
    (13, 'export_catalog', 'Titel- und Exemplardaten exportieren'),
    (13, 'import_patrons', 'Benutzerdaten importieren'),
+   (13, 'edit_patrons', 'Perform batch modification of patrons'),
    (13, 'delete_anonymize_patrons', 'Inaktive Benutzer löschen und Ausleihhistorie anonymisieren (Benutzerausleihhistorie löschen)'),
    (13, 'batch_upload_patron_images', 'Benutzerfotos einzeln oder im Stapel hochladen'),
    (13, 'schedule_tasks', 'Aufgabenplaner verwenden'),
diff --git a/installer/data/mysql/en/mandatory/userpermissions.sql b/installer/data/mysql/en/mandatory/userpermissions.sql
index 873089a..da61f69 100644
--- a/installer/data/mysql/en/mandatory/userpermissions.sql
+++ b/installer/data/mysql/en/mandatory/userpermissions.sql
@@ -28,6 +28,7 @@ INSERT INTO permissions (module_bit, code, description) VALUES
    (13, 'manage_staged_marc', 'Managed staged MARC records, including completing and reversing imports'),
    (13, 'export_catalog', 'Export bibliographic and holdings data'),
    (13, 'import_patrons', 'Import patron data'),
+   (13, 'edit_patrons', 'Perform batch modification of patrons'),
    (13, 'delete_anonymize_patrons', 'Delete old borrowers and anonymize circulation history (deletes borrower reading history)'),
    (13, 'batch_upload_patron_images', 'Upload patron images in batch or one at a time'),
    (13, 'schedule_tasks', 'Schedule tasks to run'),
diff --git a/installer/data/mysql/es-ES/mandatory/userpermissions.sql b/installer/data/mysql/es-ES/mandatory/userpermissions.sql
index ec61ea0..ce96ec1 100644
--- a/installer/data/mysql/es-ES/mandatory/userpermissions.sql
+++ b/installer/data/mysql/es-ES/mandatory/userpermissions.sql
@@ -28,6 +28,7 @@ INSERT INTO permissions (module_bit, code, description) VALUES
    (13, 'manage_staged_marc', 'Managed staged MARC records, including completing and reversing imports'),
    (13, 'export_catalog', 'Export bibliographic and holdings data'),
    (13, 'import_patrons', 'Import patron data'),
+   (13, 'edit_patrons', 'Perform batch modification of patrons'),
    (13, 'delete_anonymize_patrons', 'Delete old borrowers and anonymize circulation history (deletes borrower reading history)'),
    (13, 'batch_upload_patron_images', 'Upload patron images in batch or one at a time'),
    (13, 'schedule_tasks', 'Schedule tasks to run'),
diff --git a/installer/data/mysql/fr-FR/1-Obligatoire/userpermissions.sql b/installer/data/mysql/fr-FR/1-Obligatoire/userpermissions.sql
index faaaf39..7a30f82 100644
--- a/installer/data/mysql/fr-FR/1-Obligatoire/userpermissions.sql
+++ b/installer/data/mysql/fr-FR/1-Obligatoire/userpermissions.sql
@@ -18,6 +18,7 @@ INSERT INTO permissions (module_bit, code, description) VALUES
    (13, 'manage_staged_marc', 'Gérer les notices du réservoir, les charger ou annuler leur chargement'),
    (13, 'export_catalog', 'Exporter des notices bibliographiques et leurs exemplaires'),
    (13, 'import_patrons', 'Importer des données d''adhérents'),
+   (13, 'edit_patrons', 'Modification par lot des lecteurs'),
    (13, 'delete_anonymize_patrons', 'Supprimer les anciens adhérents et anonymiser l''historique des prêts (supprime l''historique des prêts des lecteurs'),
    (13, 'batch_upload_patron_images', 'Charger sur le serveur les images des adhérents par lot ou un par un'),
    (13, 'schedule_tasks', 'Planifier les tâches à exécuter'),
diff --git a/installer/data/mysql/it-IT/necessari/userpermissions.sql b/installer/data/mysql/it-IT/necessari/userpermissions.sql
index 926b47c..fd7cb12 100644
--- a/installer/data/mysql/it-IT/necessari/userpermissions.sql
+++ b/installer/data/mysql/it-IT/necessari/userpermissions.sql
@@ -30,6 +30,7 @@ INSERT INTO permissions (module_bit, code, description) VALUES
    (13, 'manage_staged_marc', 'Gestisci i record MARC in lavorazione, inclusi il completare e il cancellare gli import'),
    (13, 'export_catalog', 'Esporta i dati bibliografici e di copia'),
    (13, 'import_patrons', 'Importa i dati utente'),
+   (13, 'edit_patrons', 'Perform batch modification of patrons'),
    (13, 'delete_anonymize_patrons', 'Cancella i vecchi prestiti e rendi anonimo lo storico della circolazione (canella in lettura lo storico utenti prestito)'),
    (13, 'batch_upload_patron_images', 'Aggiorna le foto utente in modalità batch o al momento'),
    (13, 'schedule_tasks', 'Schedula i task da far andare'),
diff --git a/installer/data/mysql/nb-NO/1-Obligatorisk/userpermissions.sql b/installer/data/mysql/nb-NO/1-Obligatorisk/userpermissions.sql
index 24f081e..8525a20 100644
--- a/installer/data/mysql/nb-NO/1-Obligatorisk/userpermissions.sql
+++ b/installer/data/mysql/nb-NO/1-Obligatorisk/userpermissions.sql
@@ -49,6 +49,7 @@ INSERT INTO permissions (module_bit, code, description) VALUES
    (13, 'manage_staged_marc', 'Behandle lagrede MARC-poster, inkludert ferdigstilling og reversering av importer'),
    (13, 'export_catalog', 'Eksportere bibliografiske data og beholdningsdata'),
    (13, 'import_patrons', 'Importere låneropplysninger'),
+   (13, 'edit_patrons', 'Perform batch modification of patrons'),
    (13, 'delete_anonymize_patrons', 'Slette utgåtte lånere og anonymisere lånehistorikk'),
    (13, 'batch_upload_patron_images', 'Laste opp bilder av lånere enkeltvis eller en masse'),
    (13, 'schedule_tasks', 'Planlegge oppgaver som skal kjøres'),
diff --git a/installer/data/mysql/pl-PL/mandatory/userpermissions.sql b/installer/data/mysql/pl-PL/mandatory/userpermissions.sql
index f16a14d..a46ddfe 100644
--- a/installer/data/mysql/pl-PL/mandatory/userpermissions.sql
+++ b/installer/data/mysql/pl-PL/mandatory/userpermissions.sql
@@ -28,6 +28,7 @@ INSERT INTO permissions (module_bit, code, description) VALUES
    (13, 'manage_staged_marc', 'Managed staged MARC records, including completing and reversing imports'),
    (13, 'export_catalog', 'Export bibliographic and holdings data'),
    (13, 'import_patrons', 'Import patron data'),
+   (13, 'edit_patrons', 'Perform batch modification of patrons'),
    (13, 'delete_anonymize_patrons', 'Delete old borrowers and anonymize circulation history (deletes borrower reading history)'),
    (13, 'batch_upload_patron_images', 'Upload patron images in batch or one at a time'),
    (13, 'schedule_tasks', 'Schedule tasks to run'),
diff --git a/installer/data/mysql/updatedatabase.pl b/installer/data/mysql/updatedatabase.pl
index 9225c32..1aacd81 100755
--- a/installer/data/mysql/updatedatabase.pl
+++ b/installer/data/mysql/updatedatabase.pl
@@ -5086,6 +5086,16 @@ if ( C4::Context->preference("Version") < TransformToNum($DBversion) ) {
     SetVersion($DBversion);
 }
 
+
+
+
+$DBversion = "3.07.00.XXX";
+if ( C4::Context->preference("Version") < TransformToNum($DBversion) ) {
+    $dbh->do("INSERT IGNORE INTO `permissions` (`module_bit`, `code`, `description`) VALUES('13', 'edit_patrons', 'Perform batch modifivation of patrons')");
+    print "Upgrade to $DBversion done (Adds permissions flag for access to the patron modifications tool)\n";
+    SetVersion($DBversion);
+}
+
 =head1 FUNCTIONS
 
 =head2 DropAllForeignKeys($table)
diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/modborrowers.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/modborrowers.tt
new file mode 100644
index 0000000..c363181
--- /dev/null
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/modborrowers.tt
@@ -0,0 +1,310 @@
+[% USE KohaDates %]
+[% INCLUDE 'doc-head-open.inc'%]
+<title>Koha &rsaquo; Tools &rsaquo; [% IF ( del ) %]Batch item deletion[% ELSE %]Batch item modification[% END %] </title>
+[% INCLUDE 'doc-head-close.inc' %]
+[% INCLUDE 'calendar.inc' %]
+<script type="text/javascript" src="[% themelang %]/lib/jquery/plugins/jquery.tablesorter.min.js"></script>
+<script type="text/JavaScript" language="JavaScript">
+//<![CDATA[
+        var patron_attributes_lib = new Array();
+        var patron_attributes_values = new Array();
+        $(document).ready(function() {
+            $("#borrowerst").tablesorter();
+
+            $("#selectallbutton").click(function() {
+                $("#borrowerst").find("input:checkbox").each(function() {
+                    $(this).attr("checked", true);
+                });
+                return false;
+            });
+            $("#clearallbutton").click(function() {
+                $("#borrowerst").find("input:checkbox").each(function() {
+                    $(this).attr("checked", false);
+                });
+                return false;
+            });
+
+            var values = new Array();
+            var lib = new Array();
+            [% FOREACH pav IN patron_attributes_values %]
+                values = new Array();
+                lib = new Array();
+                [% FOREACH option IN pav.options %]
+                    values.push("[% option.lib %]");
+                    lib.push("[% option.authorised_value %]");
+                [% END %]
+                patron_attributes_lib["[% pav.attribute_code %]"] = values;
+                patron_attributes_values["[% pav.attribute_code %]"] = lib;
+            [% END %]
+
+            $('select[name="patron_attributes"]').change(function() {
+                updateAttrValues(this);
+            } );
+
+            $('select[name="patron_attributes"]').change();
+
+        } );
+
+        function updateAttrValues (select_attr) {
+            var attr_code = $(select_attr).val();
+            var type = $(select_attr).find("option:selected").attr('data-type');
+            var category = $(select_attr).find("option:selected").attr('data-category');
+            var span = $(select_attr).parent().parent().find('span.patron_attributes_value');
+            var information_category_node = $(select_attr).parent().parent().find('span.information_category');
+            information_category_node.html("");
+            if ( category.length > 0 ) {
+                information_category_node.html('This attribute will be only applied to the borrower\'s category "' + category + '"');
+            }
+            if ( type == 'select' ) {
+                var options = '<option value = ""></option>';
+                for ( var i = 0 ; i < patron_attributes_values[attr_code].length ; i++ ) {
+                    options += '<option value="'+patron_attributes_values[attr_code][i]+'">'+patron_attributes_lib[attr_code][i]+'</option>';
+                }
+                span.html('<select name="patron_attributes_value">' + options + '</select>');
+            } else {
+                span.html('<input type="text" name="patron_attributes_value"/>')
+            }
+        }
+
+        function add_attributes() {
+            var li_node = $("li.attributes:last");
+            var li_clone = $(li_node).clone();
+            if ( $(li_clone).find("a.delete").length == 0 ) {
+                $(li_clone).append('[<a href="#" title="Delete" class="delete" onclick="del_attributes(this);return false;">X</a>]');
+            }
+            $(li_clone).find('select[name="patron_attributes"]').change(function() {
+                updateAttrValues(this);
+            } );
+
+            $(li_clone).find('select[name="patron_attributes"]').change();
+
+            $("#fields_list>ol").append(li_clone);
+            update_attr_values();
+        }
+
+        function del_attributes(a_node) {
+            $(a_node).parent('li').remove();
+            update_attr_values();
+        }
+
+        function update_attr_values() {
+            $("li.attributes").each(function(i) {
+                $(this).find("input:checkbox").val("attr"+i+"_value");
+            });
+        }
+        function clearDate(nodeid) {
+            $("#"+nodeid).val("");
+        }
+
+//]]>
+</script>
+</head>
+<body>
+[% INCLUDE 'header.inc' %]
+[% INCLUDE 'cat-search.inc' %]
+
+<div id="breadcrumbs">
+    <a href="/cgi-bin/koha/mainpage.pl">Home</a> &rsaquo;
+    <a href="/cgi-bin/koha/tools/tools-home.pl">Tools</a> &rsaquo;
+    <a href="/cgi-bin/koha/tools/modborrowers.pl">Patrons modification</a>
+</div>
+
+<div id="doc3" class="yui-t2">
+    <div id="bd">
+        <div id="yui-main">
+            <div class="yui-b">
+                [% IF ( op == 'show_form' ) %]
+                <h1>Batch patrons modification</h1>
+                <form method="post" enctype="multipart/form-data" action="/cgi-bin/koha/tools/modborrowers.pl">
+                    <fieldset class="rows">
+                    <legend>Use a file</legend>
+                    <label for="uploadfile">File: </label> <input type="file" id="uploadfile" name="uploadfile" />
+                    </fieldset>
+                    <fieldset class="rows">
+                        <legend>Or list cardnumbers one by one</legend>
+                        <ol>
+                            <li>
+                              <label for="cardnumberlist">Carnumber list (one cardnumber per line): </label>
+                              <textarea rows="10" cols="30" id="cardnumberlist" name="cardnumberlist">[% cardnumberlist %]</textarea>
+                            </li>
+                        </ol>
+                    </fieldset>
+                    <input type="hidden" name="op" value="show" />
+                    <fieldset class="action">
+                        <input type="submit" value="Continue" class="button" />
+                        <a class="cancel" href="/cgi-bin/koha/tools/tools-home.pl">Cancel</a>
+                    </fieldset>
+                </form>
+                [% END %]
+
+                [% IF ( op == 'show' or op == 'show_results' ) %]
+                    [% IF ( op == 'show' ) %]
+                        <h1>Batch patrons modification</h1>
+                    [% ELSE %]
+                        <h1>Batch patrons results</h1>
+                    [% END %]
+                    [% IF ( notfoundcardnumbers ) %]
+                        <div class="dialog alert"><p>Warning, the following cardnumbers were not found:</p></div>
+                        <table style="margin:auto;">
+                            <thead>
+                                <tr><th>Cardnumbers not found</th></tr>
+                            </thead>
+                            <tbody>
+                                [% FOREACH notfoundcardnumber IN notfoundcardnumbers %]
+                                    <tr><td>[% notfoundcardnumber.cardnumber %]</td></tr>
+                                [% END %]
+                            </tbody>
+                        </table>
+                    [% END %]
+
+                    [% IF ( op == 'show_results' ) %]
+                        [% IF ( errors ) %]
+                            Errors occured :
+                            <ul class="warnings">
+                            [% FOREACH error IN errors %]
+                                [% IF ( error.error == 'can_not_update' ) %]
+                                    <li>Can not update borrower with borrowernumber [% error.borrowernumber %]</li>
+                                [% ELSE %]
+                                    <li>[% error.error %]</li>
+                                [% END %]
+                            [% END %]
+                            </ul>
+                        [% END %]
+                        <br/>
+                    [% END %]
+
+                    [% IF ( op == 'show' ) %]
+                    <form name="f" action="modborrowers.pl" method="post">
+                        <input type="hidden" name="op" value="do" />
+                        [% IF ( borrowers ) %]
+                            <div id="toolbar"><a id="selectallbutton" href="#">Select All</a> | <a id="clearallbutton" href="#">Clear All</a></div>
+                        [% END %]
+                    [% END %]
+                            <div id="cataloguing_additem_itemlist">
+                                <div style="overflow:auto">
+                                    <table id="borrowerst">
+                                        <thead>
+                                            <tr>
+                                                [% IF ( op == 'show' ) %]
+                                                    <th>&nbsp;</th>
+                                                [% END %]
+                                                <th>Surname</th>
+                                                <th>Firstname</th>
+                                                <th>Branchname</th>
+                                                <th>Categorycode</th>
+                                                <th>Cardnumber</th>
+                                                <th>dateenrolled</th>
+                                                <th>dateexpiry</th>
+                                                <th>debarred</th>
+                                                [% FOREACH attrh IN attributes_header %]
+                                                    <th>[% attrh.attribute %]</th>
+                                                [% END %]
+                                            </tr>
+                                        </thead>
+                                        <tbody>
+                                            [% FOREACH borrower IN borrowers %]
+                                                <tr>
+                                                    [% IF ( op == 'show' ) %]
+                                                        <td><input type="checkbox" name="borrowernumber" value="[% borrower.borrowernumber %]" checked="checked" /></td>
+                                                    [% END %]
+                                                    <td>[% borrower.surname %]</td>
+                                                    <td>[% borrower.firstname %]</td>
+                                                    <td>[% borrower.branchname %]</td>
+                                                    <td>[% borrower.categorycode %]</td>
+                                                    <td>[% borrower.cardnumber %]</td>
+                                                    <td>[% borrower.dateenrolled | $KohaDates %]</td>
+                                                    <td>[% borrower.dateexpiry | $KohaDates %]</td>
+                                                    <td>[% borrower.debarred | $KohaDates %]</td>
+                                                    [% FOREACH pa IN borrower.patron_attributes %]
+                                                        [% IF ( pa.code ) %]
+                                                            <td>[% pa.code %]=[% pa.value %]</td>
+                                                        [% ELSE %]
+                                                            <td></td>
+                                                        [% END %]
+                                                    [% END %]
+                                                </tr>
+                                            [% END %]
+                                        </tbody>
+                                    </table>
+                                </div>
+                            </div>
+
+                            [% IF ( op == 'show' ) %]
+                            <div id="cataloguing_additem_newitem">
+                                <h2>Edit Patrons</h2>
+                                <div class="hint">Checking the box right next the label will disable the entry and delete the values of that field on all selected patrons</div>
+                                <fieldset class="rows" id="fields_list">
+                                    <ol>
+                                        [% FOREACH field IN fields %]
+                                        <li>
+                                            <label style="width:20em;">[% field.lib %]</label>
+                                            <input type="checkbox" title="check to delete this field" name="disable_input" value="[% field.name %]"/>
+                                            [% IF ( field.type == 'text' ) %]
+                                                <input type="text" name="[% field.name %]" value="" />
+                                            [% END %]
+                                            [% IF ( field.type == 'select' ) %]
+                                                [% IF field.option.size %]
+                                                    <select name="[% field.name %]" >
+                                                        [% FOREACH opt IN field.option %]
+                                                            <option value="[% opt.value %]">[% opt.lib %]</option>
+                                                        [% END %]
+                                                    </select>
+                                                [% ELSE %]
+                                                    There is no value defined for [% field.name %]
+                                                [% END %]
+                                            [% END %]
+                                            [% IF ( field.type == 'date' ) %]
+                                                <img src="[% themelang %]/lib/calendar/cal.gif" style="cursor: pointer;" alt="Show Calendar" title="Show Calendar" id="[% field.name %]button"/>
+                                                <input type="text" name="[% field.name %]" id="[% field.name %]" value="" size="10" maxlength="10" readonly />
+                                                <a href="#" onclick="clearDate('[% field.name %]');return false;">X</a>
+                                                <script type="text/javascript">
+                                                 //<![CDATA[ 
+                                                Calendar.setup({
+                                                    inputField      : "[% field.name %]", 
+                                                    button          : "[% field.name %]button",
+                                                    ifFormat        : "[% DHTMLcalendar_dateformat %]",
+                                                    onClose: function() { $("#[% field.name %]").change(); this.hide();}
+                                                });
+                                                //]]>
+                                                </script>
+                                            [% END %]
+                                        </li>
+                                        [% END %]
+                                        <li class="attributes">
+                                            <label style="width:20em;">Attribute
+                                                <select name="patron_attributes">
+                                                    [% FOREACH pac IN patron_attributes_codes %]
+                                                        <option value="[% pac.attribute_code %]" data-type="[% pac.type %]" data-category="[% pac.category_lib %]">[% pac.attribute_lib %]</option>
+                                                    [% END %]
+                                                </select>
+                                            </label>
+                                            <input type="checkbox" title="check to delete this field" name="disable_input" value="attr0_value" />
+                                            <span class="patron_attributes_value"></span>
+                                            <a href="#" title="Add an attribute" onclick="add_attributes(); return false;">+</a>
+                                            <span class="information_category"></span>
+                                        </li>
+                                    </ol>
+                                </fieldset>
+                                <fieldset class="action">
+                                    <input type="submit" name="mainformsubmit" value="Save" />
+                                    <a href="/cgi-bin/koha/tools/modborrowers.pl" class="cancel">Cancel</a>
+                                </fieldset>
+                            </div>
+                        </form>
+                        [% END %]
+                [% END %]
+                [% IF ( op == 'show_results' ) %]
+                    <br/>
+                    <a href="/cgi-bin/koha/tools/modborrowers.pl" title="new Batch patrons modification">new Batch patrons modification</a>
+                [% END %]
+            </div>
+            </div>
+            <div class="yui-b">
+                [% INCLUDE 'tools-menu.inc' %]
+            </div>
+        </div>
+    </div>
+</div>
+</body>
+</html>
+
diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/tools-home.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/tools-home.tt
index 0cd163d..58400d8 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/tools/tools-home.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/tools/tools-home.tt
@@ -45,6 +45,11 @@
     <dd>Delete old borrowers and anonymize circulation history (deletes borrower reading history)</dd>
     [% END %]
 
+    [% IF ( CAN_user_tools_edit_patrons ) %]
+    <dt><a href="/cgi-bin/koha/tools/modborrowers.pl">Patrons (Modification)</a></dt>
+    <dd>Modify patrons</dd>
+    [% END %]
+
     [% IF ( CAN_user_tools_moderate_tags ) %]
     <dt><a href="/cgi-bin/koha/tags/review.pl">Tags</a> [% IF ( pendingtags ) %]<span class="holdcount"><a href="/cgi-bin/koha/tags/review.pl">[% pendingtags %]</a></span>[% END %]</dt>
 	<dd>Moderate patron tags</dd>
diff --git a/tools/modborrowers.pl b/tools/modborrowers.pl
new file mode 100755
index 0000000..6dac4e8
--- /dev/null
+++ b/tools/modborrowers.pl
@@ -0,0 +1,322 @@
+#!/usr/bin/perl
+
+# Copyright 2012 BibLibre
+#
+# This file is part of Koha.
+#
+# Koha is free software; you can redistribute it and/or modify it under the
+# terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with Koha; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+use Modern::Perl;
+use CGI;
+use C4::Auth;
+use C4::Branch;
+use C4::Koha;
+use C4::Members;
+use C4::Members::Attributes qw(GetBorrowerAttributes UpdateBorrowerAttribute DeleteBorrowerAttribute);
+use C4::Members::AttributeTypes qw/GetAttributeTypes_hashref/;
+use C4::Output;
+use List::MoreUtils qw /any uniq/;
+
+my $input = new CGI;
+my $op = $input->param('op') || 'show_form';
+my ( $template, $loggedinuser, $cookie ) = get_template_and_user(
+    {   template_name   => "tools/modborrowers.tmpl",
+        query           => $input,
+        type            => "intranet",
+        authnotrequired => 0,
+        flagsrequired   => { tools => "edit_patrons" },
+    }
+);
+
+my %cookies   = parse CGI::Cookie($cookie);
+my $sessionID = $cookies{'CGISESSID'}->value;
+my $dbh       = C4::Context->dbh;
+
+
+
+if ( $op eq 'show' ) {
+    my $filefh      = $input->upload('uploadfile');
+    my $filecontent = $input->param('filecontent');
+    my @borrowers;
+    my @cardnumbers;
+    my @notfoundcardnumbers;
+
+    my @contentlist;
+    if ($filefh) {
+        while ( my $content = <$filefh> ) {
+            $content =~ s/[\r\n]*$//g;
+            push @cardnumbers, $content if $content;
+        }
+    } else {
+        if ( my $list = $input->param('cardnumberlist') ) {
+            push @cardnumbers, split( /\s\n/, $list );
+        }
+    }
+
+    my $max_nb_attr = 0;
+    for my $cardnumber ( @cardnumbers ) {
+        my $borrower = GetBorrowerInfos( cardnumber => $cardnumber );
+        if ( $borrower ) {
+            $max_nb_attr = scalar( @{ $borrower->{patron_attributes} } )
+                if scalar( @{ $borrower->{patron_attributes} } ) > $max_nb_attr;
+            push @borrowers, $borrower;
+        } else {
+            push @notfoundcardnumbers, $cardnumber;
+        }
+    }
+
+    # Just for a correct display
+    for my $borrower ( @borrowers ) {
+        my $length = scalar( @{ $borrower->{patron_attributes} } );
+        push @{ $borrower->{patron_attributes} }, {} for ( $length .. $max_nb_attr - 1);
+    }
+
+    my @patron_attributes_values;
+    my @patron_attributes_codes;
+    my $patron_attribute_types = C4::Members::AttributeTypes::GetAttributeTypes_hashref('all');
+    my $patron_categories = C4::Members::GetBorrowercategoryList;
+    for ( values $patron_attribute_types ) {
+        my $attr_type = C4::Members::AttributeTypes->fetch( $_->{code} );
+        my $options = $attr_type->authorised_value_category
+            ? GetAuthorisedValues( $attr_type->authorised_value_category )
+            : undef;
+        push @patron_attributes_values,
+            {
+                attribute_code => $_->{code},
+                options        => $options,
+            };
+
+        my $category_code = $_->{category_code};
+        my ( $category_lib ) = map {
+            ( defined $category_code and $_->{categorycode} eq $category_code ) ? $_->{description} : ()
+        } @$patron_categories;
+        push @patron_attributes_codes,
+            {
+                attribute_code => $_->{code},
+                attribute_lib  => $_->{description},
+                category_lib   => $category_lib,
+                type           => $attr_type->authorised_value_category ? 'select' : 'text',
+            };
+    }
+
+    my @attributes_header = ();
+    for ( 1 .. scalar( $max_nb_attr ) ) {
+        push @attributes_header, { attribute => "Attributes $_" };
+    }
+    $template->param( borrowers => \@borrowers );
+    $template->param( attributes_header => \@attributes_header );
+    @notfoundcardnumbers = map { { cardnumber => $_ } } @notfoundcardnumbers;
+    $template->param( notfoundcardnumbers => \@notfoundcardnumbers )
+        if @notfoundcardnumbers;
+
+    my $branches = GetBranchesLoop;
+    my @branches_option;
+    push @branches_option, { value => $_->{value}, lib => $_->{branchname} } for @$branches;
+    unshift @branches_option, { value => "", lib => "" };
+    my $categories = GetBorrowercategoryList;
+    my @categories_option;
+    push @categories_option, { value => $_->{categorycode}, lib => $_->{description} } for @$categories;
+    unshift @categories_option, { value => "", lib => "" };
+    my $bsort1 = GetAuthorisedValues("Bsort1");
+    my @sort1_option;
+    push @sort1_option, { value => $_->{authorised_value}, lib => $_->{lib} } for @$bsort1;
+    unshift @sort1_option, { value => "", lib => "" }
+        if @sort1_option;
+    my $bsort2 = GetAuthorisedValues("Bsort2");
+    my @sort2_option;
+    push @sort2_option, { value => $_->{authorised_value}, lib => $_->{lib} } for @$bsort2;
+    unshift @sort2_option, { value => "", lib => "" }
+        if @sort2_option;
+    my @fields = (
+        {
+            name => "surname",
+            lib  => "Surname",
+            type => "text",
+        }
+        ,
+        {
+            name => "firstname",
+            lib  => "Firstname",
+            type => "text",
+        }
+        ,
+        {
+            name => "branchcode",
+            lib  => "Branchname",
+            type => "select",
+            option => \@branches_option,
+        }
+        ,
+        {
+            name => "categorycode",
+            lib  => "Category",
+            type => "select",
+            option => \@categories_option,
+        }
+        ,
+        {
+            name => "sort1",
+            lib  => "Sort 1",
+            type => "select",
+            option => \@sort1_option,
+        }
+        ,
+        {
+            name => "sort2",
+            lib  => "Sort 2",
+            type => "select",
+            option => \@sort2_option,
+        }
+        ,
+        {
+            name => "dateenrolled",
+            lib  => "Date enrolled",
+            type => "date",
+        }
+        ,
+        {
+            name => "dateexpiry",
+            lib  => "Date expiry",
+            type => "date",
+        }
+        ,
+        {
+            name => "debarred",
+            lib  => "Debarred",
+            type => "date",
+        }
+        ,
+        {
+            name => "debarredcomment",
+            lib  => "Debarred comment",
+            type => "text",
+        }
+        ,
+        {
+            name => "borrowernotes",
+            lib  => "Borrower Notes",
+            type => "text",
+        }
+    );
+
+    $template->param('patron_attributes_codes', \@patron_attributes_codes);
+    $template->param('patron_attributes_values', \@patron_attributes_values);
+
+    $template->param( fields => \@fields );
+    $template->param( DHTMLcalendar_dateformat => C4::Dates->DHTMLcalendar() );
+}
+
+if ( $op eq 'do' ) {
+
+    my @disabled = $input->param('disable_input');
+    my $infos;
+    for my $field ( qw/surname firstname branchcode categorycode sort1 sort2 dateenrolled dateexpiry debarred debarredcomment borrowernotes/ ) {
+        my $value = $input->param($field);
+        $infos->{$field} = $value if $value;
+        $infos->{$field} = "" if grep { /^$field$/ } @disabled;
+    }
+
+    my @attributes = $input->param('patron_attributes');
+    my @attr_values = $input->param('patron_attributes_value');
+
+    my @errors;
+    my @borrowernumbers = $input->param('borrowernumber');
+    for my $borrowernumber ( @borrowernumbers ) {
+        if ( defined $infos ) {
+            $infos->{borrowernumber} = $borrowernumber;
+            my $success = ModMember(%$infos);
+            push @errors, { error => "can_not_update", borrowernumber => $infos->{borrowernumber} } if not $success;
+        }
+
+        my $borrower_categorycode = GetBorrowerCategorycode $borrowernumber;
+        my $i=0;
+        for ( @attributes ) {
+            my $attribute;
+            $attribute->{code} = $_;
+            $attribute->{attribute} = $attr_values[$i];
+            my $attr_type = C4::Members::AttributeTypes->fetch( $_ );
+            ++$i and next if $attr_type->{category_code} and $attr_type->{category_code} ne $borrower_categorycode;
+            my $valuename = "attr" . $i . "_value";
+            if ( grep { /^$valuename$/ } @disabled ) {
+                eval {
+                    DeleteBorrowerAttribute $borrowernumber, $attribute;
+                };
+                push @errors, { error => $@ } if $@;
+            } else {
+                ++$i and next if not $attribute->{attribute};
+                eval {
+                    UpdateBorrowerAttribute $borrowernumber, $attribute;
+                };
+                push @errors, { error => $@ } if $@;
+            }
+            $i++;
+        }
+    }
+    $op = "show_results";
+
+    my @borrowers;
+    my $max_nb_attr = 0;
+    for my $borrowernumber ( @borrowernumbers ) {
+        my $borrower = GetBorrowerInfos( borrowernumber => $borrowernumber );
+        if ( $borrower ) {
+            $max_nb_attr = scalar( @{ $borrower->{patron_attributes} } )
+                if scalar( @{ $borrower->{patron_attributes} } ) > $max_nb_attr;
+            push @borrowers, $borrower;
+        }
+    }
+    my @patron_attributes_option;
+    for my $borrower ( @borrowers ) {
+        push @patron_attributes_option, { value => "$_->{code}", lib => $_->{code} } for @{ $borrower->{patron_attributes} };
+        my $length = scalar( @{ $borrower->{patron_attributes} } );
+        push @{ $borrower->{patron_attributes} }, {} for ( $length .. $max_nb_attr - 1);
+    }
+
+    my @attributes_header = ();
+    for ( 1 .. scalar( $max_nb_attr ) ) {
+        push @attributes_header, { attribute => "Attributes $_" };
+    }
+
+    $template->param( borrowers => \@borrowers );
+    $template->param( attributes_header => \@attributes_header );
+
+    $template->param( borrowers => \@borrowers );
+    $template->param( errors => \@errors );
+}
+
+$template->param(
+    op => $op,
+);
+output_html_with_http_headers $input, $cookie, $template->output;
+exit;
+
+sub GetBorrowerInfos {
+    my ( %info ) = @_;
+    my $borrower = GetMember( %info );
+    if ( $borrower ) {
+        $borrower->{branchname} = GetBranchName( $borrower->{branchcode} );
+        for ( qw(dateenrolled dateexpiry debarred) ) {
+            my $userdate = $borrower->{$_};
+            unless ($userdate && $userdate ne "0000-00-00" and $userdate ne "9999-12-31") {
+                $borrower->{$_} = '';
+                next;
+            }
+            $borrower->{$_} = $userdate || '';
+        }
+        $borrower->{category_description} = GetBorrowercategory( $borrower->{categorycode} )->{description};
+        my $attr_loop = C4::Members::Attributes::GetBorrowerAttributes( $borrower->{borrowernumber} );
+        $borrower->{patron_attributes} = $attr_loop;
+    }
+    return $borrower;
+}
+
-- 
1.7.7.3



More information about the Koha-patches mailing list