From 5037c9ab23047c797def33018d86a3dc260ab1a9 Mon Sep 17 00:00:00 2001 From: Emmi Takkinen Date: Tue, 22 Mar 2022 13:17:19 +0200 Subject: [PATCH] Bug 30328: Add ability to generate barcode with branch specific prefix We should add more flexibility to generating barcodes by letting libraries define branch specific prefix for their barcodes. This patch adds new option "preyyyymmincr" in systempreference "autoBarcode" and new systempreference "BarcodePrefix" to define branch specific prefix Test plan for adding item: 1. Apply patch and update database 2. Set syspref "autoBarcode" as 3. Add some values to syspref "Barcodeprefix" e.g.: Default: DEF CPL: CPL FFL: FFL 4. Now change your library to CPL (if needed) and add an item 5. Click barcode input field => barcode should be CPL00001 6. Add another item and click barcode field => barcode should be CPL00002 7. Change your library to FFL and add an item => barcode should now be FFL00001 8. Change your library to e.g. FPL (or another as long as it doesn't have prefix value in "Barcodeprefix"), add an item and click barcode field => barcode should now be DEF00001 9. Try to add multiple items => barcodes incremental value should increase in order (no skipping values, no errors) Test plan for adding multiple aqcuisitions: 1. Set syspref "AcqCreateItem" as "placing order". 2. Create an order and add multiple items to it. 3. Save the order. 4. Navigate to that biblios detail page to which you added items. => ordered items barcodes incremental value should increase in order (no skipping values, no errors) Also prove t/db_dependent/Barcodes_ValueBuilder.t and t/Barcodes_preyyyymmincr.t Sponsored-by: Koha-Suomi Oy --- C4/Barcodes.pm | 12 +- C4/Barcodes/ValueBuilder.pm | 54 +++++++++ C4/Barcodes/preyyyymmincr.pm | 105 ++++++++++++++++++ cataloguing/value_builder/barcode.pl | 4 + cataloguing/value_builder/barcode_manual.pl | 4 + .../data/mysql/atomicupdate/bug_30328.pl | 15 +++ installer/data/mysql/mandatory/sysprefs.sql | 3 +- .../admin/preferences/cataloguing.pref | 10 ++ t/Barcodes_preyyyymmincr.t | 10 ++ t/db_dependent/Barcodes_ValueBuilder.t | 30 ++++- 10 files changed, 240 insertions(+), 7 deletions(-) create mode 100644 C4/Barcodes/preyyyymmincr.pm create mode 100755 installer/data/mysql/atomicupdate/bug_30328.pl create mode 100755 t/Barcodes_preyyyymmincr.t diff --git a/C4/Barcodes.pm b/C4/Barcodes.pm index 957d60f283..8342ae1c68 100644 --- a/C4/Barcodes.pm +++ b/C4/Barcodes.pm @@ -27,6 +27,7 @@ use C4::Barcodes::hbyymmincr; use C4::Barcodes::annual; use C4::Barcodes::incremental; use C4::Barcodes::EAN13; +use C4::Barcodes::preyyyymmincr; use vars qw($max $prefformat); @@ -154,11 +155,12 @@ sub default_self { } our $types = { - annual => sub {C4::Barcodes::annual->new_object(@_); }, - incremental => sub {C4::Barcodes::incremental->new_object(@_);}, - hbyymmincr => sub {C4::Barcodes::hbyymmincr->new_object(@_); }, - OFF => sub {C4::Barcodes::OFF->new_object(@_); }, - EAN13 => sub {C4::Barcodes::EAN13->new_object(@_); }, + annual => sub {C4::Barcodes::annual->new_object(@_); }, + incremental => sub {C4::Barcodes::incremental->new_object(@_); }, + hbyymmincr => sub {C4::Barcodes::hbyymmincr->new_object(@_); }, + OFF => sub {C4::Barcodes::OFF->new_object(@_); }, + EAN13 => sub {C4::Barcodes::EAN13->new_object(@_); }, + preyyyymmincr => sub {C4::Barcodes::preyyyymmincr->new_object(@_);}, }; sub new { diff --git a/C4/Barcodes/ValueBuilder.pm b/C4/Barcodes/ValueBuilder.pm index 76464ccd4b..abfdaf8bdb 100644 --- a/C4/Barcodes/ValueBuilder.pm +++ b/C4/Barcodes/ValueBuilder.pm @@ -89,6 +89,60 @@ sub get_barcode { return $nextnum; } +package C4::Barcodes::ValueBuilder::preyyyymmincr; +use C4::Context; +use YAML::XS; +my $DEBUG = 0; + +sub get_barcode { + my ($args) = @_; + my $nextnum; + my $barcode; + my $branchcode = $args->{branchcode}; + my $query; + my $sth; + + # Getting the barcodePrefixes + my $branchPrefixes = C4::Context->preference("BarcodePrefix"); + my $yaml = YAML::XS::Load( + Encode::encode( + 'UTF-8', + $branchPrefixes, + Encode::FB_CROAK + ) + ); + + my $prefix = $yaml->{$branchcode} || $yaml->{'Default'}; + + $query = "SELECT MAX(CAST(SUBSTRING(barcode,-4) AS signed)) from items where barcode REGEXP ?"; + $sth=C4::Context->dbh->prepare($query); + $sth->execute("^$prefix$args->{year}$args->{mon}"); + + while (my ($count)= $sth->fetchrow_array) { + $nextnum = $count if $count; + $nextnum = 0 if $nextnum && $nextnum == 9999; + } + + $nextnum++; + $nextnum = sprintf("%0*d", "5",$nextnum); + + $barcode = $prefix; + $barcode .= $args->{year}.$args->{mon}.$nextnum; + + my $scr = qq~ + let elt = \$("#"+id); + let branchcode = elt.parents('fieldset.rows:first') + .find('input[name="kohafield"][value="items.homebranch"]') + .siblings("select") + .val(); + if ( \$(elt).val() == '' ) { + \$(elt).val('$barcode'); + } + ~; + + return $barcode, $scr; +} + 1; diff --git a/C4/Barcodes/preyyyymmincr.pm b/C4/Barcodes/preyyyymmincr.pm new file mode 100644 index 0000000000..41fcec2378 --- /dev/null +++ b/C4/Barcodes/preyyyymmincr.pm @@ -0,0 +1,105 @@ +package C4::Barcodes::preyyyymmincr; + +# Copyright 2022 Koha Development team +# +# 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 3 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, see . + +use strict; +use warnings; + +use Carp qw( carp ); + +use C4::Context; + +use Koha::DateUtils qw( dt_from_string output_pref ); + +use vars qw(@ISA); + +BEGIN { + @ISA = qw(C4::Barcodes); +} + +sub new_object { + my $class = shift; + my $type = ref($class) || $class; + my $self = $type->default_self('preyyyymmincr'); + + my $branchcode = C4::Context->userenv->{'branch'}; + my $branchPrefixes = C4::Context->preference("BarcodePrefix"); + my $yaml = YAML::XS::Load( + Encode::encode( + 'UTF-8', + $branchPrefixes, + Encode::FB_CROAK + ) + ); + my $prefix = $yaml->{$branchcode} || $yaml->{'Default'}; + + $self->{prefix} = $prefix; + $self->{datetime} = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 }); + + return bless $self, $type; +} + +sub initial { + my $self = shift; + + my $prefix = $self->{prefix}; + my ($year, $month, $day) = split('-', $self->{datetime}); + + return $prefix.$year.$month.'00001'; +} + +sub db_max { + my $self = shift; + + my $prefix = $self->{prefix}; + my ($year, $month, $day) = split('-', $self->{datetime}); + + my $query = "SELECT MAX(CAST(SUBSTRING(barcode,-4) AS signed)) from items where barcode REGEXP ?"; + my $sth=C4::Context->dbh->prepare($query); + $sth->execute("^$prefix$year$month"); + + my $nextnum; + while (my ($count)= $sth->fetchrow_array) { + $nextnum = $count if $count; + $nextnum = 0 if $nextnum && $nextnum == 9999; + } + + $nextnum = sprintf("%0*d", "5",$nextnum); + + return $nextnum; +} + +sub parse { + my $self = shift; + + my $prefix = $self->{prefix}; + my ($year, $month, $day) = split('-', $self->{datetime}); + + my $head = $prefix.$year.$month; + my $incr = (@_) ? shift : $self->value; + my $barcode = $head.$incr; + unless ($incr){ + carp "Barcode '$barcode' has no incrementing part!"; + return ($barcode,undef,undef); + } + + return ($head, $incr, ''); +} + +1; +__END__ \ No newline at end of file diff --git a/cataloguing/value_builder/barcode.pl b/cataloguing/value_builder/barcode.pl index 8fcd45beae..5f8756d476 100755 --- a/cataloguing/value_builder/barcode.pl +++ b/cataloguing/value_builder/barcode.pl @@ -37,6 +37,7 @@ my $builder = sub { # find today's date ($args{year}, $args{mon}, $args{day}) = split('-', output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 })); ($args{tag},$args{subfield}) = GetMarcFromKohaField( "items.barcode" ); + ($args{branchcode}) = C4::Context->userenv->{'branch'}; my $nextnum; my $scr; @@ -73,6 +74,9 @@ my $builder = sub { $nextnum++; } } + elsif ($autoBarcodeType eq 'preyyyymmincr') { # Generates a barcode where pre = branch specific prefix set on systempreference BarcodePrefix, yyyymm = year/month catalogued, incr = incremental number + ($nextnum, $scr) = C4::Barcodes::ValueBuilder::preyyyymmincr::get_barcode(\%args); + } else { warn "ERROR: unknown autoBarcode: $autoBarcodeType"; } diff --git a/cataloguing/value_builder/barcode_manual.pl b/cataloguing/value_builder/barcode_manual.pl index d2b7f7a414..d667fc2573 100755 --- a/cataloguing/value_builder/barcode_manual.pl +++ b/cataloguing/value_builder/barcode_manual.pl @@ -38,6 +38,7 @@ my $builder = sub { # find today's date ($args{year}, $args{mon}, $args{day}) = split('-', output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 })); ($args{tag},$args{subfield}) = GetMarcFromKohaField( "items.barcode" ); + ($args{branchcode}) = C4::Context->userenv->{'branch'}; my $nextnum; my $scr; @@ -55,6 +56,9 @@ my $builder = sub { elsif ($autoBarcodeType eq 'hbyymmincr') { # Generates a barcode where hb = home branch Code, yymm = year/month catalogued, incr = incremental number, reset yearly -fbcit ($nextnum, $scr) = C4::Barcodes::ValueBuilder::hbyymmincr::get_barcode(\%args); } + elsif ($autoBarcodeType eq 'preyyyymmincr') { # Generates a barcode where pre = branch specific prefix set on systempreference BarcodePrefix, yyyymm = year/month catalogued, incr = incremental number + ($nextnum, $scr) = C4::Barcodes::ValueBuilder::preyyyymmincr::get_barcode(\%args); + } # default js body (if not filled by hbyymmincr) $scr or $scr = < "30328", + description => "Add option to create barcode with branch specific prefix", + up => sub { + my ($args) = @_; + my ($dbh, $out) = @$args{qw(dbh out)}; + # Do you stuffs here + $dbh->do(q{ UPDATE IGNORE systempreferences SET options = 'incremental|annual|hbyymmincr|EAN13|preyyyymmincr|OFF' , explanation = 'Used to autogenerate a barcode: incremental will be of the form 1, 2, 3; annual of the form 2007-0001, 2007-0002; hbyymmincr of the form HB08010001 where HB=Home Branch; preyyyymmincr of the form PRE2021030001 where PRE = branch specific prefix set on systempreference BarcodePrefix' WHERE variable = 'autoBarcode'}); + $dbh->do(q{ INSERT IGNORE INTO systempreferences ( variable, value, options, explanation, type ) VALUES ('BarcodePrefix','','','Defines the barcode prefixes when the autoBarcode value is set as preyyyymmincr','Textarea')}); + # Print useful stuff here + say $out "Update is going well so far"; + }, +}; diff --git a/installer/data/mysql/mandatory/sysprefs.sql b/installer/data/mysql/mandatory/sysprefs.sql index c6d0fd49e9..989baa2d2c 100644 --- a/installer/data/mysql/mandatory/sysprefs.sql +++ b/installer/data/mysql/mandatory/sysprefs.sql @@ -72,7 +72,7 @@ INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` ('AuthoritySeparator','--','10','Used to separate a list of authorities in a display. Usually --','free'), ('AuthorityXSLTResultsDisplay','','','Enable XSL stylesheet control over authority results page display on intranet','Free'), ('AuthSuccessLog','0',NULL,'If enabled, log successful authentications','YesNo'), -('autoBarcode','OFF','incremental|annual|hbyymmincr|EAN13|OFF','Used to autogenerate a barcode: incremental will be of the form 1, 2, 3; annual of the form 2007-0001, 2007-0002; hbyymmincr of the form HB08010001 where HB=Home Branch','Choice'), +('autoBarcode','OFF','incremental|annual|hbyymmincr|EAN13|preyyyymmincr|OFF','Used to autogenerate a barcode: incremental will be of the form 1, 2, 3; annual of the form 2007-0001, 2007-0002; hbyymmincr of the form HB08010001 where HB=Home Branch; preyyyymmincr of the form PRE2021030001 where PRE = branch specific prefix set on systempreference BarcodePrefix','Choice'), ('AutoCreateAuthorities','0',NULL,'Automatically create authorities that do not exist when cataloging records.','YesNo'), ('AutoCreditNumber', '', '', 'Automatically generate a number for account credits', 'Choice'), ('AutoEmailNewUser','0',NULL,'Send an email to newly created patrons.','YesNo'), @@ -96,6 +96,7 @@ INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` ('BakerTaylorEnabled','0','','Enable or disable all Baker & Taylor features.','YesNo'), ('BakerTaylorPassword','','','Baker & Taylor Password for Content Cafe (external content)','Free'), ('BakerTaylorUsername','','','Baker & Taylor Username for Content Cafe (external content)','Free'), +('BarcodePrefix','','','Defines the barcode prefixes when the autoBarcode value is set as preyyyymmincr','Textarea'), ('BarcodeSeparators','\\s\\r\\n','','Splitting characters for barcodes','Free'), ('BasketConfirmations','1','always ask for confirmation.|do not ask for confirmation.','When closing or reopening a basket,','Choice'), ('BatchCheckouts','0','','Enable or disable batch checkouts','YesNo'), 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 86840eff5a..c69da228f4 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 @@ -123,7 +123,9 @@ Cataloging: annual: generated in the form -0001, -0002. hbyymmincr: generated in the form yymm0001. EAN13: incremental EAN-13 barcodes. + preyyyymmincr: generated in the form yyyymm00001. "OFF": not generated automatically. + - Define the behavior of the <​prefix>yyyymm00001 option using BarcodePrefix. - - When a new item is added, - pref: PrefillItem @@ -165,6 +167,14 @@ Cataloging: - and record's last modifier name in MARC subfield - pref: MarcFieldForModifierName - ".
NOTE: Use a dollar sign between field and subfield like 123$a." + - + - "Define branch specific barcode prefixes in a YAML format." + - "This defines the behavior of the <​prefix>yyyymm00001 option of autoBarcode." + - "Barcodes generated look like: DEF20220400001" + - pref: BarcodePrefix + type: textarea + syntax: text/x-yaml + class: code Display: - - 'Separate main entry and subdivisions with ' diff --git a/t/Barcodes_preyyyymmincr.t b/t/Barcodes_preyyyymmincr.t new file mode 100755 index 0000000000..c3ab5d8e9a --- /dev/null +++ b/t/Barcodes_preyyyymmincr.t @@ -0,0 +1,10 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use Test::More tests => 1; + +BEGIN { + use_ok('C4::Barcodes::preyyyymmincr'); +} \ No newline at end of file diff --git a/t/db_dependent/Barcodes_ValueBuilder.t b/t/db_dependent/Barcodes_ValueBuilder.t index 84fcce1983..1879e341f8 100755 --- a/t/db_dependent/Barcodes_ValueBuilder.t +++ b/t/db_dependent/Barcodes_ValueBuilder.t @@ -16,12 +16,14 @@ use Modern::Perl; -use Test::More tests => 9; +use Test::More tests => 12; use Test::MockModule; use t::lib::TestBuilder; use Koha::Database; +use t::lib::Mocks; + BEGIN { use_ok('C4::Barcodes::ValueBuilder', qw( get_barcode )); }; @@ -85,4 +87,30 @@ my $item_5 = $builder->build_sample_item( is($nextnum, '979', 'incremental barcode'); is($scr, undef, 'incremental javascript'); +$dbh->do(q|DELETE FROM items|); +my $library_1 = $builder->build_object( { class => 'Koha::Libraries' } ); + +my $prefix_yaml = 'Default: DEF +'.$library_1->branchcode.': TEST'; +t::lib::Mocks::mock_preference( 'BarcodePrefix', $prefix_yaml ); + +my $item_6 = $builder->build_sample_item( + { + barcode => 'TEST20120700001', + homebranch => $library_1->branchcode + } +); + +($args{branchcode}) = $library_1->branchcode; +($nextnum, $scr) = C4::Barcodes::ValueBuilder::preyyyymmincr::get_barcode(\%args); +is($nextnum, 'TEST20120700002', 'preyyyymmincr barcode test branch specific prefix'); +ok(length($scr) > 0, 'preyyyymmincr javascript'); + +$dbh->do(q|DELETE FROM items|); +my $library_2 = $builder->build_object( { class => 'Koha::Libraries' } ); + +($args{branchcode}) = $library_2->branchcode; +($nextnum, $scr) = C4::Barcodes::ValueBuilder::preyyyymmincr::get_barcode(\%args); +is($nextnum, 'DEF20120700001', 'preyyyymmincr barcode test default prefix'); + $schema->storage->txn_rollback; -- 2.25.1