Bug 42221: Fix EAN-13 autoBarcode not incrementing

The EAN-13 barcode query used max(abs(barcode)) which allowed non-EAN-13
barcodes (e.g. 14-digit KTD sample barcodes) to dominate the result.
The saved 13-digit EAN-13 barcodes were always smaller, so $nextnum
never advanced and the same barcode was generated on every page load.

Fix by extracting EAN-13 logic into C4::Barcodes::ValueBuilder::EAN13
(consistent with other barcode type packages) and scoping the query to
WHERE length(barcode) = 13 so only valid-length barcodes contribute to
the max. Both barcode.pl and Koha::REST::V1::Biblios now call the shared
function, eliminating the duplicated inline code.

Tests: adds EAN-13 test cases to t/db_dependent/Barcodes_ValueBuilder.t
Signed-off-by: Caroline Cyr La Rose <caroline.cyr-la-rose@inlibro.com>
Signed-off-by: Andrew Fuerste Henry <andrew@bywatersolutions.com>
Signed-off-by: Lucas Gass <lucas@bywatersolutions.com>
This commit is contained in:
Martin Renvoize 2026-04-02 13:43:58 +01:00 committed by Lucas Gass
parent 757526cbde
commit fc6e5839d4
No known key found for this signature in database
GPG Key ID: 60B447398B0D36CA
4 changed files with 53 additions and 42 deletions

View File

@ -92,6 +92,33 @@ sub get_barcode {
return $nextnum;
}
package C4::Barcodes::ValueBuilder::EAN13;
use Modern::Perl;
use C4::Context;
use Algorithm::CheckDigits qw( CheckDigits );
sub get_barcode {
my ($args) = @_;
# Filter to 13-digit barcodes only so non-EAN-13 items (e.g. 14-digit sample
# barcodes) do not dominate max() and prevent the sequence from advancing.
my $query = "SELECT max(cast(barcode as unsigned)) FROM items WHERE length(barcode) = 13";
my $sth = C4::Context->dbh->prepare($query);
$sth->execute();
my ($nextnum) = $sth->fetchrow_array;
my $ean = CheckDigits('ean');
if ( $nextnum && $ean->is_valid($nextnum) ) {
my $next = $ean->basenumber($nextnum) + 1;
$nextnum = $ean->complete($next);
$nextnum = '0' x ( 13 - length($nextnum) ) . $nextnum; # pad zeros
} else {
warn "ERROR: invalid EAN-13 " . ( $nextnum // '' ) . ", using increment";
$nextnum = ( $nextnum // 0 ) + 1;
}
return $nextnum;
}
1;
=head1 Barcodes::ValueBuilder

View File

@ -332,27 +332,7 @@ sub add_item {
($barcode) = C4::Barcodes::ValueBuilder::hbyymmincr::get_barcode( { year => $year, mon => $month } );
$barcode = $homebranch . $barcode;
} elsif ( $autoBarcode eq 'EAN13' ) {
# not the best, two catalogers could add the same
# barcode easily this way :/
my $query = "select max(abs(barcode)) from items";
my $dbh = C4::Context->dbh;
my $sth = $dbh->prepare($query);
$sth->execute();
my $nextnum;
while ( my ($last) = $sth->fetchrow_array ) {
$nextnum = $last;
}
my $ean = CheckDigits('ean');
if ( $ean->is_valid($nextnum) ) {
my $next = $ean->basenumber($nextnum) + 1;
$nextnum = $ean->complete($next);
$nextnum = '0' x ( 13 - length($nextnum) ) . $nextnum; # pad zeros
} else {
warn "ERROR: invalid EAN-13 $nextnum, using increment";
$nextnum++;
}
$barcode = $nextnum;
($barcode) = C4::Barcodes::ValueBuilder::EAN13::get_barcode();
} else {
warn "ERROR: unknown autoBarcode: $autoBarcode";
}

View File

@ -27,8 +27,6 @@ use C4::Barcodes::ValueBuilder;
use C4::Biblio qw( GetMarcFromKohaField );
use Koha::DateUtils qw( dt_from_string );
use Algorithm::CheckDigits qw( CheckDigits );
use CGI qw ( -utf8 );
use C4::Auth qw( check_cookie_auth );
my $input = CGI->new;
@ -64,24 +62,7 @@ my $builder = sub {
{ # 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 'EAN13' ) {
# not the best, two catalogers could add the same barcode easily this way :/
my $query = "select max(abs(barcode)) from items";
my $dbh = $params->{dbh};
my $sth = $dbh->prepare($query);
$sth->execute();
while ( my ($last) = $sth->fetchrow_array ) {
$nextnum = $last;
}
my $ean = CheckDigits('ean');
if ( $ean->is_valid($nextnum) ) {
my $next = $ean->basenumber($nextnum) + 1;
$nextnum = $ean->complete($next);
$nextnum = '0' x ( 13 - length($nextnum) ) . $nextnum; # pad zeros
} else {
warn "ERROR: invalid EAN-13 $nextnum, using increment";
$nextnum++;
}
($nextnum) = C4::Barcodes::ValueBuilder::EAN13::get_barcode( \%args );
} else {
warn "ERROR: unknown autoBarcode: $autoBarcodeType";
}

View File

@ -17,7 +17,8 @@
use Modern::Perl;
use Test::NoWarnings;
use Test::More tests => 9;
use Test::More tests => 14;
use Test::Warn;
use Test::MockModule;
use t::lib::TestBuilder;
@ -63,4 +64,26 @@ my $item_5 = $builder->build_sample_item( { barcode => '978e0143019375' } );
is( $nextnum, '979', 'incremental barcode' );
is( $scr, undef, 'incremental javascript' );
# EAN-13 tests
$dbh->do(q|DELETE FROM items|);
$builder->build_sample_item( { barcode => '3999900001927' } ); # valid 13-digit EAN-13
( $nextnum, $scr ) = C4::Barcodes::ValueBuilder::EAN13::get_barcode( \%args );
is( $nextnum, '3999900001934', 'EAN-13 increments correctly from valid EAN-13' );
is( $scr, undef, 'EAN-13 javascript' );
$dbh->do(q|DELETE FROM items|);
$builder->build_sample_item( { barcode => '39999000019193' } ); # 14-digit, should be ignored
warning_like(
sub { ( $nextnum, $scr ) = C4::Barcodes::ValueBuilder::EAN13::get_barcode( \%args ) },
qr/ERROR: invalid EAN-13/,
'EAN-13 warns when no valid 13-digit barcode found'
);
is( $nextnum, 1, 'EAN-13 not confused by 14-digit barcode (falls back to increment)' );
$dbh->do(q|DELETE FROM items|);
$builder->build_sample_item( { barcode => '39999000019193' } ); # 14-digit
$builder->build_sample_item( { barcode => '3999900001927' } ); # 13-digit EAN-13
( $nextnum, $scr ) = C4::Barcodes::ValueBuilder::EAN13::get_barcode( \%args );
is( $nextnum, '3999900001934', 'EAN-13 picks max among 13-digit barcodes, ignores 14-digit' );
$schema->storage->txn_rollback;