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>
Fri, 25 May 2018 21:25:46 +0000 (17:25 -0400)
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 d3657d2..1b433b8 100644 (file)
@@ -4880,6 +4880,57 @@ sub mark_users_contact_invalid {
     );
 }
 
+__PACKAGE__->register_method(
+    method   => "generate_patron_barcode",
+    api_name => "open-ils.actor.generate_patron_barcode",
+    signature => {
+        desc => "Generates a new patron barcode. If a user ID is supplied," .
+                "that user's card will be updated to point at the new barcode." ,
+        params => [
+            {desc => 'Authentication token', type => 'string'},
+            {desc => 'User ID', type => 'number'},
+            {desc => 'prefix', type => 'string'},
+        ],
+        return => {desc => 'Generated barcode on success'}
+    }
+);
+
+# 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.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, $prefix ) = @_;
+
+    my $e = new_editor( authtoken=>$auth );
+    return $e->die_event unless $e->checkauth;
+
+    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 => $args})->[0] 
+            or return $e->die_event;
+    } else {
+        my $args = ['evergreen.actor_generate_barcode'];
+        if ($prefix) {
+            push @$args, $prefix;
+        }
+        $barcode = $e->json_query(
+            {from => $args})->[0] 
+            or return $e->die_event;
+    }
+
+    return $barcode;
+}
+
 # Putting the following method in open-ils.actor is a bad fit, except in that
 # it serves an interface that lives under 'actor' in the templates directory,
 # and in that there's nowhere else obvious to put it (open-ils.trigger is
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;