|
Lines 16-22
Link Here
|
| 16 |
# along with Koha; if not, see <https://www.gnu.org/licenses>. |
16 |
# along with Koha; if not, see <https://www.gnu.org/licenses>. |
| 17 |
|
17 |
|
| 18 |
use Modern::Perl; |
18 |
use Modern::Perl; |
| 19 |
|
|
|
| 20 |
use Koha::Script; |
19 |
use Koha::Script; |
| 21 |
use Koha::AuthorisedValues; |
20 |
use Koha::AuthorisedValues; |
| 22 |
use Koha::Authorities; |
21 |
use Koha::Authorities; |
|
Lines 27-52
use Koha::Items;
Link Here
|
| 27 |
use Koha::ItemTypes; |
26 |
use Koha::ItemTypes; |
| 28 |
use Koha::Patrons; |
27 |
use Koha::Patrons; |
| 29 |
use C4::Biblio qw( GetMarcFromKohaField ); |
28 |
use C4::Biblio qw( GetMarcFromKohaField ); |
|
|
29 |
use Getopt::Long; |
| 30 |
use Pod::Usage; |
| 31 |
|
| 32 |
our %options = ( |
| 33 |
'check-branch' => 0, |
| 34 |
'check-auth' => 0, |
| 35 |
'check-status' => 0, |
| 36 |
'check-framework' => 0, |
| 37 |
'check-title' => 0, |
| 38 |
'check-age' => 0, |
| 39 |
'skip-branch' => 1, |
| 40 |
'skip-auth' => 1, |
| 41 |
'skip-status' => 1, |
| 42 |
'skip-framework' => 1, |
| 43 |
'skip-title' => 1, |
| 44 |
'skip-age' => 1, |
| 45 |
'help' => 0, |
| 46 |
); |
| 47 |
|
| 48 |
# Parse command line options |
| 49 |
GetOptions( |
| 50 |
'check-branch' => sub { $options{'check-branch'} = 1 }, |
| 51 |
'check-auth' => sub { $options{'check-auth'} = 1 }, |
| 52 |
'check-status' => sub { $options{'check-status'} = 1 }, |
| 53 |
'check-framework' => sub { $options{'check-framework'} = 1 }, |
| 54 |
'check-title' => sub { $options{'check-title'} = 1 }, |
| 55 |
'check-age' => sub { $options{'check-age'} = 1 }, |
| 56 |
'skip-branch' => sub { $options{'skip-branch'} = 0 }, |
| 57 |
'skip-auth' => sub { $options{'skip-auth'} = 0 }, |
| 58 |
'skip-status' => sub { $options{'skip-status'} = 0 }, |
| 59 |
'skip-framework' => sub { $options{'skip-framework'} = 0 }, |
| 60 |
'skip-title' => sub { $options{'skip-title'} = 0 }, |
| 61 |
'skip-age' => sub { $options{'skip-age'} = 0 }, |
| 62 |
'help' => sub { $options{'help'} = 1; }, |
| 63 |
) or pod2usage(2); # Print usage if invalid options are provided |
| 64 |
|
| 65 |
# Call handle_invalid_options subroutine if invalid options are provided |
| 66 |
Getopt::Long::Configure('pass_through'); |
| 67 |
GetOptions( |
| 68 |
'<>' => sub { |
| 69 |
my ($opt_name) = @_; |
| 70 |
handle_invalid_options($opt_name); |
| 71 |
} |
| 72 |
); |
| 73 |
|
| 74 |
# Print usage and exit if --help flag is provided |
| 75 |
pod2usage(1) if $options{'help'}; |
| 76 |
|
| 77 |
# Run tests based on options |
| 78 |
set_skip_options(); |
| 79 |
CheckItemsBranch() if $options{'check-branch'} || $options{'skip-branch'}; |
| 80 |
CheckItemsAuthHeader() if $options{'check-auth'} || $options{'skip-auth'}; |
| 81 |
CheckItemsStatus() if $options{'check-status'} || $options{'skip-status'}; |
| 82 |
CheckItemsFramework() if $options{'check-framework'} || $options{'skip-framework'}; |
| 83 |
CheckItemsTitle() if $options{'check-title'} || $options{'skip-title'}; |
| 84 |
CheckAgeForCategory() if $options{'check-age'} || $options{'skip-age'}; |
| 85 |
|
| 86 |
# Handle invalid options |
| 87 |
sub handle_invalid_options { |
| 88 |
my ($opt_name) = @_; |
| 89 |
print "Invalid option provided: $opt_name\n"; |
| 90 |
pod2usage(2); |
| 91 |
} |
| 30 |
|
92 |
|
| 31 |
{ |
93 |
# Set skip options based on check options |
|
|
94 |
sub set_skip_options { |
| 95 |
my $has_check_option = grep { $options{$_} == 1 } grep { /^check-/ } keys %options; |
| 96 |
if ($has_check_option) { |
| 97 |
foreach my $key ( keys %options ) { |
| 98 |
$options{$key} = 0 if $key =~ /^skip-/; |
| 99 |
} |
| 100 |
} |
| 101 |
return %options; |
| 102 |
} |
| 103 |
|
| 104 |
sub CheckItemsBranch { |
| 32 |
my $items = Koha::Items->search( { -or => { homebranch => undef, holdingbranch => undef } } ); |
105 |
my $items = Koha::Items->search( { -or => { homebranch => undef, holdingbranch => undef } } ); |
| 33 |
if ( $items->count ) { new_section("Not defined items.homebranch and/or items.holdingbranch") } |
106 |
if ( $items->count ) { new_section("Not defined items.homebranch and/or items.holdingbranch") } |
| 34 |
while ( my $item = $items->next ) { |
107 |
while ( my $item = $items->next ) { |
| 35 |
if ( not $item->homebranch and not $item->holdingbranch ) { |
108 |
if ( not $item->homebranch and not $item->holdingbranch ) { |
| 36 |
new_item( |
109 |
new_item( |
| 37 |
sprintf "Item with itemnumber=%s does not have homebranch and holdingbranch defined", |
110 |
sprintf |
| 38 |
$item->itemnumber |
111 |
"Item with itemnumber=%s and biblionumber=%s does not have homebranch and holdingbranch defined", |
|
|
112 |
$item->itemnumber, $item->biblionumber |
| 39 |
); |
113 |
); |
| 40 |
} elsif ( not $item->homebranch ) { |
114 |
} elsif ( not $item->homebranch ) { |
| 41 |
new_item( sprintf "Item with itemnumber=%s does not have homebranch defined", $item->itemnumber ); |
115 |
new_item( |
|
|
116 |
sprintf "Item with itemnumber=%s and biblionumber=%s does not have homebranch defined", |
| 117 |
$item->itemnumber, $item->biblionumber |
| 118 |
); |
| 42 |
} else { |
119 |
} else { |
| 43 |
new_item( sprintf "Item with itemnumber=%s does not have holdingbranch defined", $item->itemnumber ); |
120 |
new_item( |
|
|
121 |
sprintf "Item with itemnumber=%s and biblionumber=%s does not have holdingbranch defined", |
| 122 |
$item->itemnumber, $item->biblionumber |
| 123 |
); |
| 44 |
} |
124 |
} |
| 45 |
} |
125 |
} |
| 46 |
if ( $items->count ) { new_hint("Edit these items and set valid homebranch and/or holdingbranch") } |
126 |
if ( $items->count ) { new_hint("Edit these items and set valid homebranch and/or holdingbranch") } |
| 47 |
} |
127 |
} |
| 48 |
|
128 |
|
| 49 |
{ |
129 |
sub CheckItemsAuthHeader { |
|
|
130 |
|
| 50 |
# No join possible, FK is missing at DB level |
131 |
# No join possible, FK is missing at DB level |
| 51 |
my @auth_types = Koha::Authority::Types->search->get_column('authtypecode'); |
132 |
my @auth_types = Koha::Authority::Types->search->get_column('authtypecode'); |
| 52 |
my $authorities = Koha::Authorities->search( { authtypecode => { 'not in' => \@auth_types } } ); |
133 |
my $authorities = Koha::Authorities->search( { authtypecode => { 'not in' => \@auth_types } } ); |
|
Lines 57-66
use C4::Biblio qw( GetMarcFromKohaField );
Link Here
|
| 57 |
$authority->authtypecode |
138 |
$authority->authtypecode |
| 58 |
); |
139 |
); |
| 59 |
} |
140 |
} |
| 60 |
if ( $authorities->count ) { new_hint("Go to 'Home › Administration › Authority types' to define them") } |
141 |
if ( $authorities->count ) { new_hint("Go to 'Home -> Administration -> Authority types' to define them") } |
| 61 |
} |
142 |
} |
| 62 |
|
143 |
|
| 63 |
{ |
144 |
sub CheckItemsStatus { |
| 64 |
if ( C4::Context->preference('item-level_itypes') ) { |
145 |
if ( C4::Context->preference('item-level_itypes') ) { |
| 65 |
my $items_without_itype = Koha::Items->search( { -or => [ itype => undef, itype => '' ] } ); |
146 |
my $items_without_itype = Koha::Items->search( { -or => [ itype => undef, itype => '' ] } ); |
| 66 |
if ( $items_without_itype->count ) { |
147 |
if ( $items_without_itype->count ) { |
|
Lines 69-82
use C4::Biblio qw( GetMarcFromKohaField );
Link Here
|
| 69 |
if ( defined $item->biblioitem->itemtype && $item->biblioitem->itemtype ne '' ) { |
150 |
if ( defined $item->biblioitem->itemtype && $item->biblioitem->itemtype ne '' ) { |
| 70 |
new_item( |
151 |
new_item( |
| 71 |
sprintf |
152 |
sprintf |
| 72 |
"Item with itemnumber=%s does not have a itype value, biblio's item type will be used (%s)", |
153 |
"Item with itemnumber=%s and biblionumber = %s does not have a itype value, biblio's item type will be used (%s)", |
| 73 |
$item->itemnumber, $item->biblioitem->itemtype |
154 |
$item->itemnumber, $item->biblioitem->biblionumber, $item->biblioitem->itemtype |
| 74 |
); |
155 |
); |
| 75 |
} else { |
156 |
} else { |
| 76 |
new_item( |
157 |
new_item( |
| 77 |
sprintf |
158 |
sprintf |
| 78 |
"Item with itemnumber=%s does not have a itype value, additionally no item type defined for biblionumber=%s", |
159 |
"Item with itemnumber=%s and biblionumber = %s does not have a itype value, additionally no item type defined for biblionumber=%s", |
| 79 |
$item->itemnumber, $item->biblioitem->biblionumber |
160 |
$item->itemnumber, $item->biblioitem->biblionumber, $item->biblioitem->biblionumber |
| 80 |
); |
161 |
); |
| 81 |
} |
162 |
} |
| 82 |
} |
163 |
} |
|
Lines 229-235
use C4::Biblio qw( GetMarcFromKohaField );
Link Here
|
| 229 |
} |
310 |
} |
| 230 |
} |
311 |
} |
| 231 |
|
312 |
|
| 232 |
{ |
313 |
sub CheckItemsFramework { |
| 233 |
my @framework_codes = Koha::BiblioFrameworks->search()->get_column('frameworkcode'); |
314 |
my @framework_codes = Koha::BiblioFrameworks->search()->get_column('frameworkcode'); |
| 234 |
push @framework_codes, ""; # The default is not stored in frameworks, we need to force it |
315 |
push @framework_codes, ""; # The default is not stored in frameworks, we need to force it |
| 235 |
|
316 |
|
|
Lines 294-305
use C4::Biblio qw( GetMarcFromKohaField );
Link Here
|
| 294 |
$table eq 'items' ? $i->$column |
375 |
$table eq 'items' ? $i->$column |
| 295 |
: $table eq 'biblio' ? $i->biblio->$column |
376 |
: $table eq 'biblio' ? $i->biblio->$column |
| 296 |
: $i->biblioitem->$column; |
377 |
: $i->biblioitem->$column; |
| 297 |
$output .= " {" . $i->itemnumber . " => " . $value . "}\n"; |
378 |
$output .= " {" . $i->itemnumber . " and " . $i->biblioitem->biblionumber . " => " . $value . "}\n"; |
| 298 |
} |
379 |
} |
| 299 |
new_item( |
380 |
new_item( |
| 300 |
sprintf( |
381 |
sprintf( |
| 301 |
"The Framework *%s* is using the authorised value's category *%s*, " |
382 |
"The Framework *%s* is using the authorised value's category *%s*, " |
| 302 |
. "but the following %s do not have a value defined ({itemnumber => value }):\n%s", |
383 |
. "but the following %s do not have a value defined ({itemnumber and biblionumber => value }):\n%s", |
| 303 |
$frameworkcode, $av_category, $kohafield, $output |
384 |
$frameworkcode, $av_category, $kohafield, $output |
| 304 |
) |
385 |
) |
| 305 |
); |
386 |
); |
|
Lines 308-314
use C4::Biblio qw( GetMarcFromKohaField );
Link Here
|
| 308 |
} |
389 |
} |
| 309 |
} |
390 |
} |
| 310 |
|
391 |
|
| 311 |
{ |
392 |
sub CheckItemsTitle { |
| 312 |
my $biblios = Koha::Biblios->search( |
393 |
my $biblios = Koha::Biblios->search( |
| 313 |
{ |
394 |
{ |
| 314 |
-or => [ |
395 |
-or => [ |
|
Lines 329-335
use C4::Biblio qw( GetMarcFromKohaField );
Link Here
|
| 329 |
} |
410 |
} |
| 330 |
} |
411 |
} |
| 331 |
|
412 |
|
| 332 |
{ |
413 |
sub CheckAgeForCategory { |
| 333 |
my $aging_patrons = Koha::Patrons->search( |
414 |
my $aging_patrons = Koha::Patrons->search( |
| 334 |
{ |
415 |
{ |
| 335 |
-not => { |
416 |
-not => { |
|
Lines 502-527
sub new_hint {
Link Here
|
| 502 |
say "=> $name"; |
583 |
say "=> $name"; |
| 503 |
} |
584 |
} |
| 504 |
|
585 |
|
| 505 |
=head1 NAME |
|
|
| 506 |
|
| 507 |
search_for_data_inconsistencies.pl |
| 508 |
|
| 509 |
=head1 SYNOPSIS |
586 |
=head1 SYNOPSIS |
| 510 |
|
587 |
|
| 511 |
perl search_for_data_inconsistencies.pl |
588 |
search_for_data_inconsistencies.pl [options] |
| 512 |
|
589 |
|
| 513 |
=head1 DESCRIPTION |
590 |
=head2 DESCRIPTION |
| 514 |
|
591 |
|
| 515 |
Catch data inconsistencies in Koha database |
592 |
Catch data inconsistencies in Koha database: |
| 516 |
|
593 |
|
| 517 |
* Items with undefined homebranch and/or holdingbranch |
594 |
* Items with undefined homebranch and/or holdingbranch |
| 518 |
* Authorities with undefined authtypecodes/authority types |
595 |
* Authorities with undefined authtypecodes/authority types |
| 519 |
* Item types: |
596 |
* Item types: |
| 520 |
* if item types are defined at item level (item-level_itypes=specific item), |
597 |
* if item types are defined at item level (item-level_itypes=specific item), |
| 521 |
then items.itype must be set else biblioitems.itemtype must be set |
598 |
then items.itype must be set else biblioitems.itemtype must be set |
| 522 |
* Item types defined in items or biblioitems must be defined in the itemtypes table |
599 |
* Item types defined in items or biblioitems must be defined in the itemtypes table |
| 523 |
* Invalid MARCXML in bibliographic records |
600 |
* Bibliographic records without a title |
| 524 |
* Patrons with invalid category types due to lower and upper age limits |
601 |
* Invalid MARCXML in bibliographic records |
|
|
602 |
* Patrons with invalid category types due to lower and upper age limits |
| 525 |
* Any date fields in the database (timestamp, datetime, date) set to 0000-00-00 |
603 |
* Any date fields in the database (timestamp, datetime, date) set to 0000-00-00 |
| 526 |
|
604 |
|
|
|
605 |
=head2 OPTIONS |
| 606 |
|
| 607 |
--check-branch Check for items without home or holding library |
| 608 |
--check-auth Check for authority records with invalid authority type |
| 609 |
--check-status Check for bibliographic records and items without an item type or with an invalid item type |
| 610 |
--check-framework Check for invalid values in fields where the framework limits to an authorized value category |
| 611 |
--check-title Check for bibliographic records without a title |
| 612 |
--check-age Check for patrons with invalid age for category |
| 613 |
--skip-branch Skip checking for items without home or holding library |
| 614 |
--skip-auth Skip checking for authority records with invalid authority type |
| 615 |
--skip-status Skip checking for bibliographic records and items without an item type or with an invalid item type |
| 616 |
--skip-framework Skip checking for invalid values in fields where the framework limits to an authorized value category |
| 617 |
--skip-title Skip checking for bibliographic records without a title |
| 618 |
--skip-age Skip checking for patrons with invalid age for category |
| 619 |
--help Print usage information |
| 620 |
|
| 621 |
Note: If no options are provided, all tests will be run. |
| 622 |
|
| 527 |
=cut |
623 |
=cut |
| 528 |
- |
|
|