Generic patron barcode generation (OpenSRF and DB)
authorDan Scott <dscott@laurentian.ca>
Thu, 10 Jan 2013 19:17:33 +0000 (14:17 -0500)
committerDan Scott <dscott@laurentian.ca>
Thu, 10 Jan 2013 22:14:41 +0000 (17:14 -0500)
Laurentian University needed the ability to generate barcodes as part of
its LDAP integration work, and the first generation (so to speak) of the
was specific to LU - including hard-coded prefixes and database
functions that include the "lu" name.

This commit makes the functionality much more generic and thus more
likely to be able to be adopted by other institutions. The principle
components are:

Database functions:

evergreen.actor_generate_barcode([prefix TEXT]) - returns a 14-digit
  barcode from the evergreen.actor_barcode_seq sequence with a prefix of
  'AUTOBC' or the specific prefix of up to 6 characters. If the
  resulting barcode is all digits, then the 14th character will be a
  mod10 check digit; otherwise the 14th digit will be '0'.

evergreen.actor_update_barcode(usr_id INTEGER[, prefix TEXT]) -
  generates a new barcode for the specified user, with the optional
  barcode prefix.

evergreen.mod10(barcode TEXT) - given a barcode, generates a mod10
  check digit and returns the barcode with the appended check digit

OpenSRF method:

open-ils.actor.generate_patron_barcode([usr_id INT[, prefix TEXT]]) -
  generates a new barcode for the patron

Signed-off-by: Dan Scott <dscott@laurentian.ca>
Open-ILS/src/perlmods/lib/OpenILS/Application/Actor.pm
Open-ILS/src/sql/Pg/upgrade/XXXX.generate_patron_barcodes.sql [new file with mode: 0644]

index 170b129..218aa5d 100644 (file)
@@ -4628,18 +4628,21 @@ __PACKAGE__->register_method(
         params => [
             {desc => 'Authentication token', type => 'string'},
             {desc => 'User ID', type => 'number'},
+            {desc => 'prefix', type => 'string'},
         ],
         return => {desc => 'Generated barcode on success'}
     }
 );
 
-# evergreen.lu_update_barcode(user-id) generates a barcode, creates an actor.card
-# object, and points actor.usr.card to the new actor.card.id
+# evergreen.actor_update_barcode(user_id[, prefix]) generates a barcode, creates
+# an actor.card object, and points actor.usr.card to the new actor.card.id;
+# prefix is an optional prefix for the barcode
 #
-# evergreen.lu_generate_barcode() just generates a barcode
+# evergreen.actor_generate_barcode([prefix]) just generates a barcode, with
+# prefix as an optional prefix for the barcode
 sub generate_patron_barcode {
 
-    my( $self, $client, $auth, $user_id ) = @_;
+    my( $self, $client, $auth, $user_id, $prefix ) = @_;
 
     my $e = new_editor( authtoken=>$auth );
     return $e->die_event unless $e->checkauth;
@@ -4647,12 +4650,20 @@ sub generate_patron_barcode {
     my $barcode;
     if ($user_id) {
         return $e->die_event unless $e->allowed('UPDATE_USER');
+        my $args = ['evergreen.actor_update_barcode', $user_id];
+        if ($prefix) {
+            push @$args, $prefix;
+        }
         $barcode = $e->json_query(
-            {from => ['evergreen.lu_update_barcode', $user_id]})->[0] 
+            {from => $args})->[0] 
             or return $e->die_event;
     } else {
+        my $args = ['evergreen.actor_generate_barcode'];
+        if ($prefix) {
+            push @$args, $prefix;
+        }
         $barcode = $e->json_query(
-            {from => ['evergreen.lu_generate_barcode']})->[0] 
+            {from => $args})->[0] 
             or return $e->die_event;
     }
 
diff --git a/Open-ILS/src/sql/Pg/upgrade/XXXX.generate_patron_barcodes.sql b/Open-ILS/src/sql/Pg/upgrade/XXXX.generate_patron_barcodes.sql
new file mode 100644 (file)
index 0000000..14544e7
--- /dev/null
@@ -0,0 +1,87 @@
+-- Provides support for generating patron barcodes, with optional prefixes
+-- If all digits, then a mod10 check digit is calculated and appended
+CREATE SEQUENCE evergreen.actor_barcode_seq;
+
+CREATE OR REPLACE FUNCTION evergreen.mod10(barcode TEXT)
+RETURNS TEXT AS $$
+use strict; 
+use warnings; 
+my $barcode = shift; 
+my $total = 0; 
+my $position = 0; 
+foreach my $digit (split('', $barcode)) { 
+    $digit = sprintf('%d', $digit); 
+    $position++; 
+    if ($position % 2) { 
+        # Double it 
+        $digit *= 2; 
+        # If less than 10, add to the total 
+        if ($digit < 10) { 
+            $total += $digit; 
+        } else { 
+            $total += $digit - 9; 
+        } 
+    } else { 
+        $total += $digit; 
+    } 
+} 
+my $rem = $total % 10; 
+if ($rem) { 
+    return 10 - $rem; 
+} 
+return $rem;  
+$$ LANGUAGE plperlu;
+
+CREATE OR REPLACE FUNCTION evergreen.actor_generate_barcode(prefix TEXT DEFAULT 'AUTOBC')
+RETURNS TEXT AS $$
+DECLARE 
+    bc_gen TEXT; 
+    mod TEXT; 
+    bc_serial RECORD; 
+    bc_holder TEXT; 
+BEGIN 
+    LOOP 
+        SELECT lpad(NEXTVAL('evergreen.actor_barcode_seq')::text, 7, '0') AS bc INTO bc_serial; 
+        bc_gen := rpad(COALESCE(prefix, '0'), 6, '0') || bc_serial.bc::text; 
+        IF unnest(regexp_matches(bc_gen, '\D')) IS NOT NULL THEN
+            bc_gen := rpad(bc_gen, 14, '0');
+        ELSE
+            bc_gen := bc_gen || evergreen.mod10(bc_gen); 
+        END IF;
+        SELECT barcode INTO bc_holder FROM actor.card WHERE barcode = bc_gen; 
+        EXIT WHEN bc_holder IS NULL; 
+    END LOOP; 
+    RETURN bc_gen; 
+END;  
+$$ LANGUAGE plpgsql;
+
+CREATE OR REPLACE FUNCTION evergreen.actor_update_barcode(usr_id INT, prefix TEXT DEFAULT NULL)
+RETURNS TEXT AS $$
+DECLARE
+    bc_gen TEXT;
+    bc_holder TEXT;
+BEGIN
+
+    LOOP
+        IF prefix IS NULL THEN
+            bc_gen := evergreen.actor_generate_barcode();
+        ELSE
+            bc_gen := evergreen.actor_generate_barcode(prefix);
+        END IF;
+
+        SELECT barcode INTO bc_holder FROM actor.card WHERE barcode = bc_gen;
+        EXIT WHEN bc_holder IS NULL;
+    END LOOP;
+
+    INSERT INTO actor.card (usr, barcode) VALUES (usr_id, bc_gen);
+
+    UPDATE actor.usr
+        SET card = CURRVAL('actor.card_id_seq')
+        WHERE id = usr_id;
+
+    RETURN bc_gen;
+END;
+$$ LANGUAGE plpgsql;