Bug 28606: Remove $DEBUG and $ENV{DEBUG}
[srvgit] / installer / data / mysql / updatedatabase.pl
index 885e0b9..d31ec08 100755 (executable)
@@ -50,8 +50,6 @@ use File::Slurp;
 # FIXME - The user might be installing a new database, so can't rely
 # on /etc/koha.conf anyway.
 
-my $debug = 0;
-
 my (
     $sth,
     $query,
@@ -151,7 +149,7 @@ if (C4::Context->preference("Version") < TransformToNum($DBversion)) {
 
 $DBversion = "3.00.00.006";
 if (C4::Context->preference("Version") < TransformToNum($DBversion)) {
-    $dbh->do("UPDATE issues SET issuedate=timestamp WHERE issuedate='0000-00-00'");
+    sanitize_zero_date('issues', 'issuedate');
     print "Upgrade to $DBversion done (filled issues.issuedate with timestamp)\n";
     SetVersion ($DBversion);
 }
@@ -1949,7 +1947,9 @@ if (C4::Context->preference("Version") < TransformToNum($DBversion)) {
 
     print "Upgrade to $DBversion done (remove default '0000-00-00' in subscriptionhistory.enddate field)\n";
     $dbh->do("ALTER TABLE `subscriptionhistory` CHANGE `enddate` `enddate` DATE NULL DEFAULT NULL ");
-    $dbh->do("UPDATE subscriptionhistory SET enddate=NULL WHERE enddate='0000-00-00'");
+
+    sanitize_zero_date('subscriptionhistory', 'enddate');
+
     SetVersion ($DBversion);
 }
 
@@ -2903,7 +2903,8 @@ if (C4::Context->preference("Version") < TransformToNum($DBversion)) {
     # match the new type of the corresponding field
     $dbh->do('ALTER TABLE fundmapping modify column bookfundid varchar(30)');
     # System did not ensure budgetdate was valid historically
-    $dbh->do(q|UPDATE fundmapping SET budgetdate = entrydate WHERE budgetdate = '0000-00-00' OR budgetdate IS NULL|);
+    sanitize_zero_date('fundmapping', 'budgetdate');
+    $dbh->do(q|UPDATE fundmapping SET budgetdate = entrydate WHERE budgetdate IS NULL|);
     # We save the map in fundmapping in case you need later processing
     $dbh->do(q|ALTER TABLE fundmapping add column aqbudgetid integer|);
     # these can speed processing up
@@ -3626,7 +3627,7 @@ if (C4::Context->preference("Version") < TransformToNum($DBversion)) {
 
 $DBversion = "3.01.00.130";
 if (C4::Context->preference("Version") < TransformToNum($DBversion)) {
-    $dbh->do("UPDATE reserves SET expirationdate = NULL WHERE expirationdate = '0000-00-00'");
+    sanitize_zero_date('reserves', 'expirationdate');
     print "Upgrade to $DBversion done (change reserves.expirationdate values of 0000-00-00 to NULL (bug 1532)\n";
     SetVersion ($DBversion);
 }
@@ -4574,7 +4575,7 @@ if ( C4::Context->preference("Version") < TransformToNum($DBversion) ) {
 
 $DBversion = "3.07.00.002";
 if ( C4::Context->preference("Version") < TransformToNum($DBversion) ) {
-    $dbh->do("UPDATE borrowers SET debarred=NULL WHERE debarred='0000-00-00';");
+    sanitize_zero_date('borrowers', 'debarred');
     print "Setting NULL to debarred where 0000-00-00 is stored (bug 7272)\n";
     SetVersion($DBversion);
 }
@@ -7603,6 +7604,8 @@ if ( CheckVersion($DBversion) ) {
         SET firstacquidate = ?
         WHERE subscriptionid = ?
     |);
+
+    sanitize_zero_date('subscription', 'firstacquidate');
     my $get_subscriptions_sth = $dbh->prepare(qq|
         SELECT subscriptionid, startdate
         FROM subscription
@@ -8300,7 +8303,7 @@ if ( CheckVersion($DBversion) ) {
     } );
 
     $dbh->do( q{
-        ALTER TABLE letter ADD CONSTRAINT message_transport_type_fk FOREIGN KEY (message_transport_type) REFERENCES message_transport_types(message_transport_type);
+        ALTER TABLE letter ADD CONSTRAINT message_transport_type_fk FOREIGN KEY (message_transport_type) REFERENCES message_transport_types(message_transport_type) ON DELETE CASCADE ON UPDATE CASCADE
     } );
 
     $dbh->do( q{
@@ -8873,7 +8876,7 @@ if ( CheckVersion($DBversion) ) {
         q{
        INSERT INTO systempreferences (variable, value, options, explanation, type )
        VALUES
-        ('UsageStatsCountry', '', NULL, 'The country where your library is located, to be shown on the Hea Koha community website', 'YesNo'),
+        ('UsageStatsCountry', '', NULL, 'The country where your library is located, to be shown on the Hea Koha community website', 'Choice'),
         ('UsageStatsID', '', NULL, 'This preference is part of Koha but it should not be deleted or updated manually.',  'Free'),
         ('UsageStatsLastUpdateTime', '', NULL, 'This preference is part of Koha but it should not be deleted or updated manually.', 'Free'),
         ('UsageStatsLibraryName', '', NULL, 'The library name to be shown on Hea Koha community website', 'Free'),
@@ -10635,11 +10638,14 @@ if ( CheckVersion($DBversion) ) {
 
 $DBversion = "3.21.00.009";
 if ( CheckVersion($DBversion) ) {
+
+    sanitize_zero_date('aqorders', 'datecancellationprinted');
+
     $dbh->do(q|
         UPDATE aqorders SET orderstatus='cancelled'
-        WHERE (datecancellationprinted IS NOT NULL OR
-               datecancellationprinted<>'0000-00-00');
+        WHERE (datecancellationprinted IS NOT NULL)
     |);
+
     print "Upgrade to $DBversion done (Bug 13993: Correct orderstatus for transferred orders)\n";
     SetVersion($DBversion);
 }
@@ -10870,18 +10876,12 @@ if ( CheckVersion($DBversion) ) {
 
 $DBversion = "3.21.00.023";
 if ( CheckVersion($DBversion) ) {
-    $dbh->do(q{
-        UPDATE borrowers SET debarred=NULL WHERE debarred='0000-00-00'
-    });
-    $dbh->do(q{
-        UPDATE borrowers SET dateexpiry=NULL where dateexpiry='0000-00-00'
-    });
-    $dbh->do(q{
-        UPDATE borrowers SET dateofbirth=NULL where dateofbirth='0000-00-00'
-    });
-    $dbh->do(q{
-        UPDATE borrowers SET dateenrolled=NULL where dateenrolled='0000-00-00'
-    });
+
+    sanitize_zero_date('borrowers', 'debarred');
+    sanitize_zero_date('borrowers', 'dateexpiry');
+    sanitize_zero_date('borrowers', 'dateofbirth');
+    sanitize_zero_date('borrowers', 'dateenrolled');
+
     print "Upgrade to $DBversion done (Bug 14717: Prevent 0000-00-00 dates in patron data)\n";
     SetVersion($DBversion);
 }
@@ -17266,7 +17266,7 @@ if( CheckVersion( $DBversion ) ) {
     }
     $dbh->do(q{
         INSERT IGNORE INTO systempreferences ( `variable`, `value`, `options`, `explanation`, `type` ) VALUES
-        ('Mana','2',NULL,'request to Mana Webservice. Mana centralize common information between other Koha to facilitate the creation of new subscriptions, vendors, report queries etc... You can search, share, import and comment the content of Mana.','YesNo');
+        ('Mana','2',NULL,'request to Mana Webservice. Mana centralize common information between other Koha to facilitate the creation of new subscriptions, vendors, report queries etc... You can search, share, import and comment the content of Mana.','Choice');
     });
     $dbh->do(q{
         INSERT IGNORE INTO systempreferences ( `variable`, `value`, `options`, `explanation`, `type` ) VALUES
@@ -17629,15 +17629,18 @@ $DBversion = '18.12.00.024';
 if ( CheckVersion($DBversion) ) {
 
     # Fixup any pre-existing bad suggestedby, manageddate, accepteddate dates
-    $dbh->do(
-        "UPDATE suggestions SET suggesteddate = '1970-01-01' WHERE suggesteddate = '0000-00-00';"    
-    );
-    $dbh->do(
-        "UPDATE suggestions SET manageddate = '1970-01-01' WHERE manageddate = '0000-00-00';"    
-    );
-    $dbh->do(
-        "UPDATE suggestions SET accepteddate = '1970-01-01' WHERE accepteddate = '0000-00-00';"    
-    );
+    eval {
+        local $dbh->{PrintError} = 0;
+        $dbh->do(
+            "UPDATE suggestions SET suggesteddate = '1970-01-01' WHERE suggesteddate = '0000-00-00';"
+        );
+        $dbh->do(
+            "UPDATE suggestions SET manageddate = '1970-01-01' WHERE manageddate = '0000-00-00';"
+        );
+        $dbh->do(
+            "UPDATE suggestions SET accepteddate = '1970-01-01' WHERE accepteddate = '0000-00-00';"
+        );
+    };
 
     # Add constraint for suggestedby
     unless ( foreign_key_exists( 'suggestions', 'suggestions_ibfk_suggestedby' ) )
@@ -17809,8 +17812,10 @@ if( CheckVersion( $DBversion ) ) {
     }
 
     # Rename accountlines_ibfk_2 to accountlines_ibfk_items
-    if ( foreign_key_exists( 'accountlines', 'accountlines_ibfk_2' ) && !foreign_key_exists( 'accountlines', 'accountlines_ibfk_items' ) ) {
+    if ( foreign_key_exists( 'accountlines', 'accountlines_ibfk_2' ) ) {
         $dbh->do("ALTER TABLE accountlines DROP FOREIGN KEY accountlines_ibfk_2");
+    }
+    unless ( foreign_key_exists( 'accountlines', 'accountlines_ibfk_items' ) ) {
         $dbh->do("ALTER TABLE accountlines ADD CONSTRAINT `accountlines_ibfk_items` FOREIGN KEY (`itemnumber`) REFERENCES `items` (`itemnumber`) ON DELETE SET NULL ON UPDATE CASCADE");
     }
 
@@ -20707,6 +20712,21 @@ if( CheckVersion( $DBversion ) ) {
         note
     );
 
+    $dbh->do(q|
+        DELETE i FROM issuingrules i
+        LEFT JOIN itemtypes it ON i.itemtype=it.itemtype
+        WHERE it.itemtype IS NULL AND i.itemtype!='*'
+    |);
+    $dbh->do(q|
+        DELETE i FROM issuingrules i
+        LEFT JOIN branches b ON i.branchcode=b.branchcode
+        WHERE b.branchcode IS NULL AND i.branchcode!='*'
+    |);
+    $dbh->do(q|
+        DELETE i FROM issuingrules i
+        LEFT JOIN categories c ON i.categorycode=c.categorycode
+        WHERE c.categorycode IS NULL AND i.categorycode!='*'
+    |);
     if ( column_exists( 'issuingrules', 'categorycode' ) ) {
         foreach my $column ( @columns ) {
             $dbh->do("
@@ -21670,32 +21690,9 @@ $DBversion = '19.12.00.076';
 if( CheckVersion( $DBversion ) ) {
     my @warnings;
 
-    $dbh->do(q|
-        UPDATE
-          serial
-        SET
-          planneddate = NULL
-        WHERE
-          planneddate = '0000-00-00'
-    |);
-
-    $dbh->do(q|
-        UPDATE
-          serial
-        SET
-          publisheddate = NULL
-        WHERE
-          publisheddate = '0000-00-00'
-    |);
-
-    $dbh->do(q|
-        UPDATE
-          serial
-        SET
-          claimdate = NULL
-        WHERE
-          claimdate = '0000-00-00'
-    |);
+    sanitize_zero_date('serial', 'planneddate');
+    sanitize_zero_date('serial', 'publisheddate');
+    sanitize_zero_date('serial', 'claimdate');
 
     $dbh->do(q|
         ALTER TABLE serial
@@ -21704,6 +21701,15 @@ if( CheckVersion( $DBversion ) ) {
 
     unless ( foreign_key_exists( 'serial', 'serial_ibfk_1' ) ) {
         my $serials = $dbh->selectall_arrayref(q|
+            SELECT serialid FROM serial JOIN subscription USING (subscriptionid) WHERE serial.biblionumber != subscription.biblionumber
+        |, { Slice => {} });
+        if ( @$serials ) {
+            push @warnings, q|WARNING - The following serials will be updated, they were attached to a different biblionumber than their related subscription: | . join ", ", map { $_->{serialid} } @$serials;
+            $dbh->do(q|
+                UPDATE serial JOIN subscription USING (subscriptionid) SET serial.biblionumber = subscription.biblionumber WHERE serial.biblionumber != subscription.biblionumber
+            |);
+        }
+        $serials = $dbh->selectall_arrayref(q|
             SELECT serialid FROM serial WHERE biblionumber NOT IN (SELECT biblionumber FROM biblio)
         |, { Slice => {} });
         if ( @$serials ) {
@@ -21747,6 +21753,9 @@ if( CheckVersion( $DBversion ) ) {
 
     unless ( foreign_key_exists( 'subscriptionhistory', 'subscription_history_ibfk_1' ) ) {
         $dbh->do(q|
+            UPDATE subscriptionhistory JOIN subscription USING (subscriptionid) SET subscriptionhistory.biblionumber = subscription.biblionumber WHERE subscriptionhistory.biblionumber != subscription.biblionumber
+        |);
+        $dbh->do(q|
             DELETE FROM subscriptionhistory WHERE biblionumber NOT IN (SELECT biblionumber FROM biblio)
         |);
         $dbh->do(q|
@@ -22118,7 +22127,7 @@ if( CheckVersion( $DBversion ) ) {
     });
     $dbh->do(q{
         UPDATE letter SET
-        content = REPLACE(content, "The following item [% biblio.title %] has correctly been renewed and is now due [% checkout.date_due %]", "The following item, [% biblio.title %], has correctly been renewed and is now due on [% checkout.date_due as_due_date => 1 %]
+        content = REPLACE(content, "The following item [% biblio.title %] has correctly been renewed and is now due [% checkout.date_due %]", "The following item, [% biblio.title %], has correctly been renewed and is now due on [% checkout.date_due | $KohaDates as_due_date => 1 %]
 ")
         WHERE code = 'AUTO_RENEWALS';
     });
@@ -22579,16 +22588,18 @@ if( CheckVersion( $DBversion ) ) {
 $DBversion = '20.06.00.023';
 if( CheckVersion( $DBversion ) ) {
 
-    my $QuoteOfTheDay = C4::Context->preference('QuoteOfTheDay');
+    my ( $QuoteOfTheDay ) = $dbh->selectrow_array(q|
+        SELECT value FROM systempreferences WHERE variable='QuoteOfTheDay'
+    |);
+    my $options = $QuoteOfTheDay ? 'opac' : '';
     $dbh->do( q|
         UPDATE systempreferences
-        SET options = 'intranet,opac',
+        SET value = ?,
+            options = 'intranet,opac',
             explanation = 'Enable or disable display of Quote of the Day on the OPAC and staff interface home page',
             type = 'multiple'
         WHERE variable = 'QuoteOfTheDay'
-    | );
-
-    C4::Context->set_preference('QuoteOfTheDay', $QuoteOfTheDay ? 'opac' : '');
+    |, undef, $options );
 
     NewVersion( $DBversion, 16371, "Quote of the Day (QOTD) for the staff interface " );
 }
@@ -22662,10 +22673,6 @@ if ( CheckVersion( $DBversion ) ) {
         INSERT IGNORE INTO authorised_value_categories( category_name, is_system ) VALUES ('HOLD_CANCELLATION', 0);
     });
 
-    $dbh->do(q{
-INSERT IGNORE INTO `letter` VALUES ('reserves','HOLD_CANCELLATION','','Hold Cancellation',0,'Your hold was canceled.','[%- USE AuthorisedValues -%]\r\nDear [% borrower.firstname %] [% borrower.surname %],\r\n\r\nYour hold for [% biblio.title %] was canceled for the following reason: [% AuthorisedValues.GetByCode( \'HOLD_CANCELLATION\', hold.cancellation_reason ) %]','email','default');
-    });
-
     if ( !column_exists( 'reserves', 'cancellation_reason' ) ) {
         $dbh->do(q{
             ALTER TABLE reserves ADD COLUMN `cancellation_reason` varchar(80) default NULL AFTER cancellationdate;
@@ -23012,7 +23019,7 @@ if( CheckVersion( $DBversion ) ) {
 $DBversion = '20.06.00.049';
 if( CheckVersion( $DBversion ) ) {
 
-    if( !column_exists( 'biblioimages', 'itemnumber' ) ) {
+    if( TableExists('biblioimages') && !column_exists( 'biblioimages', 'itemnumber' ) ) {
         $dbh->do(q|
             ALTER TABLE biblioimages
             ADD COLUMN itemnumber INT(11) DEFAULT NULL
@@ -23176,6 +23183,16 @@ if( CheckVersion( $DBversion ) ) {
 
 $DBversion = '20.06.00.058';
 if( CheckVersion( $DBversion ) ) {
+
+    # Adding the ON DELETE CASCASE ON UPDATE CASCADE, in case it's missing (from 9016 - 3.15.00.039)
+    $dbh->do( q{
+        ALTER TABLE letter DROP FOREIGN KEY message_transport_type_fk
+    } );
+
+    $dbh->do( q{
+        ALTER TABLE letter ADD CONSTRAINT message_transport_type_fk FOREIGN KEY (message_transport_type) REFERENCES message_transport_types(message_transport_type) ON DELETE CASCADE ON UPDATE CASCADE
+    } );
+
     $dbh->do(q{
         UPDATE message_transport_types SET message_transport_type = "itiva" WHERE message_transport_type = "phone"
     });
@@ -23193,149 +23210,1333 @@ if( CheckVersion( $DBversion ) ) {
     NewVersion( $DBversion, 19482, "Add mandatory column to search_field for ES mapping" );
 }
 
-# SEE bug 13068
-# if there is anything in the atomicupdate, read and execute it.
-my $update_dir = C4::Context->config('intranetdir') . '/installer/data/mysql/atomicupdate/';
-opendir( my $dirh, $update_dir );
-foreach my $file ( sort readdir $dirh ) {
-    next if $file !~ /\.(sql|perl)$/;  #skip other files
-    next if $file eq 'skeleton.perl'; # skip the skeleton file
-    print "DEV atomic update: $file\n";
-    if ( $file =~ /\.sql$/ ) {
-        my $installer = C4::Installer->new();
-        my $rv = $installer->load_sql( $update_dir . $file ) ? 0 : 1;
-    } elsif ( $file =~ /\.perl$/ ) {
-        my $code = read_file( $update_dir . $file );
-        eval $code; ## no critic (StringyEval)
-        say "Atomic update generated errors: $@" if $@;
-    }
-}
-
-=head1 FUNCTIONS
+$DBversion = '20.06.00.060';
+if( CheckVersion( $DBversion ) ) {
+    $dbh->do(q{
+        INSERT IGNORE INTO systempreferences ( `variable`, `value`, `options`, `explanation`, `type` ) VALUES
+        ('PhoneNotification','0',NULL,'If ON, enables generation of phone notifications to be sent by plugins','YesNo')
+    });
 
-=head2 DropAllForeignKeys($table)
+    $dbh->do(q{
+        INSERT IGNORE INTO message_transport_types (message_transport_type) VALUES ('phone')
+    });
 
-Drop all foreign keys of the table $table
+    $dbh->do(q{
+        INSERT IGNORE INTO `message_transports`
+        (`message_attribute_id`, `message_transport_type`, `is_digest`, `letter_module`, `letter_code`)
+        VALUES
+        (1, 'phone',       0, 'circulation', 'DUE'),
+        (1, 'phone',       1, 'circulation', 'DUEDGST'),
+        (2, 'phone',       0, 'circulation', 'PREDUE'),
+        (2, 'phone',       1, 'circulation', 'PREDUEDGST'),
+        (4, 'phone',       0, 'reserves',    'HOLD'),
+        (5, 'phone',       0, 'circulation', 'CHECKIN'),
+        (6, 'phone',       0, 'circulation', 'CHECKOUT');
+    });
 
-=cut
+    NewVersion( $DBversion, 25334, "Add generic 'phone' message transport type");
+}
 
-sub DropAllForeignKeys {
-    my ($table) = @_;
-    # get the table description
-    my $sth = $dbh->prepare("SHOW CREATE TABLE $table");
-    $sth->execute;
-    my $vsc_structure = $sth->fetchrow;
-    # split on CONSTRAINT keyword
-    my @fks = split /CONSTRAINT /,$vsc_structure;
-    # parse each entry
-    foreach (@fks) {
-        # isolate what is before FOREIGN KEY, if there is something, it's a foreign key to drop
-        $_ = /(.*) FOREIGN KEY.*/;
-        my $id = $1;
-        if ($id) {
-            # we have found 1 foreign, drop it
-            $dbh->do("ALTER TABLE $table DROP FOREIGN KEY $id");
-            $id="";
-        }
+$DBversion = '20.06.00.061';
+if( CheckVersion( $DBversion ) ) {
+    if ( !column_exists( 'reserves', 'desk_id' ) ) {
+        $dbh->do(q{
+             ALTER TABLE reserves ADD COLUMN desk_id INT(11) DEFAULT NULL AFTER branchcode,
+             ADD KEY desk_id (`desk_id`),
+             ADD CONSTRAINT `reserves_ibfk_6` FOREIGN KEY (`desk_id`) REFERENCES `desks` (`desk_id`) ON DELETE SET NULL ON UPDATE CASCADE ;
+        });
+        $dbh->do(q{
+             ALTER TABLE old_reserves ADD COLUMN desk_id INT(11) DEFAULT NULL AFTER branchcode,
+             ADD KEY `old_desk_id` (`desk_id`);
+        });
     }
-}
 
+    NewVersion( $DBversion, 24412, "Attach waiting reserve to desk" );
+}
 
-=head2 TransformToNum
+$DBversion = '20.06.00.062';
+if( CheckVersion( $DBversion ) ) {
+    $dbh->do( "UPDATE circulation_rules SET rule_name = 'lostreturn' WHERE rule_name = 'refund'" );
+    $dbh->do( "UPDATE circulation_rules SET rule_value = 'refund' WHERE rule_name = 'lostreturn' AND rule_value = 1" );
 
-Transform the Koha version from a 4 parts string
-to a number, with just 1 .
+    NewVersion( $DBversion, 23091, "Update refund rules");
+}
 
-=cut
+$DBversion = '20.06.00.063';
+if( CheckVersion( $DBversion ) ) {
+    $dbh->do(q{INSERT IGNORE INTO circulation_rules (branchcode, categorycode, itemtype, rule_name, rule_value) VALUES (NULL, NULL, NULL, 'decreaseloanholds', NULL) });
 
-sub TransformToNum {
-    my $version = shift;
-    # remove the 3 last . to have a Perl number
-    $version =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
-    # three X's at the end indicate that you are testing patch with dbrev
-    # change it into 999
-    # prevents error on a < comparison between strings (should be: lt)
-    $version =~ s/XXX$/999/;
-    return $version;
+    NewVersion( $DBversion, 14866, "Add decreaseloanholds circulation rule" );
 }
 
-=head2 SetVersion
+$DBversion = '20.06.00.064';
+if ( CheckVersion($DBversion) ) {
 
-set the DBversion in the systempreferences
+    $dbh->do(q{
+        INSERT IGNORE INTO account_credit_types (code, description, can_be_added_manually, is_system)
+        VALUES ('CANCELLATION', 'Cancelled charge', 0, 1)
+    });
 
-=cut
+    $dbh->do(q{
+        INSERT IGNORE INTO account_offset_types ( type ) VALUES ('CANCELLATION');
+    });
 
-sub SetVersion {
-    return if $_[0]=~ /XXX$/;
-      #you are testing a patch with a db revision; do not change version
-    my $kohaversion = TransformToNum($_[0]);
-    if (C4::Context->preference('Version')) {
-      my $finish=$dbh->prepare("UPDATE systempreferences SET value=? WHERE variable='Version'");
-      $finish->execute($kohaversion);
-    } else {
-      my $finish=$dbh->prepare("INSERT into systempreferences (variable,value,explanation) values ('Version',?,'The Koha database version. WARNING: Do not change this value manually, it is maintained by the webinstaller')");
-      $finish->execute($kohaversion);
-    }
-    C4::Context::clear_syspref_cache(); # invalidate cached preferences
+    NewVersion( $DBversion, 24603, "Add CANCELLATION credit_type_code" );
 }
 
-sub NewVersion {
-    my ( $DBversion, $bug_number, $descriptions ) = @_;
+$DBversion = '20.06.00.065';
+if( CheckVersion( $DBversion ) ) {
+    if( !column_exists( 'issues', 'issuer_id' ) ) {
+        $dbh->do( q| ALTER TABLE issues ADD issuer_id INT(11) DEFAULT NULL AFTER borrowernumber | );
+    }
+    if (!foreign_key_exists( 'issues', 'issues_ibfk_borrowers_borrowernumber' )) {
+        $dbh->do( q| ALTER TABLE issues ADD CONSTRAINT `issues_ibfk_borrowers_borrowernumber` FOREIGN KEY (`issuer_id`) REFERENCES `borrowers` (`borrowernumber`) ON DELETE SET NULL ON UPDATE CASCADE | );
+    }
+    if( !column_exists( 'old_issues', 'issuer_id' ) ) {
+        $dbh->do( q| ALTER TABLE old_issues ADD issuer_id INT(11) DEFAULT NULL AFTER borrowernumber | );
+    }
+    if (!foreign_key_exists( 'old_issues', 'old_issues_ibfk_borrowers_borrowernumber' )) {
+        $dbh->do( q| ALTER TABLE old_issues ADD CONSTRAINT `old_issues_ibfk_borrowers_borrowernumber` FOREIGN KEY (`issuer_id`) REFERENCES `borrowers` (`borrowernumber`) ON DELETE SET NULL ON UPDATE CASCADE | );
+    }
 
-    SetVersion($DBversion);
+    $dbh->do( q| INSERT IGNORE INTO systempreferences (variable, value, explanation, options, type) VALUES ('RecordStaffUserOnCheckout', '0', 'If enabled, when an item is checked out, the user who checked out the item is recorded', '', 'YesNo'); | );
 
-    unless ( ref($descriptions) ) {
-        $descriptions = [ $descriptions ];
+    NewVersion( $DBversion, 23916, [ "Add new [old_]issues.issuer DB fields", "Add new syspref RecordStaffUserOnCheckout" ] );
+}
+
+$DBversion = '20.06.00.066';
+if( CheckVersion( $DBversion ) ) {
+    if( !column_exists( 'branches', 'branchillemail' ) ) {
+        $dbh->do( q| ALTER TABLE branches ADD branchillemail LONGTEXT AFTER branchemail | );
     }
-    my $first = 1;
-    my $time = POSIX::strftime("%H:%M:%S",localtime);
-    for my $description ( @$descriptions ) {
-        if ( @$descriptions > 1 ) {
-            if ( $first ) {
-                unless ( $bug_number ) {
-                    say sprintf "Upgrade to %s done [%s]: %s", $DBversion, $time, $description;
-                } else {
-                    say sprintf "Upgrade to %s done [%s]: Bug %5s - %s", $DBversion, $time, $bug_number, $description;
-                }
-            } else {
-                say sprintf "\t\t\t\t\t\t   - %s", $description;
-            }
-        } else {
-            unless ( $bug_number ) {
-                say sprintf "Upgrade to %s done [%s]: %s", $DBversion, $time, $description;
-            } else {
-                say sprintf "Upgrade to %s done [%s]: Bug %5s - %s", $DBversion, $time, $bug_number, $description;
-            }
-        }
-        $first = 0;
+    # Add new sysprefs
+    $dbh->do( q| INSERT IGNORE INTO systempreferences (variable, value, explanation, options, type) VALUES ('ILLDefaultStaffEmail', '', 'Fallback email address for staff ILL notices to be sent to in the absence of a branch address', NULL, 'Free'); | );
+    $dbh->do( q| INSERT IGNORE INTO systempreferences (variable, value, explanation, options, type) VALUES ('ILLSendStaffNotices', NULL, 'Send these ILL notices to staff', NULL, 'multiple'); | );
+    # Add new notices
+    $dbh->do( q| INSERT IGNORE INTO letter(module, code, branchcode, name, is_html, title, content, message_transport_type, lang) VALUES ('ill', 'ILL_PICKUP_READY', '', 'ILL request ready for pickup', 0, "Interlibrary loan request ready for pickup", "Dear [% borrower.firstname %] [% borrower.surname %],\n\nThe Interlibrary loans request number [% illrequest.illrequest_id %] you placed for:\n\n- [% ill_bib_title %] - [% ill_bib_author %]\n\nis ready for pick up from [% branch.branchname %].\n\nKind Regards\n\n[% branch.branchname %]\n[% branch.branchaddress1 %]\n[% branch.branchaddress2 %]\n[% branch.branchaddress3 %]\n[% branch.branchcity %]\n[% branch.branchstate %]\n[% branch.branchzip %]\n[% branch.branchphone %]\n[% branch.branchillemail %]\n[% branch.branchemail %]", 'email', 'default'); | );
+    $dbh->do( q| INSERT IGNORE INTO letter(module, code, branchcode, name, is_html, title, content, message_transport_type, lang) VALUES ('ill', 'ILL_REQUEST_UNAVAIL', '', 'ILL request unavailable', 0, "Interlibrary loan request unavailable", "Dear [% borrower.firstname %] [% borrower.surname %],\n\nThe Interlibrary loans request number [% illrequest.illrequest_id %] you placed for\n\n- [% ill_bib_title %] - [% ill_bib_author %]\n\nis unfortunately unavailable.\n\nKind Regards\n\n[% branch.branchname %]\n[% branch.branchaddress1 %]\n[% branch.branchaddress2 %]\n[% branch.branchaddress3 %]\n[% branch.branchcity %]\n[% branch.branchstate %]\n[% branch.branchzip %]\n[% branch.branchphone %]\n[% branch.branchillemail %]\n[% branch.branchemail %]", 'email', 'default'); | );
+    $dbh->do( q| INSERT IGNORE INTO letter(module, code, branchcode, name, is_html, title, content, message_transport_type, lang) VALUES ('ill', 'ILL_REQUEST_CANCEL', '', 'ILL request cancelled', 0, "Interlibrary loan request cancelled", "The patron for interlibrary loans request [% illrequest.illrequest_id %], with the following details, has requested cancellation of this ILL request:\n\n[% ill_full_metadata %]", 'email', 'default'); | );
+    $dbh->do( q| INSERT IGNORE INTO letter(module, code, branchcode, name, is_html, title, content, message_transport_type, lang) VALUES ('ill', 'ILL_REQUEST_MODIFIED', '', 'ILL request modified', 0, "Interlibrary loan request modified", "The patron for interlibrary loans request [% illrequest.illrequest_id %], with the following details, has modified this ILL request:\n\n[% ill_full_metadata %]", 'email', 'default'); | );
+    $dbh->do( q| INSERT IGNORE INTO letter(module, code, branchcode, name, is_html, title, content, message_transport_type, lang) VALUES ('ill', 'ILL_PARTNER_REQ', '', 'ILL request to partners', 0, "Interlibrary loan request to partners", "Dear Sir/Madam,\n\nWe would like to request an interlibrary loan for a title matching the following description:\n\n[% ill_full_metadata %]\n\nPlease let us know if you are able to supply this to us.\n\nKind Regards\n\n[% branch.branchname %]\n[% branch.branchaddress1 %]\n[% branch.branchaddress2 %]\n[% branch.branchaddress3 %]\n[% branch.branchcity %]\n[% branch.branchstate %]\n[% branch.branchzip %]\n[% branch.branchphone %]\n[% branch.branchillemail %]\n[% branch.branchemail %]", 'email', 'default'); | );
+    $dbh->do( q| INSERT IGNORE INTO letter(module, code, branchcode, name, is_html, title, content, message_transport_type, lang) VALUES ('ill', 'ILL_PICKUP_READY', '', 'ILL request ready for pickup', 0, "Interlibrary loan request ready for pickup", "Dear [% borrower.firstname %] [% borrower.surname %],\n\nThe Interlibrary loans request number [% illrequest.illrequest_id %] you placed for:\n\n- [% ill_bib_title %] - [% ill_bib_author %]\n\nis ready for pick up from [% branch.branchname %].\n\nKind Regards\n\n[% branch.branchname %]\n[% branch.branchaddress1 %]\n[% branch.branchaddress2 %]\n[% branch.branchaddress3 %]\n[% branch.branchcity %]\n[% branch.branchstate %]\n[% branch.branchzip %]\n[% branch.branchphone %]\n[% branch.branchillemail %]\n[% branch.branchemail %]", 'sms', 'default'); | );
+    $dbh->do( q| INSERT IGNORE INTO letter(module, code, branchcode, name, is_html, title, content, message_transport_type, lang) VALUES ('ill', 'ILL_REQUEST_UNAVAIL', '', 'ILL request unavailable', 0, "Interlibrary loan request unavailable", "Dear [% borrower.firstname %] [% borrower.surname %],\n\nThe Interlibrary loans request number [% illrequest.illrequest_id %] you placed for\n\n- [% ill_bib_title %] - [% ill_bib_author %]\n\nis unfortunately unavailable.\n\nKind Regards\n\n[% branch.branchname %]\n[% branch.branchaddress1 %]\n[% branch.branchaddress2 %]\n[% branch.branchaddress3 %]\n[% branch.branchcity %]\n[% branch.branchstate %]\n[% branch.branchzip %]\n[% branch.branchphone %]\n[% branch.branchillemail %]\n[% branch.branchemail %]", 'sms', 'default'); | );
+    $dbh->do( q| INSERT IGNORE INTO letter(module, code, branchcode, name, is_html, title, content, message_transport_type, lang) VALUES ('ill', 'ILL_REQUEST_CANCEL', '', 'ILL request cancelled', 0, "Interlibrary loan request cancelled", "The patron for interlibrary loans request [% illrequest.illrequest_id %], with the following details, has requested cancellation of this ILL request:\n\n[% ill_full_metadata %]", 'sms', 'default'); | );
+    $dbh->do( q| INSERT IGNORE INTO letter(module, code, branchcode, name, is_html, title, content, message_transport_type, lang) VALUES ('ill', 'ILL_REQUEST_MODIFIED', '', 'ILL request modified', 0, "Interlibrary loan request modified", "The patron for interlibrary loans request [% illrequest.illrequest_id %], with the following details, has modified this ILL request:\n\n[% ill_full_metadata %]", 'sms', 'default'); | );
+    $dbh->do( q| INSERT IGNORE INTO letter(module, code, branchcode, name, is_html, title, content, message_transport_type, lang) VALUES ('ill', 'ILL_PARTNER_REQ', '', 'ILL request to partners', 0, "Interlibrary loan request to partners", "Dear Sir/Madam,\n\nWe would like to request an interlibrary loan for a title matching the following description:\n\n[% ill_full_metadata %]\n\nPlease let us know if you are able to supply this to us.\n\nKind Regards\n\n[% branch.branchname %]\n[% branch.branchaddress1 %]\n[% branch.branchaddress2 %]\n[% branch.branchaddress3 %]\n[% branch.branchcity %]\n[% branch.branchstate %]\n[% branch.branchzip %]\n[% branch.branchphone %]\n[% branch.branchillemail %]\n[% branch.branchemail %]", 'sms', 'default'); | );
+    # Add patron messaging preferences
+    $dbh->do( q| INSERT IGNORE INTO message_attributes (message_name, takes_days) VALUES ('Ill_ready', 0); | );
+    my $ready_id = $dbh->last_insert_id(undef, undef, 'message_attributes', undef);
+    if (defined $ready_id) {
+        $dbh->do( qq(INSERT IGNORE INTO message_transports (message_attribute_id, message_transport_type, is_digest, letter_module, letter_code) VALUES ($ready_id, 'email', 0, 'ill', 'ILL_PICKUP_READY');) );
+        $dbh->do( qq(INSERT IGNORE INTO message_transports (message_attribute_id, message_transport_type, is_digest, letter_module, letter_code) VALUES ($ready_id, 'sms', 0, 'ill', 'ILL_PICKUP_READY');) );
+        $dbh->do( qq(INSERT IGNORE INTO message_transports (message_attribute_id, message_transport_type, is_digest, letter_module, letter_code) VALUES ($ready_id, 'phone', 0, 'ill', 'ILL_PICKUP_READY');) );
+    }
+    $dbh->do( q| INSERT IGNORE INTO message_attributes (message_name, takes_days) VALUES ('Ill_unavailable', 0); | );
+    my $unavail_id = $dbh->last_insert_id(undef, undef, 'message_attributes', undef);
+    if (defined $unavail_id) {
+        $dbh->do( qq(INSERT IGNORE INTO message_transports (message_attribute_id, message_transport_type, is_digest, letter_module, letter_code) VALUES ($unavail_id, 'email', 0, 'ill', 'ILL_REQUEST_UNAVAIL');) );
+        $dbh->do( qq(INSERT IGNORE INTO message_transports (message_attribute_id, message_transport_type, is_digest, letter_module, letter_code) VALUES ($unavail_id, 'sms', 0, 'ill', 'ILL_REQUEST_UNAVAIL');) );
+        $dbh->do( qq(INSERT IGNORE INTO message_transports (message_attribute_id, message_transport_type, is_digest, letter_module, letter_code) VALUES ($unavail_id, 'phone', 0, 'ill', 'ILL_REQUEST_UNAVAIL');) );
     }
+
+    NewVersion( $DBversion, 22818, "Add ILL notices" );
 }
 
-=head2 CheckVersion
+$DBversion = '20.06.00.067';
+if( CheckVersion( $DBversion ) ) {
+    $dbh->do(q{
+        INSERT IGNORE INTO systempreferences ( `variable`, `value`, `options`, `explanation`, `type` ) VALUES
+        ('OPACHoldsHistory','0','','If ON, enables display of Patron Holds History in OPAC','YesNo')
+    });
 
-Check whether a given update should be run when passed the proposed version
-number. The update will always be run if the proposed version is greater
-than the current database version and less than or equal to the version in
-kohaversion.pl. The update is also run if the version contains XXX, though
-this behavior will be changed following the adoption of non-linear updates
-as implemented in bug 7167.
+    NewVersion( $DBversion, 20936, "Add new system preference OPACHoldsHistory");
+}
 
-=cut
+$DBversion = '20.06.00.068';
+if( CheckVersion( $DBversion ) ) {
+  if( !TableExists( 'import_batch_profiles' ) ) {
+    $dbh->do(q{
+      CREATE TABLE `import_batch_profiles` ( -- profile for batches of marc records to be imported
+        `id` int(11) NOT NULL auto_increment, -- unique identifier and primary key
+        `name` varchar(100) NOT NULL, -- name of this profile
+        `matcher_id` int(11) default NULL, -- the id of the match rule used (matchpoints.matcher_id)
+        `template_id` int(11) default NULL, -- the id of the marc modification template
+        `overlay_action` varchar(50) default NULL, -- how to handle duplicate records
+        `nomatch_action` varchar(50) default NULL, -- how to handle records where no match is found
+        `item_action` varchar(50) default NULL, -- what to do with item records
+        `parse_items` tinyint(1) default NULL, -- should items be parsed
+        `record_type` varchar(50) default NULL, -- type of record in the batch
+        `encoding` varchar(50) default NULL, -- file encoding
+        `format` varchar(50) default NULL, -- marc format
+        `comments` LONGTEXT, -- any comments added when the file was uploaded
+        PRIMARY KEY (`id`),
+        UNIQUE KEY `u_import_batch_profiles__name` (`name`)
+      ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci
+    });
+  }
 
-sub CheckVersion {
-    my ($proposed_version) = @_;
-    my $version_number = TransformToNum($proposed_version);
+  if(!column_exists('import_batches', 'profile_id')) {
+    $dbh->do(q{
+      ALTER TABLE import_batches ADD COLUMN `profile_id` int(11) default NULL AFTER comments
+    });
 
-    # The following line should be deleted when bug 7167 is pushed
-    return 1 if ( $proposed_version =~ m/XXX/ );
+    $dbh->do(q{
+      ALTER TABLE import_batches ADD CONSTRAINT `import_batches_ibfk_1` FOREIGN KEY (`profile_id`) REFERENCES `import_batch_profiles` (`id`) ON DELETE SET NULL ON UPDATE SET NULL
+    });
+  }
 
-    if ( C4::Context->preference("Version") < $version_number
-        && $version_number <= TransformToNum( $Koha::VERSION ) )
-    {
-        return 1;
+  NewVersion( $DBversion, 23019, "Add import_batch_profiles table and profile_id column in import_batches" );
+}
+
+$DBversion = '20.06.00.069';
+if( CheckVersion( $DBversion ) ) {
+    my ($count) = $dbh->selectrow_array(
+        q|
+            SELECT COUNT(*)
+            FROM circulation_rules
+            WHERE rule_name = 'unseen_renewals_allowed'
+        |
+    );
+    if ($count == 0) {
+        $dbh->do( q|
+            INSERT INTO circulation_rules (rule_name, rule_value)
+            VALUES ('unseen_renewals_allowed', '')
+        | );
     }
-    else {
-        return 0;
+
+    if( !column_exists( 'issues', 'unseen_renewals' ) ) {
+        $dbh->do( q| ALTER TABLE issues ADD unseen_renewals TINYINT(4) DEFAULT 0 NOT NULL AFTER renewals | );
+    }
+    if( !column_exists( 'old_issues', 'unseen_renewals' ) ) {
+        $dbh->do( q| ALTER TABLE old_issues ADD unseen_renewals TINYINT(4) DEFAULT 0 NOT NULL AFTER renewals | );
+    }
+
+    $dbh->do( q| INSERT IGNORE INTO systempreferences (variable, value, explanation, options, type) VALUES ('UnseenRenewals', '0', 'If enabled, a renewal can be recorded as "unseen" by the library and count against the borrowers unseen renewals limit', '', 'YesNo'); | );
+
+    NewVersion( $DBversion, 24083, ["Add circulation_rules 'unseen_renewals_allowed'", "Add issues.unseen_renewals & old_issues.unseen_renewals)", "Add new system preference UnseenRenewals"] );
+}
+
+$DBversion = '20.11.00.000';
+if( CheckVersion( $DBversion ) ) {
+    NewVersion( $DBversion, "", "Koha 20.11.00 release" );
+}
+
+$DBversion = '20.12.00.000';
+if( CheckVersion( $DBversion ) ) {
+    NewVersion( $DBversion, "", "Sorry, this is my first life, I am still learning!" );
+}
+
+$DBversion = '20.12.00.001';
+if( CheckVersion( $DBversion ) ) {
+    $dbh->do(q{
+        INSERT IGNORE INTO systempreferences ( `variable`, `value`, `options`, `explanation`, `type` ) VALUES
+       ('ElasticsearchCrossFields', '1', '', 'Enable "cross_fields" option for searches using Elastic search.', 'YesNo')
+    });
+    NewVersion( $DBversion, 27252, "Add ElasticsearchCrossFields system preference");
+}
+
+$DBversion = '20.12.00.002';
+if( CheckVersion( $DBversion ) ) {
+    $dbh->do(q{UPDATE systempreferences SET `type` = 'Choice' WHERE `variable` = 'UsageStatsCountry'});
+    NewVersion( $DBversion, 27351, "Set type for UsageStatsCountry to Choice");
+}
+
+$DBversion = '20.12.00.003';
+if( CheckVersion( $DBversion ) ) {
+    $dbh->do(q{UPDATE systempreferences SET `type` = 'Choice' WHERE `variable` = 'Mana'});
+    NewVersion( $DBversion, 27349, "Update type for Mana system preference to Choice");
+}
+
+$DBversion = '20.12.00.004';
+if( CheckVersion( $DBversion ) ) {
+    $dbh->do(q{UPDATE systempreferences set variable="TaxRates" WHERE variable="gist"});
+    NewVersion( $DBversion, 27485, "Rename system preference 'gist' to 'TaxRates'");
+}
+
+$DBversion = '20.12.00.005';
+if( CheckVersion( $DBversion ) ) {
+    $dbh->do(q{UPDATE systempreferences set variable="OPACLanguages" WHERE variable="opaclanguages"});
+    NewVersion( $DBversion, 27491, "Rename system preference 'opaclanguages' to 'OPACLanguages'");
+}
+
+$DBversion = '20.12.00.006';
+if( CheckVersion( $DBversion ) ) {
+    $dbh->do(q{UPDATE systempreferences SET variable="OPACComments" WHERE variable="reviewson" });
+    NewVersion( $DBversion, 27487, "Rename system preference 'reviewson' to 'OPACComments");
+}
+
+$DBversion = '20.12.00.007';
+if( CheckVersion( $DBversion ) ) {
+    $dbh->do(q{UPDATE systempreferences set variable="CSVDelimiter" WHERE variable="delimiter"});
+    NewVersion( $DBversion, 27486, "Renaming system preference 'delimiter' to 'CSVDelimiter'");
+}
+
+$DBversion = '20.12.00.008';
+if( CheckVersion( $DBversion ) ) {
+    $dbh->do(q{
+        UPDATE systempreferences
+        SET options = "claim_returned|batchmod|moredetail|cronjob|additem|pendingreserves|onpayment"
+        WHERE variable = "MarkLostItemsAsReturned";
+    });
+    NewVersion( $DBversion, 25552, "Add missing Claims Returned option to MarkLostItemsAsReturned");
+}
+
+$DBversion = '20.12.00.009';
+if( CheckVersion( $DBversion ) ) {
+    $dbh->do( "UPDATE systempreferences SET variable = 'UseICUStyleQUotes' WHERE variable = 'UseICU'" );
+    NewVersion( $DBversion, 27581, "Rename system preference 'UseICU' to 'UseICUStyleQuotes'");
+}
+
+$DBversion = '20.12.00.010';
+if( CheckVersion( $DBversion ) ) {
+    $dbh->do( q{
+        DELETE FROM systempreferences WHERE variable="OpacGroupResults"
+    });
+
+    NewVersion( $DBversion, 20410, "Remove OpacGroupResults");
+}
+
+$DBversion = '20.12.00.011';
+if ( CheckVersion($DBversion) ) {
+    $dbh->do( q{
+        INSERT IGNORE INTO systempreferences (variable,value,explanation,options,type)
+        VALUES
+        ('OPACShibOnly','0','If ON enables shibboleth only authentication for the opac','','YesNo'),
+        ('staffShibOnly','0','If ON enables shibboleth only authentication for the staff client','','YesNo')
+    } );
+    NewVersion( $DBversion, 18506, "Add OPACShibOnly and staffShibOnly system preferences" );
+}
+
+$DBversion = '20.12.00.012';
+if( CheckVersion( $DBversion ) ) {
+    my $category_exists = $dbh->selectrow_array("SELECT count(category_name) FROM authorised_value_categories WHERE category_name='UPLOAD'");
+    my $description;
+    if( $category_exists ){
+        $description = "The UPLOAD authorized value category exists. Update the 'is_system' value to 1.";
+        $dbh->do( "UPDATE authorised_value_categories SET is_system = 1 WHERE category_name = 'UPLOAD'" );
+    } else {
+        $description = "The UPLOAD authorized value category does not exist. Create it.";
+        $dbh->do( "INSERT IGNORE INTO authorised_value_categories (category_name, is_system) VALUES ('UPLOAD', 1)" );
+    }
+
+    NewVersion( $DBversion, 27598, ["Add UPLOAD as a built-in system authorized value category", $description] );
+}
+
+$DBversion = '20.12.00.013';
+if( CheckVersion( $DBversion ) ) {
+    $dbh->do(q{
+         INSERT IGNORE INTO systempreferences
+         (variable, value, explanation, options, type) VALUES
+         ('DefaultSaveRecordFileID', 'biblionumber', 'Defines whether the advanced cataloging editor will use the bibliographic record number or control number field to populate the name of the save file.', 'biblionumber|controlnumber', 'Choice')
+    });
+    NewVersion( $DBversion, 24108, "Add system preference DefaultSaveRecordFileID");
+}
+
+$DBversion = '20.12.00.014';
+if( CheckVersion( $DBversion ) ) {
+
+    sanitize_zero_date('aqorders', 'datecancellationprinted');
+    sanitize_zero_date('old_issues', 'returndate');
+
+    NewVersion( $DBversion, 7806, "Remove remaining possible 0000-00-00 values");
+}
+
+$DBversion = '20.12.00.015';
+if( CheckVersion( $DBversion ) ) {
+    $dbh->do( "UPDATE search_marc_to_field SET sort = 1 WHERE sort IS NULL" );
+    $dbh->do( "ALTER TABLE search_marc_to_field MODIFY COLUMN sort tinyint(1) DEFAULT 1 NOT NULL COMMENT 'Sort defaults to 1 (Yes) and creates sort fields in the index, 0 (no) will prevent this'" );
+    NewVersion( $DBversion, 27316, "In Elastisearch mappings convert NULL (Undef) for sort to 1 (Yes)");
+}
+
+$DBversion = '20.12.00.016';
+if( CheckVersion( $DBversion ) ) {
+
+    unless ( column_exists( 'marc_subfield_structure', 'display_order' ) ) {
+        $dbh->do(q{
+            ALTER TABLE marc_subfield_structure
+            ADD COLUMN display_order INT(2) NOT NULL DEFAULT 0 AFTER maxlength
+        });
+    }
+
+    unless ( column_exists( 'auth_subfield_structure', 'display_order' ) ) {
+        $dbh->do(q{
+            ALTER TABLE auth_subfield_structure
+            ADD COLUMN display_order INT(2) NOT NULL DEFAULT 0 AFTER defaultvalue
+        });
+    }
+
+    NewVersion( $DBversion, 8976, "Allow setting a default sequence of subfields in cataloguing editor" );
+}
+
+$DBversion = '20.12.00.017';
+if( CheckVersion( $DBversion ) ) {
+    $dbh->do(q|
+        INSERT IGNORE INTO systempreferences (variable,value,explanation,options,type)
+        VALUES ('CheckPrevCheckoutDelay','0', 'Maximum number of days that will trigger a warning if the patron has borrowed that item in the past when CheckPrevCheckout is enabled. Disabled if 0 or empty.', NULL, 'free')
+    |);
+
+    NewVersion( $DBversion, 26937, "Add CheckPrevCheckoutDelay system preference)" );
+}
+
+$DBversion = '20.12.00.018';
+if( CheckVersion( $DBversion ) ) {
+
+    $dbh->do(q|
+        UPDATE items
+        LEFT JOIN issues ON issues.itemnumber=items.itemnumber
+        SET items.onloan=CAST(issues.date_due AS DATE)
+        WHERE items.onloan IS NULL AND issues.issue_id IS NOT NULL
+    |);
+
+    NewVersion( $DBversion, 27808, "Adjust items.onloan if needed" );
+}
+
+$DBversion = '20.12.00.019';
+if( CheckVersion( $DBversion ) ) {
+
+    if( !column_exists( 'branchtransfers', 'datecancelled' ) ) {
+        $dbh->do(q|
+            ALTER TABLE `branchtransfers`
+            ADD COLUMN `datecancelled` datetime default NULL AFTER `datearrived`
+        |);
+    }
+
+    if( !column_exists( 'branchtransfers', 'cancellation_reason' ) ) {
+        $dbh->do(q|
+            ALTER TABLE `branchtransfers`
+            ADD COLUMN `cancellation_reason` ENUM('Manual', 'StockrotationAdvance', 'StockrotationRepatriation', 'ReturnToHome', 'ReturnToHolding', 'RotatingCollection', 'Reserve', 'LostReserve', 'CancelReserve') DEFAULT NULL AFTER `reason`
+        |);
+    }
+
+    NewVersion( $DBversion, 26057, "Add datecancelled field to branchtransfers");
+}
+
+$DBversion = '20.12.00.020';
+if ( CheckVersion($DBversion) ) {
+
+    # Update daterequested from datesent for stockrotation
+    $dbh->do(q|
+            UPDATE `branchtransfers`
+            SET
+              `daterequested` = `datesent`,
+              `datesent` = NULL
+            WHERE `reason` LIKE 'Stockrotation%'
+            AND   `datearrived` IS NULL
+    |);
+
+    NewVersion( $DBversion, 24446, "Update stockrotation 'daterequested' field in transfers table" );
+}
+
+$DBversion = '20.12.00.021';
+if( CheckVersion( $DBversion ) ) {
+    $dbh->do(q{
+        UPDATE systempreferences SET type="Free" WHERE variable="OverDriveClientSecret" OR variable="RecordedBooksClientSecret"
+    });
+    $dbh->do(q{
+        UPDATE systempreferences SET type="integer" WHERE variable="UsageStats"
+    });
+    $dbh->do(q{
+        UPDATE systempreferences
+        SET value="0"
+        WHERE ( ( type = "YesNo" AND ( value NOT IN ( "1", "0" ) OR value IS NULL ) ) )
+    });
+
+    NewVersion( $DBversion, 22824, "Update syspref values for YesNo");
+}
+
+$DBversion = '20.12.00.022';
+if( CheckVersion( $DBversion ) ) {
+    $dbh->do(q{ INSERT IGNORE INTO letter (module, code, branchcode, name, is_html, title, content, message_transport_type) VALUES
+        ('circulation','CHECKINSLIP','','Checkin slip',1,'Checkin slip',
+"<h3>[% branch.branchname %]</h3>
+Checked in items for [% borrower.title %] [% borrower.firstname %] [% borrower.initials %] [% borrower.surname %] <br />
+([% borrower.cardnumber %]) <br />
+
+[% today | $KohaDates %]<br />
+
+<h4>Checked in today</h4>
+[% FOREACH checkin IN old_checkouts %]
+[% SET item = checkin.item %]
+<p>
+[% item.biblio.title %] <br />
+Barcode: [% item.barcode %] <br />
+</p>
+[% END %]",
+        'print')
+    });
+
+    NewVersion( $DBversion, 12224, "Add CHECKINSLIP notice" );
+}
+
+$DBversion = '20.12.00.023';
+if( CheckVersion( $DBversion ) ) {
+
+    $dbh->do(q{
+        UPDATE systempreferences
+        SET value=REPLACE(value, '|', ',')
+        WHERE variable="OPACHoldsIfAvailableAtPickupExceptions"
+           OR variable="BatchCheckoutsValidCategories"
+    });
+    NewVersion( $DBversion, 27652, "Separate values for OPACHoldsIfAvailableAtPickupExceptions and BatchCheckoutsValidCategories with comma");
+}
+
+$DBversion = '20.12.00.024';
+if( CheckVersion( $DBversion ) ) {
+
+    $dbh->do( q{
+        INSERT IGNORE INTO letter (module, code, name, title, content, message_transport_type) VALUES ('circulation', 'AUTO_RENEWALS_DGST', 'Notification on auto renewals', 'Auto renewals (Digest)',
+        "Dear [% borrower.firstname %] [% borrower.surname %],
+        [% IF error %]
+            There were [% error %] items that were not renewed.
+        [% END %]
+        [% IF success %]
+            There were [% success %] items that were renewed.
+        [% END %]
+        [% FOREACH checkout IN checkouts %]
+            [% checkout.item.biblio.title %] : [% checkout.item.barcode %]
+            [% IF !checkout.auto_renew_error %]
+                was renewed until [% checkout.date_due | $KohaDates as_due_date => 1%]
+            [% ELSIF checkout.auto_renew_error == 'too_many' %]
+                You have reached the maximum number of checkouts possible.
+            [% ELSIF checkout.auto_renew_error == 'on_reserve' %]
+                This item is on hold for another patron.
+            [% ELSIF checkout.auto_renew_error == 'restriction' %]
+                You are currently restricted.
+            [% ELSIF checkout.auto_renew_error == 'overdue' %]
+                You have overdue items.
+            [% ELSIF checkout.auto_renew_error == 'auto_too_late' %]
+                It's too late to renew this item.
+            [% ELSIF checkout.auto_renew_error == 'auto_too_much_oweing' %]
+                Your total unpaid fines are too high.
+            [% ELSIF checkout.auto_renew_error == 'too_unseen' %]
+                This item must be renewed at the library.
+            [% END %]
+        [% END %]
+        ", 'email');
+    });
+
+    $dbh->do( q{
+        INSERT IGNORE INTO `message_attributes`
+            (`message_attribute_id`, message_name, `takes_days`)
+        VALUES (9, 'Auto_Renewals', 0)
+    });
+
+    $dbh->do( q{
+        INSERT IGNORE INTO `message_transports`
+            (`message_attribute_id`, `message_transport_type`, `is_digest`, `letter_module`, `letter_code`)
+        VALUES  (9, 'email', 0, 'circulation', 'AUTO_RENEWALS'),
+                (9, 'sms', 0, 'circulation', 'AUTO_RENEWALS'),
+                (9, 'email', 1, 'circulation', 'AUTO_RENEWALS_DGST'),
+                (9, 'sms', 1, 'circulation', 'AUTO_RENEWALS_DGST')
+    });
+
+     $dbh->do(q{
+         INSERT IGNORE INTO systempreferences (variable,value,explanation,options,type)
+         VALUES ('AutoRenewalNotices','cron','cron|preferences|never','How should Koha determine whether to end autorenewal notices','Choice')
+     });
+
+    NewVersion( $DBversion, 18532, 'Messaging preferences for auto renewals' );
+}
+
+$DBversion = '20.12.00.025';
+if( CheckVersion( $DBversion ) ) {
+
+    $dbh->do(q|
+        INSERT IGNORE INTO systempreferences (variable,value,options,explanation,type)
+        VALUES ('ChargeFinesOnClosedDays', '0', NULL, 'Charge fines on days the library is closed.', 'YesNo')
+    |);
+
+    NewVersion( $DBversion, 27835, "Add new system preference ChargeFinesOnClosedDays");
+}
+
+$DBversion = '20.12.00.026';
+if( CheckVersion( $DBversion ) ) {
+
+    $dbh->do(q{INSERT IGNORE INTO systempreferences (variable,value,options,explanation,type) VALUES ('DefaultHoldExpirationdate','0','','Automatically set default expiration date for holds','YesNo') });
+    $dbh->do(q{INSERT IGNORE INTO systempreferences (variable,value,options,explanation,type) VALUES ('DefaultHoldExpirationdatePeriod','0','','How long into the future default expiration date is set to be.','integer') });
+    $dbh->do(q{INSERT IGNORE INTO systempreferences (variable,value,options,explanation,type) VALUES ('DefaultHoldExpirationdateUnitOfTime','days','days|months|years','Which unit of time is used when setting the default expiration date. ','choice') });
+
+    NewVersion( $DBversion, 26498, "Bug 26498 - Add option to set a default expire date for holds at reservation time");
+}
+
+$DBversion = '20.12.00.027';
+if( CheckVersion( $DBversion ) ) {
+
+    $dbh->do(q{
+        UPDATE circulation_rules
+        SET
+            rule_value = CASE
+                WHEN rule_value='0' THEN 'not_allowed'
+                WHEN rule_value='1' THEN 'from_home_library'
+                WHEN rule_value='2' THEN 'from_any_library'
+                WHEN rule_value='3' THEN 'from_local_hold_group'
+            END
+        WHERE rule_name='holdallowed' AND rule_value >= 0 AND rule_value <= 3;
+    });
+
+    NewVersion( $DBversion, 27069, "Change holdallowed values from numbers to strings");
+}
+
+$DBversion = '20.12.00.028';
+if ( CheckVersion($DBversion) ) {
+
+    if ( !column_exists( 'letter', 'id' ) ) {
+        $dbh->do(q{
+            ALTER TABLE letter DROP PRIMARY KEY
+        });
+        $dbh->do(q{
+            ALTER TABLE letter ADD COLUMN `id` int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY FIRST
+        });
+        $dbh->do(q{
+            ALTER TABLE letter ADD UNIQUE KEY letter_uniq_1 (`module`,`code`,`branchcode`,`message_transport_type`,`lang`)
+        });
+    }
+
+    $dbh->do(q{
+        INSERT IGNORE INTO systempreferences (variable, value, explanation, options, type)
+        VALUES ('NoticesLog','0',NULL,'If enabled, log changes to notice templates','YesNo')
+    });
+
+    NewVersion( $DBversion, 14233, "Add id field to letter table" );
+}
+
+$DBversion = '20.12.00.029';
+if( CheckVersion( $DBversion ) ) {
+    $dbh->do("ALTER TABLE problem_reports MODIFY content TEXT NOT NULL");
+
+    NewVersion( $DBversion, 27726, "Increase field size for problem_reports.content");
+}
+
+$DBversion = '20.12.00.030';
+if( CheckVersion( $DBversion ) ) {
+    $dbh->do(q|
+        INSERT IGNORE INTO systempreferences ( `variable`, `value`, `options`, `explanation`, `type` )
+        VALUES ('LockExpiredDelay','','','Delay for locking expired patrons (empty means no locking)','Integer')
+    |);
+
+    NewVersion( $DBversion, 21549, "Add new system preference LockExpiredDelay");
+}
+
+$DBversion = '20.12.00.031';
+if( CheckVersion( $DBversion ) ) {
+    $dbh->do(q{
+        INSERT IGNORE INTO systempreferences (variable, value, explanation, options, type)
+        VALUES ('Reference_NFL_Statuses','1|2',NULL,'Contains not for loan statuses considered as available for reference','Free')
+    });
+
+    NewVersion( $DBversion, 21260, "Add new system preference Reference_NFL_Statuses");
+}
+
+$DBversion = '20.12.00.032';
+if( CheckVersion( $DBversion ) ) {
+    $dbh->do(q{
+        INSERT IGNORE INTO letter
+        (module,code,branchcode,name,is_html,title,content,message_transport_type,lang)
+        VALUES ('reserves','HOLD_REMINDER','','Waiting hold reminder',0,'You have waiting holds.','Dear [% borrower.firstname %] [% borrower.surname %],\r\n\r\nThe following holds are waiting at [% branch.branchname %]:\r\n\\r\n[% FOREACH hold IN holds %]\r\n    [% hold.biblio.title %] : waiting since [% hold.waitingdate | $KohaDates %]\r\n[% END %]','email','default')
+    });
+
+    NewVersion( $DBversion, 15986, "Add sample HOLD_REMINDER notice");
+}
+
+$DBversion = '20.12.00.033';
+if( CheckVersion( $DBversion ) ) {
+    my $debar = $dbh->selectall_arrayref(q|
+        SELECT d.borrowernumber, GROUP_CONCAT(comment SEPARATOR '\n') AS comment
+        FROM borrower_debarments d
+        LEFT JOIN borrowers b ON b.borrowernumber=d.borrowernumber
+        WHERE ( b.debarredcomment IS NULL OR b.debarredcomment = "" ) AND ( expiration > CURRENT_DATE() OR expiration IS NULL )
+        GROUP BY d.borrowernumber
+    |, { Slice => {} });
+
+
+    my $update_sth = $dbh->prepare(q|
+        UPDATE borrowers
+        SET debarredcomment=?
+        WHERE borrowernumber=?
+    |);
+    for my $d ( @$debar ) {
+        $update_sth->execute($d->{comment}, $d->{borrowernumber});
+    }
+
+    NewVersion( $DBversion, 26940, "Put in sync borrowers.debarredcomment with comments from borrower_debarments");
+}
+
+$DBversion = '20.12.00.034';
+if( CheckVersion( $DBversion ) ) {
+
+    $dbh->do(q{
+        INSERT IGNORE INTO systempreferences (variable, value, options, explanation, type)
+        VALUES ('casServerVersion', '2', '2|3', 'Version of the CAS server Koha will connect to.', 'Choice');
+    });
+
+    NewVersion( $DBversion, 20854, "Add new system preference casServerVersion");
+}
+
+$DBversion = '20.12.00.035';
+if( CheckVersion( $DBversion ) ) {
+    if( !column_exists( 'itemtypes', 'automatic_checkin' ) ) {
+        $dbh->do(q{
+            ALTER TABLE itemtypes
+                ADD COLUMN `automatic_checkin` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'If automatic checkin is enabled for items of this type' AFTER `searchcategory`
+        });
+    }
+
+    NewVersion( $DBversion, 23207, "Add automatic_checkin to itemtypes table");
+}
+
+$DBversion = '20.12.00.036';
+if( CheckVersion( $DBversion ) ) {
+    $dbh->do(q{
+        ALTER TABLE club_holds_to_patron_holds
+        MODIFY COLUMN error_code
+        ENUM ( 'damaged', 'ageRestricted', 'itemAlreadyOnHold',
+            'tooManyHoldsForThisRecord', 'tooManyReservesToday',
+            'tooManyReserves', 'notReservable', 'cannotReserveFromOtherBranches',
+            'libraryNotFound', 'libraryNotPickupLocation', 'cannotBeTransferred',
+            'noReservesAllowed'
+        )
+    });
+
+    NewVersion( $DBversion, 16787, "Add noReservesAllowed to club holds error codes");
+}
+
+$DBversion = '20.12.00.037';
+if( CheckVersion( $DBversion ) ) {
+    $dbh->do( q{
+        INSERT IGNORE INTO systempreferences (variable, value, explanation, options, type)
+        VALUES ('AcquisitionLog', '0', 'If enabled, log acquisition activity', '', 'YesNo');
+    });
+
+    NewVersion( $DBversion, 23971, "Add new system preference AcquisitionLog");
+}
+
+$DBversion = '20.12.00.038';
+if( CheckVersion( $DBversion ) ) {
+
+    # Add 'ItemLost' to reserves cancellation_reason enum
+    $dbh->do(
+        q{
+            ALTER TABLE
+                `branchtransfers`
+            MODIFY COLUMN
+                `cancellation_reason` enum(
+                    'Manual',
+                    'StockrotationAdvance',
+                    'StockrotationRepatriation',
+                    'ReturnToHome',
+                    'ReturnToHolding',
+                    'RotatingCollection',
+                    'Reserve',
+                    'LostReserve',
+                    'CancelReserve',
+                    'ItemLost'
+                )
+            AFTER `comments`
+          }
+    );
+
+    NewVersion( $DBversion, 27281, "Add 'ItemLost' to branchtransfers.cancellation_reason enum");
+}
+
+$DBversion = '20.12.00.039';
+if( CheckVersion( $DBversion ) ) {
+
+    $dbh->do(
+        q{
+            ALTER TABLE
+                `branchtransfers`
+            MODIFY COLUMN
+                `reason` enum(
+                    'Manual',
+                    'StockrotationAdvance',
+                    'StockrotationRepatriation',
+                    'ReturnToHome',
+                    'ReturnToHolding',
+                    'RotatingCollection',
+                    'Reserve',
+                    'LostReserve',
+                    'CancelReserve',
+                    'TransferCancellation'
+                )
+            AFTER `comments`
+          }
+    );
+
+    NewVersion( $DBversion, 12362, "Add 'TransferCancellation' to branchtransfers.reason enum");
+}
+
+$DBversion = '20.12.00.040';
+if( CheckVersion( $DBversion ) ) {
+    $dbh->do(
+        q{
+            INSERT IGNORE INTO account_debit_types (
+              code,
+              description,
+              can_be_invoiced,
+              can_be_sold,
+              default_amount,
+              is_system
+            )
+            VALUES
+              ('VOID', 'Credit has been voided', 0, 0, NULL, 1)
+        }
+    );
+
+    $dbh->do(q{
+        INSERT IGNORE INTO account_offset_types ( type ) VALUES ('VOID');
+    });
+
+    NewVersion( $DBversion, 27971, "Add VOID debit type code");
+}
+
+$DBversion = '20.12.00.041';
+if ( CheckVersion($DBversion) ) {
+
+    # ACCOUNT_CREDIT UPDATES
+    # backup existing notice to action_logs
+    my $credit_arr = $dbh->selectall_arrayref(q{SELECT lang FROM letter WHERE code = 'ACCOUNT_CREDIT'}, { Slice => {} });
+    my $c_sth = $dbh->prepare(q{
+      INSERT INTO action_logs ( timestamp, module, action, object, info, interface )
+      SELECT NOW(), 'NOTICES', 'UPGRADE', id, content, 'cli'
+      FROM letter
+      WHERE lang = ? AND code = 'ACCOUNT_CREDIT'
+    });
+
+    for my $c ( @{$credit_arr} ) {
+        $c_sth->execute( $c->{lang} );
+    }
+
+    # replace notice with default
+    my $c_notice = q{
+[% USE Price %]
+[% PROCESS 'accounts.inc' %]
+<table>
+[% IF ( LibraryName ) %]
+ <tr>
+    <th colspan="4" class="centerednames">
+        <h3>[% LibraryName | html %]</h3>
+    </th>
+ </tr>
+[% END %]
+ <tr>
+    <th colspan="4" class="centerednames">
+        <h2><u>Fee receipt</u></h2>
+    </th>
+ </tr>
+ <tr>
+    <th colspan="4" class="centerednames">
+        <h2>[% Branches.GetName( credit.patron.branchcode ) | html %]</h2>
+    </th>
+ </tr>
+ <tr>
+    <th colspan="4">
+        Received with thanks from  [% credit.patron.firstname | html %] [% credit.patron.surname | html %] <br />
+        Card number: [% credit.patron.cardnumber | html %]<br />
+    </th>
+ </tr>
+  <tr>
+    <th>Date</th>
+    <th>Description of charges</th>
+    <th>Note</th>
+    <th>Amount</th>
+ </tr>
+
+ <tr class="highlight">
+    <td>[% credit.date | $KohaDates %]</td>
+    <td>
+      [% PROCESS account_type_description account=credit %]
+      [%- IF credit.description %], [% credit.description | html %][% END %]
+    </td>
+    <td>[% credit.note | html %]</td>
+    <td class="credit">[% credit.amount | $Price %]</td>
+ </tr>
+
+<tfoot>
+  <tr>
+    <td colspan="3">Total outstanding dues as on date: </td>
+    [% IF ( credit.patron.account.balance >= 0 ) %]<td class="credit">[% ELSE %]<td class="debit">[% END %][% credit.patron.account.balance | $Price %]</td>
+  </tr>
+</tfoot>
+</table>
+    };
+
+    $dbh->do(q{UPDATE letter SET content = ?, is_html = 1 WHERE code = 'ACCOUNT_CREDIT'}, undef, $c_notice);
+
+    # ACCOUNT_DEBIT UPDATES
+    # backup existing notice to action_logs
+    my $debit_arr = $dbh->selectall_arrayref(
+        "SELECT lang FROM letter WHERE code = 'ACCOUNT_DEBIT'", { Slice => {} });
+    my $d_sth = $dbh->prepare(q{
+      INSERT INTO action_logs ( timestamp, module, action, object, info, interface )
+      SELECT NOW(), 'NOTICES', 'UPGRADE', id, content, 'cli'
+      FROM letter
+      WHERE lang = ? AND code = 'ACCOUNT_DEBIT'
+    });
+
+    for my $d ( @{$debit_arr} ) {
+        $d_sth->execute( $d->{lang} );
+    }
+
+    # replace notice with default
+    my $d_notice = q{
+[% USE Price %]
+[% PROCESS 'accounts.inc' %]
+<table>
+  [% IF ( LibraryName ) %]
+    <tr>
+      <th colspan="5" class="centerednames">
+        <h3>[% LibraryName | html %]</h3>
+      </th>
+    </tr>
+  [% END %]
+
+  <tr>
+    <th colspan="5" class="centerednames">
+      <h2><u>INVOICE</u></h2>
+    </th>
+  </tr>
+  <tr>
+    <th colspan="5" class="centerednames">
+      <h2>[% Branches.GetName( debit.patron.branchcode ) | html %]</h2>
+    </th>
+  </tr>
+  <tr>
+    <th colspan="5" >
+      Bill to: [% debit.patron.firstname | html %] [% debit.patron.surname | html %] <br />
+      Card number: [% debit.patron.cardnumber | html %]<br />
+    </th>
+  </tr>
+  <tr>
+    <th>Date</th>
+    <th>Description of charges</th>
+    <th>Note</th>
+    <th style="text-align:right;">Amount</th>
+    <th style="text-align:right;">Amount outstanding</th>
+  </tr>
+
+  <tr class="highlight">
+    <td>[% debit.date | $KohaDates%]</td>
+    <td>
+      [% PROCESS account_type_description account=debit %]
+      [%- IF debit.description %], [% debit.description | html %][% END %]
+    </td>
+    <td>[% debit.note | html %]</td>
+    <td class="debit">[% debit.amount | $Price %]</td>
+    <td class="debit">[% debit.amountoutstanding | $Price %]</td>
+  </tr>
+
+  [% IF ( tendered ) %]
+    <tr>
+      <td colspan="3">Amount tendered: </td>
+      <td>[% tendered | $Price %]</td>
+    </tr>
+    <tr>
+      <td colspan="3">Change given: </td>
+      <td>[% change | $Price %]</td>
+    </tr>
+  [% END %]
+
+  <tfoot>
+    <tr>
+      <td colspan="4">Total outstanding dues as on date: </td>
+      [% IF ( debit.patron.account.balance <= 0 ) %]<td class="credit">[% ELSE %]<td class="debit">[% END %][% debit.patron.account.balance | $Price %]</td>
+    </tr>
+  </tfoot>
+</table>
+    };
+    $dbh->do(q{UPDATE letter SET content = ?, is_html = 1 WHERE code = 'ACCOUNT_DEBIT'}, undef, $d_notice);
+
+    NewVersion( $DBversion, 26734, ["Update notices to use defaults", "WARNING - ACCOUNT_DEBIT and ACCOUNT_CREDIT slip templates have been replaced. Backups have been made to the action logs for your reference."] );
+}
+
+$DBversion = '20.12.00.042';
+if( CheckVersion( $DBversion ) ) {
+    unless( foreign_key_exists( 'collections_tracking', 'collectionst_ibfk_1' ) ) {
+        $dbh->do(q{
+            DELETE FROM collections_tracking WHERE colId NOT IN ( SELECT colId FROM collections )
+        });
+        $dbh->do(q{
+            ALTER TABLE collections_tracking
+            ADD CONSTRAINT `collectionst_ibfk_1` FOREIGN KEY (`colId`) REFERENCES `collections` (`colId`) ON DELETE CASCADE ON UPDATE CASCADE
+        });
+    }
+
+    NewVersion( $DBversion, 17202, "Add FK constraint for collection to collections_tracking");
+}
+
+$DBversion = '20.12.00.043';
+if( CheckVersion( $DBversion ) ) {
+    $dbh->do(q{
+        UPDATE letter SET
+        content = REPLACE(content, "The following item, [% biblio.title %], has correctly been renewed and is now due on [% checkout.date_due as_due_date => 1 %]" , "The following item, [% biblio.title %], has correctly been renewed and is now due on [% checkout.date_due | $KohaDates as_due_date => 1 %]")
+        WHERE code = 'AUTO_RENEWALS';
+    });
+
+    NewVersion( $DBversion, 28258, "Update AUTO_RENEWAL content");
+}
+
+$DBversion = '20.12.00.044';
+if( CheckVersion( $DBversion ) ) {
+    $dbh->do(q{
+        UPDATE language_subtag_registry SET description = 'Ukrainian' WHERE subtag='uk' and type='language' and description='Ukranian'
+    });
+    $dbh->do(q{
+        UPDATE language_descriptions SET description = 'Ukrainian' WHERE subtag='uk' and type='language' and lang='en' and description='Ukranian'
+    });
+
+    NewVersion( $DBversion, 28244, "Fix Ukrainian typo in English");
+}
+
+$DBversion = '20.12.00.045';
+if( CheckVersion( $DBversion ) ) {
+    $dbh->do(q{
+        INSERT IGNORE INTO systempreferences (variable, value, options, explanation, type) VALUES ('SearchLimitLibrary', 'both', 'homebranch|holdingbranch|both', "When limiting search results with a library or library group, use the item's home library, or holding library, or both.", 'Choice')
+    });
+
+    NewVersion( $DBversion, 21249, "Adding new system preference SearchLimitLibrary" );
+}
+
+$DBversion = '20.12.00.046';
+if( CheckVersion( $DBversion ) ) {
+    unless ( column_exists('message_queue', 'delivery_note') ) {
+        $dbh->do(q{
+            ALTER TABLE message_queue ADD delivery_note mediumtext AFTER content_type
+        });
+    }
+
+    NewVersion( $DBversion, 14723, "Additional delivery notes to messages" );
+}
+
+$DBversion = '20.12.00.047';
+if( CheckVersion( $DBversion ) ) {
+
+    $dbh->do(q{
+        DELETE FROM systempreferences
+        WHERE variable IN
+            ('EnablePayPalOpacPayments',
+             'PayPalChargeDescription',
+             'PayPalPwd',
+             'PayPalReturnURL',
+             'PayPalSandboxMode',
+             'PayPalSignature',
+             'PayPalUser');
+    });
+
+    NewVersion( $DBversion, 23215, "Remove core PayPal support in favor of the use of plugins" );
+}
+
+$DBversion = '20.12.00.048';
+if( CheckVersion( $DBversion ) ) {
+
+    # This DB upgrade has been commented out because it removes
+    # actively used data, the relationship columns will be added back
+
+    # if ( column_exists( 'borrowers', 'relationship' ) ) {
+    #     $dbh->do(q{
+    #         ALTER TABLE borrowers DROP COLUMN relationship
+    #     });
+    # }
+
+    # if ( column_exists( 'deletedborrowers', 'relationship' ) ) {
+    #     $dbh->do(q{
+    #         ALTER TABLE deletedborrowers DROP COLUMN relationship
+    #     });
+    # }
+
+    # if ( column_exists( 'borrower_modifications', 'relationship' ) ) {
+    #     $dbh->do(q{
+    #         ALTER TABLE borrower_modifications DROP COLUMN relationship
+    #     });
+    # }
+
+    NewVersion( $DBversion, 26995, "[SKIP] Drop column relationship from borrower tables [not executed]");
+}
+
+$DBversion = '20.12.00.049';
+if ( CheckVersion($DBversion) ) {
+    $dbh->do(q{
+        UPDATE action_logs SET module = 'CLAIMS'
+        WHERE module = 'ACQUISITIONS' AND ( action = 'SERIAL CLAIM' OR action = 'ACQUISITION CLAIM')
+    });
+
+    $dbh->do(q{
+        UPDATE systempreferences SET variable = 'ClaimsLog' WHERE variable = 'LetterLog';
+    });
+
+    NewVersion( $DBversion, 28108, "Move action logs 'SERIAL CLAIM' and 'ACQUISITION CLAIM' to a new 'CLAIMS' module" );
+}
+
+$DBversion = '20.12.00.050';
+if ( CheckVersion($DBversion) ) {
+    $dbh->do(q{
+        INSERT IGNORE INTO systempreferences (variable, value, options, explanation, type) VALUES
+        ('OpacHiddenItemsHidesRecord','1','','Hide bibliographic record when all its items are hidden because of OpacHiddenItems','YesNo')
+    });
+
+    NewVersion( $DBversion, 28108, "Add new systempreference OpacHiddenItemsHidesRecord" );
+}
+
+$DBversion = '21.05.00.000';
+if( CheckVersion( $DBversion ) ) {
+    NewVersion( $DBversion, "", "Koha 21.05.00 release" );
+}
+
+$DBversion = '21.06.00.000';
+if( CheckVersion( $DBversion ) ) {
+    NewVersion( $DBversion, "", ["🎵 Run, rabbit run. ðŸŽ¶", "Dig that hole, forget the sun,", "And when at last the work is done", "Don't sit down it's time to dig another one."] );
+}
+
+$DBversion = '21.06.00.001';
+if ( CheckVersion($DBversion) ) {
+    $dbh->do('DELETE FROM sessions');
+    $dbh->do('ALTER TABLE sessions MODIFY a_session LONGBLOB NOT NULL');
+
+    NewVersion( $DBversion, '28489', 'Modify sessions.a_session from longtext to longblob' );
+}
+
+$DBversion = '21.06.00.002';
+if( CheckVersion( $DBversion ) ) {
+    if( !column_exists( 'borrower_modifications', 'relationship' ) ) {
+      $dbh->do(q{
+          ALTER TABLE borrower_modifications ADD COLUMN `relationship` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL AFTER `borrowernotes`
+      });
+    }
+
+    if( !column_exists( 'borrowers', 'relationship' ) ) {
+      $dbh->do(q{
+          ALTER TABLE borrowers ADD COLUMN `relationship` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'used for children to include the relationship to their guarantor' AFTER `borrowernotes`
+      });
+    }
+
+    if( !column_exists( 'deletedborrowers', 'relationship' ) ) {
+      $dbh->do(q{
+          ALTER TABLE deletedborrowers ADD COLUMN `relationship` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'used for children to include the relationship to their guarantor' AFTER `borrowernotes`
+      });
+    }
+
+    NewVersion( $DBversion, 28490, "Bring back accidentally deleted relationship columns");
+}
+
+$DBversion = '21.06.00.003';
+if( CheckVersion( $DBversion ) ) {
+
+    # Add 'WrongTransfer' to branchtransfers cancellation_reason enum
+    $dbh->do(
+        q{
+            ALTER TABLE
+                `branchtransfers`
+            MODIFY COLUMN
+                `cancellation_reason` enum(
+                    'Manual',
+                    'StockrotationAdvance',
+                    'StockrotationRepatriation',
+                    'ReturnToHome',
+                    'ReturnToHolding',
+                    'RotatingCollection',
+                    'Reserve',
+                    'LostReserve',
+                    'CancelReserve',
+                    'ItemLost',
+                    'WrongTransfer'
+                )
+            AFTER `comments`
+          }
+    );
+
+    NewVersion( $DBversion, 24434, "Add 'WrongTransfer' to branchtransfers.cancellation_reason enum");
+}
+
+$DBversion = '21.06.00.004';
+if ( CheckVersion($DBversion) ) {
+
+    $dbh->do(q{
+        INSERT IGNORE permissions (module_bit, code, description)
+        VALUES
+        (4, 'delete_borrowers', 'Delete borrowers')
+    });
+
+    $dbh->do(q{
+        INSERT IGNORE INTO user_permissions (borrowernumber, module_bit, code)
+        SELECT borrowernumber, 4, 'delete_borrowers' FROM user_permissions WHERE code = 'edit_borrowers'
+    });
+
+    NewVersion( $DBversion, 15788, "Split edit_borrowers permission" );
+}
+
+$DBversion = '21.06.00.005';
+if( CheckVersion( $DBversion ) ) {
+    $dbh->do( q{
+        INSERT IGNORE INTO systempreferences (variable, value, explanation, options, type)
+        VALUES ('NewsLog', '0', 'If enabled, log OPAC News changes', '', 'YesNo')
+    });
+
+    NewVersion( $DBversion, 26205, "Add new system preference NewsLog to log news changes");
+}
+
+# SEE bug 13068
+# if there is anything in the atomicupdate, read and execute it.
+my $update_dir = C4::Context->config('intranetdir') . '/installer/data/mysql/atomicupdate/';
+opendir( my $dirh, $update_dir );
+foreach my $file ( sort readdir $dirh ) {
+    next if $file !~ /\.(sql|perl)$/;  #skip other files
+    next if $file eq 'skeleton.perl'; # skip the skeleton file
+    print "DEV atomic update: $file\n";
+    if ( $file =~ /\.sql$/ ) {
+        my $installer = C4::Installer->new();
+        my $rv = $installer->load_sql( $update_dir . $file ) ? 0 : 1;
+    } elsif ( $file =~ /\.perl$/ ) {
+        my $code = read_file( $update_dir . $file );
+        eval $code; ## no critic (StringyEval)
+        say "Atomic update generated errors: $@" if $@;
+    }
+}
+
+=head1 FUNCTIONS
+
+=head2 DropAllForeignKeys($table)
+
+Drop all foreign keys of the table $table
+
+=cut
+
+sub DropAllForeignKeys {
+    my ($table) = @_;
+    # get the table description
+    my $sth = $dbh->prepare("SHOW CREATE TABLE $table");
+    $sth->execute;
+    my $vsc_structure = $sth->fetchrow;
+    # split on CONSTRAINT keyword
+    my @fks = split /CONSTRAINT /,$vsc_structure;
+    # parse each entry
+    foreach (@fks) {
+        # isolate what is before FOREIGN KEY, if there is something, it's a foreign key to drop
+        $_ = /(.*) FOREIGN KEY.*/;
+        my $id = $1;
+        if ($id) {
+            # we have found 1 foreign, drop it
+            $dbh->do("ALTER TABLE $table DROP FOREIGN KEY $id");
+            $id="";
+        }
+    }
+}
+
+
+=head2 TransformToNum
+
+Transform the Koha version from a 4 parts string
+to a number, with just 1 .
+
+=cut
+
+sub TransformToNum {
+    my $version = shift;
+    # remove the 3 last . to have a Perl number
+    $version =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/;
+    # three X's at the end indicate that you are testing patch with dbrev
+    # change it into 999
+    # prevents error on a < comparison between strings (should be: lt)
+    $version =~ s/XXX$/999/;
+    return $version;
+}
+
+=head2 SetVersion
+
+set the DBversion in the systempreferences
+
+=cut
+
+sub SetVersion {
+    return if $_[0]=~ /XXX$/;
+      #you are testing a patch with a db revision; do not change version
+    my $kohaversion = TransformToNum($_[0]);
+    if (C4::Context->preference('Version')) {
+      my $finish=$dbh->prepare("UPDATE systempreferences SET value=? WHERE variable='Version'");
+      $finish->execute($kohaversion);
+    } else {
+      my $finish=$dbh->prepare("INSERT into systempreferences (variable,value,explanation) values ('Version',?,'The Koha database version. WARNING: Do not change this value manually, it is maintained by the webinstaller')");
+      $finish->execute($kohaversion);
+    }
+    C4::Context::clear_syspref_cache(); # invalidate cached preferences
+}
+
+sub NewVersion {
+    my ( $DBversion, $bug_number, $descriptions ) = @_;
+
+    SetVersion($DBversion);
+
+    unless ( ref($descriptions) ) {
+        $descriptions = [ $descriptions ];
+    }
+    my $first = 1;
+    my $time = POSIX::strftime("%H:%M:%S",localtime);
+    for my $description ( @$descriptions ) {
+        if ( @$descriptions > 1 ) {
+            if ( $first ) {
+                unless ( $bug_number ) {
+                    say sprintf "Upgrade to %s done [%s]: %s", $DBversion, $time, $description;
+                } else {
+                    say sprintf "Upgrade to %s done [%s]: Bug %5s - %s", $DBversion, $time, $bug_number, $description;
+                }
+            } else {
+                say sprintf "\t\t\t\t\t\t   - %s", $description;
+            }
+        } else {
+            unless ( $bug_number ) {
+                say sprintf "Upgrade to %s done [%s]: %s", $DBversion, $time, $description;
+            } else {
+                say sprintf "Upgrade to %s done [%s]: Bug %5s - %s", $DBversion, $time, $bug_number, $description;
+            }
+        }
+        $first = 0;
+    }
+}
+
+=head2 CheckVersion
+
+Check whether a given update should be run when passed the proposed version
+number. The update will always be run if the proposed version is greater
+than the current database version and less than or equal to the version in
+kohaversion.pl. The update is also run if the version contains XXX, though
+this behavior will be changed following the adoption of non-linear updates
+as implemented in bug 7167.
+
+=cut
+
+sub CheckVersion {
+    my ($proposed_version) = @_;
+    my $version_number = TransformToNum($proposed_version);
+
+    # The following line should be deleted when bug 7167 is pushed
+    return 1 if ( $proposed_version =~ m/XXX/ );
+
+    if ( C4::Context->preference("Version") < $version_number
+        && $version_number <= TransformToNum( $Koha::VERSION ) )
+    {
+        return 1;
+    }
+    else {
+        return 0;
+    }
+}
+
+sub sanitize_zero_date {
+    my ( $table_name, $column_name ) = @_;
+
+    my (undef, $datatype) = $dbh->selectrow_array(qq|
+        SHOW COLUMNS FROM $table_name WHERE Field = ?|, undef, $column_name);
+
+    if ( $datatype eq 'date' ) {
+        $dbh->do(qq|
+            UPDATE $table_name
+            SET $column_name = NULL
+            WHERE CAST($column_name AS CHAR(10)) = '0000-00-00';
+        |);
+    } else {
+        $dbh->do(qq|
+            UPDATE $table_name
+            SET $column_name = NULL
+            WHERE CAST($column_name AS CHAR(19)) = '0000-00-00 00:00:00';
+        |);
     }
 }