From 23b571220fe5da3faf942fbf94031152d99abdfa Mon Sep 17 00:00:00 2001
From: Ian Walls <ian.walls@bywatersolutions.com>
Date: Fri, 14 Oct 2011 14:02:13 -0400
Subject: [PATCH] Barcode Prefixes [ version for 3.8.2 ]
Content-Type: text/plain; charset="utf-8"

Allows for specification, on a per branch basis, of barcode prefixes for both items and patrons.
Barcodes must come to a specific length (as set in system preferences), and barcodes shorter than
this length will be auto-prefixed with the currently-logged-in branch's prefix.

Currently supports:
Circulation (checkout, return)
Patron lookup
Item creation
Patron creation
Bulk patron import
Inventory
Offline Circ
ILSDI
batch item modification/deletion tool

http://bugs.koha-community.org/show_bug.cgi?id=7676
---
 C4/Barcodes.pm                                     |    2 +
 C4/Barcodes/prefix.pm                              |  126 ++++++++++++++++++++
 C4/Circulation.pm                                  |    5 +
 C4/ILSDI/Services.pm                               |    3 +-
 C4/Items.pm                                        |   28 +++++
 C4/Members.pm                                      |   93 +++++++++++++--
 admin/branches.pl                                  |    8 +-
 admin/systempreferences.pl                         |    2 +
 cataloguing/additem.pl                             |    8 ++
 circ/circulation.pl                                |    3 +-
 circ/returns.pl                                    |    6 +-
 installer/data/mysql/kohastructure.sql             |    4 +-
 installer/data/mysql/sysprefs.sql                  |    6 +
 installer/data/mysql/updatedatabase.pl             |   10 ++-
 .../prog/en/modules/admin/branches.tt              |    6 +
 .../prog/en/modules/admin/preferences/admin.pref   |   10 ++
 .../en/modules/admin/preferences/cataloguing.pref  |    1 +
 members/memberentry.pl                             |   14 +-
 offline_circ/process_koc.pl                        |    4 +-
 tools/batchMod.pl                                  |    2 +
 tools/import_borrowers.pl                          |   10 ++-
 tools/inventory.pl                                 |    6 +
 22 files changed, 325 insertions(+), 32 deletions(-)
 create mode 100644 C4/Barcodes/prefix.pm

diff --git a/C4/Barcodes.pm b/C4/Barcodes.pm
index 61d2127..3a5d2f3 100644
--- a/C4/Barcodes.pm
+++ b/C4/Barcodes.pm
@@ -28,6 +28,7 @@ use C4::Dates;
 use C4::Barcodes::hbyymmincr;
 use C4::Barcodes::annual;
 use C4::Barcodes::incremental;
+use C4::Barcodes::prefix;
 
 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
 use vars qw($debug $cgi_debug);	# from C4::Debug, of course
@@ -173,6 +174,7 @@ sub default_self (;$) {
 }
 
 our $types = {
+        prefix_incr => sub {C4::Barcodes::prefix->new_object(@_);     },
 	annual      => sub {C4::Barcodes::annual->new_object(@_);     },
 	incremental => sub {C4::Barcodes::incremental->new_object(@_);},
 	hbyymmincr  => sub {C4::Barcodes::hbyymmincr->new_object(@_); },
diff --git a/C4/Barcodes/prefix.pm b/C4/Barcodes/prefix.pm
new file mode 100644
index 0000000..12c8c9f
--- /dev/null
+++ b/C4/Barcodes/prefix.pm
@@ -0,0 +1,126 @@
+package C4::Barcodes::prefix;
+
+# Copyright 2011 ByWater Solutions
+#
+# 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 strict;
+use warnings;
+use Carp;
+
+use C4::Context;
+use C4::Branch;
+use C4::Debug;
+use C4::Dates;
+
+use vars qw($VERSION @ISA);
+use vars qw($debug $cgi_debug);        # from C4::Debug, of course
+use vars qw($branch $width);
+
+BEGIN {
+    $VERSION = 0.01;
+    @ISA = qw(C4::Barcodes);
+    $width = C4::Context->preference('itembarcodelength');
+    my $prefix = '';
+}
+
+
+# This package generates auto-incrementing barcodes where branch-specific prefixes are being used.
+# For a library with 10-digit barcodes, and a prefix of T123, this should generate on a range of
+# T123000000 and T123999999
+#
+# There are a couple of problems with treating barcodes as straight-numerics and just incrementing
+# them.  The first, and most-obvious, is that barcodes need not be numeric, particularly in the prefix
+# part!  The second, as noted in C4::Barcodes.pm, is that you might not actually be able to handle very
+# large ints like that...
+
+
+sub db_max ($;$) {
+        my $self = shift;
+        my $sth = C4::Context->dbh->prepare("SELECT MAX(barcode) AS biggest FROM items where barcode LIKE ? AND length(barcode) =?");
+        my $prefix_search = $self->prefix . '%';
+        $sth->execute($prefix_search, $self->width);
+        return $self->initial unless ($sth->rows);
+        my ($row) = $sth->fetchrow_hashref();
+
+        my $max = $row->{biggest};
+        return ($max || 0);
+}
+
+sub initial () {
+        my $self = shift;
+        my $increment_width = $self->width - length($self->prefix);
+        return $self->prefix . sprintf("%"."$increment_width.$increment_width"."d",1);
+}
+
+sub parse ($;$) {   # return 3 parts of barcode: non-incrementing, incrementing, non-incrementing
+        my $self = shift;
+        my $barcode = (@_) ? shift : $self->value;
+        my $prefix = $self->prefix;
+        unless ($barcode =~ /($prefix)(.+)$/) {
+           return($barcode,undef,undef);
+        }
+        return ($1,$2,'');  # the third part is in anticipation of barcodes that include checkdigits
+}
+
+sub prefix ($;$) {
+        my $self = shift;
+        (@_) and $self->{prefix} = shift;
+        return C4::Branch::GetBranchDetail(C4::Context->userenv->{'branch'})->{'itembarcodeprefix'};
+}
+
+sub width ($;$) {
+       my $self = shift;
+       (@_) and $width = shift;        # hitting the class variable.
+       return $width;
+}
+
+sub next_value ($;$) {
+    my $self = shift;
+    my $specific = (scalar @_) ? 1 : 0;
+    my $max = $specific ? shift : $self->max;
+    return $self->initial unless ($max);
+    my ($head,$incr,$tail) = $self->parse($max);
+    return undef unless (defined $incr);
+    my $incremented = '';
+
+    while ($incr =~ /([a-zA-Z]*[0-9]*)\z/ ){
+       my $fragment = $1;
+       $fragment++;   #yes, you can do that with strings.  Clever.
+       if (length($fragment) > length($1)){   #uhoh.  got to carry something over.
+          $incremented = substr($fragment,1) . $incremented;
+          $incr = $`;    #prematch.  grab everything *before* $1 from above, and go back thru this.
+       }
+       else {   #we're okay now.
+          $incr = $` . $fragment . $incremented;
+          last;
+       }
+    }
+
+    my $next_val = $head . $incr .$tail;
+    return $next_val;
+}
+
+sub new_object {
+        my $class_or_object = shift;
+        my $type = ref($class_or_object) || $class_or_object;
+        my $self = $type->default_self('prefix_incr');
+        # take the prefix from argument, or the existing object, or get it from the userenv, in that order
+        return bless $self, $type;
+}
+
+1;
+__END__
diff --git a/C4/Circulation.pm b/C4/Circulation.pm
index f3245c5..b329906 100644
--- a/C4/Circulation.pm
+++ b/C4/Circulation.pm
@@ -170,6 +170,11 @@ sub barcodedecode {
 			}
 		}
 	}
+        if(C4::Context->preference('itembarcodelength') && (length($barcode) < C4::Context->preference('itembarcodelength'))) {
+                my $prefix = GetBranchDetail(C4::Context->userenv->{'branch'})->{'itembarcodeprefix'} ;
+                my $padding = C4::Context->preference('itembarcodelength') - length($prefix) - length($barcode) ;
+                $barcode = $prefix . '0' x $padding . $barcode if($padding >= 0) ;
+        }
     return $barcode;    # return barcode, modified or not
 }
 
diff --git a/C4/ILSDI/Services.pm b/C4/ILSDI/Services.pm
index 12f869f..e9160ea 100644
--- a/C4/ILSDI/Services.pm
+++ b/C4/ILSDI/Services.pm
@@ -513,7 +513,8 @@ sub GetServices {
 
     # Issuing management
     my $barcode = $item->{'barcode'} || '';
-    $barcode = barcodedecode($barcode) if ( $barcode && C4::Context->preference('itemBarcodeInputFilter') );
+    $barcode = barcodedecode($barcode) if ( $barcode && C4::Context->preference('itemBarcodeInputFilter')
+                                            || C4::Context->preference('itembarcodelength') );
     if ($barcode) {
         my ( $issuingimpossible, $needsconfirmation ) = CanBookBeIssued( $borrower, $barcode );
 
diff --git a/C4/Items.pm b/C4/Items.pm
index 4a8439a..7ab50af 100644
--- a/C4/Items.pm
+++ b/C4/Items.pm
@@ -282,6 +282,7 @@ sub AddItem {
     $sth->execute( $item->{'biblionumber'} );
     ($item->{'biblioitemnumber'}) = $sth->fetchrow;
 
+    _check_itembarcode($item) if (C4::Context->preference('itembarcodelength'));
     _set_defaults_for_add($item);
     _set_derived_columns_for_add($item);
     $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
@@ -385,6 +386,7 @@ sub AddItemBatchFromMarc {
             next ITEMFIELD;
         }
 
+        _check_itembarcode($item) if (C4::Context->preference('itembarcodelength'));
         _set_defaults_for_add($item);
         _set_derived_columns_for_add($item);
         my ( $itemnumber, $error ) = _koha_new_item( $item, $item->{barcode} );
@@ -2795,4 +2797,30 @@ sub PrepareItemrecordDisplay {
     };
 }
 
+=head2 _check_itembarcode
+
+=over 4
+
+&_check_itembarcode
+
+=back
+
+Modifies item barcode value to include prefix defined in branches.itembarcodeprefix
+if the length is less than the syspref itembarcodelength .
+
+=cut
+sub _check_itembarcode($) {
+    my $item = shift;
+    return(0) unless $item->{'barcode'}; # only modify if we've been passed a barcode.
+    # check item barcode prefix
+    # note this doesn't enforce barcodelength.
+    my $branch_prefix = C4::Branch::GetBranchDetail($item->{'homebranch'})->{'itembarcodeprefix'};
+    if(length($item->{'barcode'}) < C4::Context->preference('itembarcodelength')) {
+        my $padding = C4::Context->preference('itembarcodelength') - length($branch_prefix) - length($item->{'barcode'}) ;
+        $item->{'barcode'} = $branch_prefix .  '0' x $padding . $item->{'barcode'} if($padding >= 0) ;
+    } else {
+      #  $errors{'invalid_barcode'} = $item->{'barcode'};
+    }
+}
+
 1;
diff --git a/C4/Members.pm b/C4/Members.pm
index 16b0540..0c04f76 100644
--- a/C4/Members.pm
+++ b/C4/Members.pm
@@ -38,6 +38,7 @@ use C4::NewsChannels; #get slip news
 use DateTime;
 use DateTime::Format::DateParse;
 use Koha::DateUtils;
+use C4::Branch qw(GetBranchDetail);
 
 our ($VERSION,@ISA,@EXPORT,@EXPORT_OK,$debug);
 
@@ -176,10 +177,10 @@ sub _express_member_find {
     # so we can check an exact match first, if that works return, otherwise do the rest
     my $dbh   = C4::Context->dbh;
     my $query = "SELECT borrowernumber FROM borrowers WHERE cardnumber = ?";
-    if ( my $borrowernumber = $dbh->selectrow_array($query, undef, $filter) ) {
+    my $cardnum = _prefix_cardnum($filter);
+    if ( my $borrowernumber = $dbh->selectrow_array($query, undef, $cardnum) ) {
         return( {"borrowernumber"=>$borrowernumber} );
     }
-
     my ($search_on_fields, $searchtype);
     if ( length($filter) == 1 ) {
         $search_on_fields = [ qw(surname) ];
@@ -718,6 +719,12 @@ sub ModMember {
             $data{password} = md5_base64($data{password});
         }
     }
+
+    # modify cardnumber with prefix, if needed.
+    if(C4::Context->preference('patronbarcodelength') && exists($data{'cardnumber'})){
+        $data{'cardnumber'} = _prefix_cardnum($data{'cardnumber'});
+    }
+
 	my $execute_success=UpdateInTable("borrowers",\%data);
     if ($execute_success) { # only proceed if the update was a success
         # ok if its an adult (type) it may have borrowers that depend on it as a guarantor
@@ -753,6 +760,8 @@ sub AddMember {
 	$data{'userid'} = Generate_Userid($data{'borrowernumber'}, $data{'firstname'}, $data{'surname'}) if $data{'userid'} eq '';
 	# create a disabled account if no password provided
 	$data{'password'} = ($data{'password'})? md5_base64($data{'password'}) : '!';
+        $data{'cardnumber'} = _prefix_cardnum($data{'cardnumber'}) if(C4::Context->preference('patronbarcodelength'));
+
 	$data{'borrowernumber'}=InsertInTable("borrowers",\%data);	
     # mysql_insertid is probably bad.  not necessarily accurate and mysql-specific at best.
     logaction("MEMBERS", "CREATE", $data{'borrowernumber'}, "") if C4::Context->preference("BorrowersLog");
@@ -846,8 +855,8 @@ mode, to avoid database corruption.
 use vars qw( @weightings );
 my @weightings = ( 8, 4, 6, 3, 5, 2, 1 );
 
-sub fixup_cardnumber ($) {
-    my ($cardnumber) = @_;
+sub fixup_cardnumber {
+    my ($cardnumber,$branch) = @_;
     my $autonumber_members = C4::Context->boolean_preference('autoMemberNum') || 0;
 
     # Find out whether member numbers should be generated
@@ -895,15 +904,44 @@ sub fixup_cardnumber ($) {
 
         return "V$cardnumber$rem";
      } else {
-
-        my $sth = $dbh->prepare(
-            'SELECT MAX( CAST( cardnumber AS SIGNED ) ) FROM borrowers WHERE cardnumber REGEXP "^-?[0-9]+$"'
-        );
-        $sth->execute;
-        my ($result) = $sth->fetchrow;
-        return $result + 1;
-    }
-    return $cardnumber;     # just here as a fallback/reminder 
+         my $cardlength = C4::Context->preference('patronbarcodelength');
+         if (defined($cardnumber) && (length($cardnumber) == $cardlength) && $cardnumber =~ m/^$branch->{'patronbarcodeprefix'}/ ) {
+            return $cardnumber;
+         }
+
+         my $query = 'SELECT MAX( CAST( cardnumber AS SIGNED ) ) FROM borrowers WHERE cardnumber REGEXP "^-?[0-9]+$"';
+         my @bind;
+         my $firstnumber = 0;
+         if ( $branch->{'patronbarcodeprefix'} && $cardlength ) {
+             my $cardnum_search = $branch->{'patronbarcodeprefix'} . '%';
+             $query .= " AND cardnumber LIKE ?";
+             $query .= " AND length(cardnumber) = ?";
+             @bind = ($cardnum_search,$cardlength ) ;
+         }
+         my $sth= $dbh->prepare($query);
+         $sth->execute(@bind);
+         my ($result) = $sth->fetchrow;
+         if($result) {
+             my $cnt = 0;
+             $result =~ s/^$branch->{'patronbarcodeprefix'}//;
+             while ( $result =~ /([a-zA-Z]*[0-9]*)\z/ ) {   # use perl's magical stringcrement behavior (++).
+                 my $incrementable = $1;
+                 $incrementable++;
+                 if ( length($incrementable) > length($1) ) { # carry a digit to next incrementable fragment
+                     $cardnumber = substr($incrementable,1) . $cardnumber;
+                     $result = $`;
+                 } else {
+                     $cardnumber = $branch->{'patronbarcodeprefix'} . $` . $incrementable . $cardnumber ;
+                     last;
+                 }
+                 last if(++$cnt>10);
+             }
+         } else {
+             $cardnumber =  ++$firstnumber ;
+         }
+     }
+
+     return $cardnumber;     # just here as a fallback/reminder 
 }
 
 =head2 GetGuarantees
@@ -2339,6 +2377,35 @@ sub GetBorrowersWithEmail {
     return @result;
 }
 
+=head2 _prefix_cardnum
+
+=over 4
+
+$cardnum = _prefix_cardnum($cardnum,$branchcode);
+
+If a system-wide barcode length is defined, and a prefix defined for the passed branch or the user's branch,
+modify the barcode by prefixing and padding.
+
+=back
+=cut
+
+sub _prefix_cardnum{
+    my ($cardnum,$branchcode) = @_;
+
+    if(C4::Context->preference('patronbarcodelength') && (length($cardnum) < C4::Context->preference('patronbarcodelength'))) {
+        #if we have a system-wide cardnum length and a branch prefix, prepend the prefix.
+        if( ! $branchcode && defined(C4::Context->userenv) ) {
+            $branchcode = C4::Context->userenv->{'branch'};
+        }
+        return $cardnum unless $branchcode;
+        my $branch = GetBranchDetail( $branchcode );
+        return $cardnum unless( defined($branch) && defined($branch->{'patronbarcodeprefix'}) );
+        my $prefix = $branch->{'patronbarcodeprefix'} ;
+        my $padding = C4::Context->preference('patronbarcodelength') - length($prefix) - length($cardnum) ;
+        $cardnum = $prefix . '0' x $padding . $cardnum if($padding >= 0) ;
+   }
+    return $cardnum;
+}
 
 END { }    # module clean-up code here (global destructor)
 
diff --git a/admin/branches.pl b/admin/branches.pl
index c12762d..006bd10 100755
--- a/admin/branches.pl
+++ b/admin/branches.pl
@@ -68,8 +68,10 @@ my ( $template, $borrowernumber, $cookie ) = get_template_and_user(
     }
 );
 $template->param(
-     script_name => $script_name,
-     action      => $script_name,
+     script_name         => $script_name,
+     action              => $script_name,
+     itembarcodelength   => C4::Context->preference('itembarcodelength'),
+     patronbarcodelength => C4::Context->preference('patronbarcodelength'),
 );
 $template->param( ($op || 'else') => 1 );
 
@@ -410,6 +412,8 @@ sub _branch_to_template {
          opac_info      => $data->{'opac_info'},
          branchip       => $data->{'branchip'},
          branchnotes    => $data->{'branchnotes'}, 
+         itembarcodeprefix    => $data->{'itembarcodeprefix'},
+         patronbarcodeprefix  => $data->{'patronbarcodeprefix'},
     );
 }
 
diff --git a/admin/systempreferences.pl b/admin/systempreferences.pl
index 5e3848d..46971b0 100755
--- a/admin/systempreferences.pl
+++ b/admin/systempreferences.pl
@@ -124,6 +124,8 @@ $tabsysprefs{soundon}               = "Admin";
 $tabsysprefs{SpineLabelShowPrintOnBibDetails}   = "Admin";
 $tabsysprefs{WebBasedSelfCheckHeader}           = "Admin";
 $tabsysprefs{WebBasedSelfCheckTimeout}          = "Admin";
+$tabsysprefs{itembarcodelength}     = "Admin";
+$tabsysprefs{patronbarcodelength}   = "Admin";
 
 # Authorities
 $tabsysprefs{authoritysep}          = "Authorities";
diff --git a/cataloguing/additem.pl b/cataloguing/additem.pl
index fea1be1..209b23e 100755
--- a/cataloguing/additem.pl
+++ b/cataloguing/additem.pl
@@ -347,6 +347,14 @@ if ($op eq "additem") {
 	push @errors,"barcode_not_unique" if($exist_itemnumber);
 	# if barcode exists, don't create, but report The problem.
     unless ($exist_itemnumber) {
+            unless ($addedolditem->{'barcode'}) {
+               use C4::Barcodes;
+               my $barcodeobj = C4::Barcodes->new();
+               my $db_max = $barcodeobj->db_max();
+               my $nextbarcode = $barcodeobj->next_value($db_max);
+               my ($tagfield,$tagsubfield) = &GetMarcFromKohaField("items.barcode",$frameworkcode);
+               $record->field($tagfield)->update($tagsubfield => "$nextbarcode") if ($nextbarcode);
+            }
 	    my ($oldbiblionumber,$oldbibnum,$oldbibitemnum) = AddItemFromMarc($record,$biblionumber);
         set_item_default_location($oldbibitemnum);
     }
diff --git a/circ/circulation.pl b/circ/circulation.pl
index 7b388ce..822a2e8 100755
--- a/circ/circulation.pl
+++ b/circ/circulation.pl
@@ -121,7 +121,8 @@ if (C4::Context->preference("UseTablesortForCirc")) {
 my $barcode        = $query->param('barcode') || '';
 $barcode =~  s/^\s*|\s*$//g; # remove leading/trailing whitespace
 
-$barcode = barcodedecode($barcode) if( $barcode && C4::Context->preference('itemBarcodeInputFilter'));
+$barcode = barcodedecode($barcode) if( $barcode && (C4::Context->preference('itemBarcodeInputFilter')
+                                      ||  C4::Context->preference('itembarcodelength')));
 my $stickyduedate  = $query->param('stickyduedate') || $session->param('stickyduedate');
 my $duedatespec    = $query->param('duedatespec')   || $session->param('stickyduedate');
 my $issueconfirmed = $query->param('issueconfirmed');
diff --git a/circ/returns.pl b/circ/returns.pl
index 9f01530..ba3fc44 100755
--- a/circ/returns.pl
+++ b/circ/returns.pl
@@ -108,7 +108,8 @@ foreach ( $query->param ) {
 
     # decode barcode    ## Didn't we already decode them before passing them back last time??
     $barcode =~ s/^\s*|\s*$//g; # remove leading/trailing whitespace
-    $barcode = barcodedecode($barcode) if(C4::Context->preference('itemBarcodeInputFilter'));
+    $barcode = barcodedecode($barcode) if(C4::Context->preference('itemBarcodeInputFilter')
+                                          || C4::Context->preference('itembarcodelength'));
 
     ######################
     #Are these lines still useful ?
@@ -201,7 +202,8 @@ if ($canceltransfer){
 # actually return book and prepare item table.....
 if ($barcode) {
     $barcode =~ s/^\s*|\s*$//g; # remove leading/trailing whitespace
-    $barcode = barcodedecode($barcode) if C4::Context->preference('itemBarcodeInputFilter');
+    $barcode = barcodedecode($barcode) if (C4::Context->preference('itemBarcodeInputFilter')
+                                           || C4::Context->preference('itembarcodelength'));
     $itemnumber = GetItemnumberFromBarcode($barcode);
 
     if ( C4::Context->preference("InProcessingToShelvingCart") ) {
diff --git a/installer/data/mysql/kohastructure.sql b/installer/data/mysql/kohastructure.sql
index 8648e18..cf49886 100644
--- a/installer/data/mysql/kohastructure.sql
+++ b/installer/data/mysql/kohastructure.sql
@@ -365,7 +365,9 @@ CREATE TABLE `branches` ( -- information about your libraries or branches are st
   `branchprinter` varchar(100) default NULL, -- unused in Koha
   `branchnotes` mediumtext, -- notes related to your library or branch
   opac_info text, -- HTML that displays in OPAC
-  UNIQUE KEY `branchcode` (`branchcode`)
+  itembarcodeprefix varchar(10) default NULL, -- branch's item barcode prefix
+  patronbarcodeprefix varchar(10) default NULL, -- branch's patron barcode prefix
+  PRIMARY KEY (`branchcode`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 
 --
diff --git a/installer/data/mysql/sysprefs.sql b/installer/data/mysql/sysprefs.sql
index 132bbbe..aaea411 100644
--- a/installer/data/mysql/sysprefs.sql
+++ b/installer/data/mysql/sysprefs.sql
@@ -53,6 +53,7 @@ INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES
 INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES('IssuingInProcess',0,'If ON, disables fines if the patron is issuing item that accumulate debt',NULL,'YesNo');
 INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES('item-level_itypes',1,'If ON, enables Item-level Itemtype / Issuing Rules','','YesNo');
 INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES('itemcallnumber','082ab','The MARC field/subfield that is used to calculate the itemcallnumber (Dewey would be 082ab or 092ab; LOC would be 050ab or 090ab) could be 852hi from an item record',NULL,'free');
+INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES('itembarcodelength','','Number of characters in system-wide barcode schema (item barcodes).','','Integer');
 INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES('DefaultClassificationSource','ddc','Default classification scheme used by the collection. E.g., Dewey, LCC, etc.', NULL,'ClassSources');
 INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES('KohaAdminEmailAddress','root@localhost','Define the email address where patron modification requests are sent','','free');
 INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES('LabelMARCView','standard','Define how a MARC record will display','standard|economical','Choice');
@@ -95,6 +96,11 @@ INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES
 INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES('OpacPublic',1,'Turn on/off public OPAC',NULL,'YesNo');
 INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES('opacuserjs','','Define custom javascript for inclusion in OPAC','70|10','Textarea');
 INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES('opacuserlogin',1,'Enable or disable display of user login features',NULL,'YesNo');
+<<<<<<< HEAD
+=======
+INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES('QuoteOfTheDay',0,'Enable or disable display of Quote of the Day on the OPAC home page',NULL,'YesNo');
+INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES('patronbarcodelength','','Number of characters in system-wide barcode schema (patron cardnumbers).','','Integer');
+>>>>>>> Barcode Prefixes
 INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES('patronimages',0,'Enable patron images for the Staff Client',NULL,'YesNo');
 INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES('OPACpatronimages',0,'Enable patron images in the OPAC',NULL,'YesNo');
 INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES('printcirculationslips',1,'If ON, enable printing circulation receipts','','YesNo');
diff --git a/installer/data/mysql/updatedatabase.pl b/installer/data/mysql/updatedatabase.pl
index d21cfad..8692277 100755
--- a/installer/data/mysql/updatedatabase.pl
+++ b/installer/data/mysql/updatedatabase.pl
@@ -5276,7 +5276,15 @@ if ( C4::Context->preference("Version") < TransformToNum($DBversion) ) {
     SetVersion($DBversion);
 }
 
-
+$DBversion = "3.09.01.XXX";
+if ( C4::Context->preference("Version") < TransformToNum($DBversion) ) {
+    $dbh->do("ALTER TABLE `branches` ADD `itembarcodeprefix` VARCHAR( 10 ) NULL AFTER `branchnotes`");
+    $dbh->do("ALTER TABLE `branches` ADD `patronbarcodeprefix` VARCHAR( 10 ) NULL AFTER `itembarcodeprefix`");
+    $dbh->do("INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES('itembarcodelength','','Number of characters in system-wide barcode schema (item barcodes).','','Integer')");
+    $dbh->do("INSERT INTO `systempreferences` (variable,value,explanation,options,type) VALUES('patronbarcodelength','','Number of characters in system-wide barcode schema (patron cardnumbers).','','Integer')");
+    print "Upgrade to $DBversion done (Add barcode prefix feature. Add fields itembarcodeprefix and patronbarcodeprefix to table branches, add sysprefs itembarcodelength and patronbarcodelength)\n";
+    SetVersion($DBversion);
+}
 
 =head1 FUNCTIONS
 
diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/branches.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/branches.tt
index 15e8064..b5d0abc 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/branches.tt
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/branches.tt
@@ -143,6 +143,12 @@ tinyMCE.init({
         <li><label for="branchurl">URL</label><input type="text" name="branchurl" id="branchurl" value="[% branchurl |html %]" /></li>
         <li><label for="opac_info">OPAC info</label><textarea name="opac_info" id="opac_info">[% opac_info |html %]</textarea></li>
         <li><label for="branchip">IP</label><input type="text" name="branchip" id="branchip" value="[% branchip |html %]" /> <span class="hint">Can be entered as a single IP, or a subnet such as 192.168.1.*</span></li>
+        [% IF ( itembarcodelength ) %]
+           <li><label for="itembarcodeprefix">Item Barcode Prefix</label><input type="text" name="itembarcodeprefix" id="itembarcodeprefix" value="[% itembarcodeprefix |html %]" /></li>
+        [% END %]
+        [% IF ( patronbarcodelength ) %]
+           <li><label for="patronbarcodeprefix">Patron Barcode Prefix</label><input type="text" name="patronbarcodeprefix" id="patronbarcodeprefix" value="[% patronbarcodeprefix |html %]" /></li>
+        [% END %]
 		<!--
         <li><label for="branchprinter">Library Printer</label>
             <select id="branchprinter" name="branchprinter">
diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/admin.pref b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/admin.pref
index 08eb954..07e71ea 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/admin.pref
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/admin.pref
@@ -40,6 +40,16 @@ Administration:
                   yes: Allow
                   no: "Don't allow"
             - staff and patrons to create and view saved lists of books.
+        -
+            - Use branch prefixes to ensure that item barcodes are
+            - pref: itembarcodelength
+              class: integer
+            - digits long
+        -
+            - Use branch prefixes to ensure that patron barcodes are
+            - pref: patronbarcodelength
+              class: integer
+            - digits long
     Login options:
         -
             - pref: insecure
diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/cataloguing.pref b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/cataloguing.pref
index 10e1d13..7673cd4 100644
--- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/cataloguing.pref
+++ b/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/cataloguing.pref
@@ -86,6 +86,7 @@ Cataloging:
             - Barcodes are
             - pref: autoBarcode
               choices:
+                  prefix_incr: generated with the branch prefix, followed by a zero-padded form of 1, 2, 3.
                   incremental: generated in the form 1, 2, 3.
                   annual: generated in the form &lt;year&gt;-0001, &lt;year&gt;-0002.
                   hbyymmincr: generated in the form &lt;branchcode&gt;yymm0001.
diff --git a/members/memberentry.pl b/members/memberentry.pl
index e46a560..d5c35e8 100755
--- a/members/memberentry.pl
+++ b/members/memberentry.pl
@@ -436,7 +436,13 @@ if ( $op eq "duplicate" ) {
     $data{'cardnumber'} = "";
 }
 
-$data{'cardnumber'}=fixup_cardnumber($data{'cardnumber'}) if ( ( $op eq 'add' ) or ( $op eq 'duplicate' ) );
+my $onlymine=(C4::Context->preference('IndependantBranches') &&
+              C4::Context->userenv &&
+              C4::Context->userenv->{flags} % 2 !=1  &&
+              C4::Context->userenv->{branch}?1:0);
+my $branches=GetBranches($onlymine);
+$data{'cardnumber'}=fixup_cardnumber( $data{'cardnumber'}, $branches->{C4::Context->userenv->{'branch'}} ) if ( ( $op eq 'add' ) or ( $op eq 'duplicate' ) );
+
 if(!defined($data{'sex'})){
     $template->param( none => 1);
 } elsif($data{'sex'} eq 'F'){
@@ -573,12 +579,6 @@ my @branches;
 my @select_branch;
 my %select_branches;
 
-my $onlymine=(C4::Context->preference('IndependantBranches') && 
-              C4::Context->userenv && 
-              C4::Context->userenv->{flags} % 2 !=1  && 
-              C4::Context->userenv->{branch}?1:0);
-              
-my $branches=GetBranches($onlymine);
 my $default;
 my $CGIbranch;
 for my $branch (sort { $branches->{$a}->{branchname} cmp $branches->{$b}->{branchname} } keys %$branches) {
diff --git a/offline_circ/process_koc.pl b/offline_circ/process_koc.pl
index 75ff319..73f7ecb 100755
--- a/offline_circ/process_koc.pl
+++ b/offline_circ/process_koc.pl
@@ -241,7 +241,7 @@ sub arguments_for_command {
 sub kocIssueItem {
     my $circ = shift;
 
-    $circ->{ 'barcode' } = barcodedecode($circ->{'barcode'}) if( $circ->{'barcode'} && C4::Context->preference('itemBarcodeInputFilter'));
+    $circ->{ 'barcode' } = barcodedecode($circ->{'barcode'}) if( $circ->{'barcode'} && (C4::Context->preference('itemBarcodeInputFilter') || C4::Context->preference('itembarcodelength')));
     my $branchcode = C4::Context->userenv->{branch};
     my $borrower = GetMember( 'cardnumber'=>$circ->{ 'cardnumber' } );
     my $item = GetBiblioFromItemNumber( undef, $circ->{ 'barcode' } );
@@ -319,7 +319,7 @@ sub kocIssueItem {
 
 sub kocReturnItem {
     my ( $circ ) = @_;
-    $circ->{'barcode'} = barcodedecode($circ->{'barcode'}) if( $circ->{'barcode'} && C4::Context->preference('itemBarcodeInputFilter'));
+    $circ->{'barcode'} = barcodedecode($circ->{'barcode'}) if( $circ->{'barcode'} && (C4::Context->preference('itemBarcodeInputFilter') || C4::Context->preference('itembarcodelength')));
     my $item = GetBiblioFromItemNumber( undef, $circ->{ 'barcode' } );
     #warn( Data::Dumper->Dump( [ $circ, $item ], [ qw( circ item ) ] ) );
     my $borrowernumber = _get_borrowernumber_from_barcode( $circ->{'barcode'} );
diff --git a/tools/batchMod.pl b/tools/batchMod.pl
index 5469496..5c770bc 100755
--- a/tools/batchMod.pl
+++ b/tools/batchMod.pl
@@ -214,6 +214,7 @@ if ($op eq "show"){
 
         if ($filecontent eq 'barcode_file') {
             foreach my $barcode (@contentlist) {
+                $barcode = barcodedecode( $barcode ) if ( C4::Context->preference('itemBarcodeInputFilter') || C4::Context->preference('itembarcodelength') );
 
                 my $itemnumber = GetItemnumberFromBarcode($barcode);
                 if ($itemnumber) {
@@ -231,6 +232,7 @@ if ($op eq "show"){
             push my @barcodelist, split(/\s\n/, $list);
 
             foreach my $barcode (@barcodelist) {
+                $barcode = barcodedecode( $barcode ) if ( C4::Context->preference('itemBarcodeInputFilter') || C4::Context->preference('itembarcodelength') );
 
                 my $itemnumber = GetItemnumberFromBarcode($barcode);
                 if ($itemnumber) {
diff --git a/tools/import_borrowers.pl b/tools/import_borrowers.pl
index 83db522..26bf52a 100755
--- a/tools/import_borrowers.pl
+++ b/tools/import_borrowers.pl
@@ -41,7 +41,7 @@ use C4::Auth;
 use C4::Output;
 use C4::Dates qw(format_date_in_iso);
 use C4::Context;
-use C4::Branch qw(GetBranchName);
+use C4::Branch qw(GetBranchName GetBranches);
 use C4::Members;
 use C4::Members::Attributes qw(:all);
 use C4::Members::AttributeTypes;
@@ -271,7 +271,13 @@ if ( $uploadborrowers && length($uploadborrowers) > 0 ) {
             # FIXME: fixup_cardnumber says to lock table, but the web interface doesn't so this doesn't either.
             # At least this is closer to AddMember than in members/memberentry.pl
             if (!$borrower{'cardnumber'}) {
-                $borrower{'cardnumber'} = fixup_cardnumber(undef);
+              my $onlymine=(C4::Context->preference('IndependantBranches') &&
+              C4::Context->userenv &&
+              C4::Context->userenv->{flags} % 2 !=1  &&
+              C4::Context->userenv->{branch}?1:0);
+
+              my $branches=GetBranches($onlymine);
+              $borrower{'cardnumber'} = fixup_cardnumber(undef,$branches->{$borrower{'branchcode'}});
             }
             if ($borrowernumber = AddMember(%borrower)) {
                 if ($extended) {
diff --git a/tools/inventory.pl b/tools/inventory.pl
index 5e7b198..9641658 100755
--- a/tools/inventory.pl
+++ b/tools/inventory.pl
@@ -150,6 +150,12 @@ if ($uploadbarcodes && length($uploadbarcodes)>0){
     my $count=0;
     while (my $barcode=<$uploadbarcodes>){
         $barcode =~ s/\r?\n$//;
+        if(C4::Context->preference('itembarcodelength') && (length($barcode) < C4::Context->preference('itembarcodelength'))) {
+                my $prefix = GetBranchDetail(C4::Context->userenv->{'branch'})->{'itembarcodeprefix'} ;
+                my $padding = C4::Context->preference('itembarcodelength') - length($prefix) - length($barcode) ;
+                $barcode = $prefix . '0' x $padding . $barcode if($padding >= 0) ;
+        }
+
         if ($qwthdrawn->execute($barcode) &&$qwthdrawn->rows){
             push @errorloop, {'barcode'=>$barcode,'ERR_WTHDRAWN'=>1};
         }else{
-- 
1.7.2.5