|
Lines 16-22
Link Here
|
| 16 |
# along with Koha; if not, see <http://www.gnu.org/licenses>. |
16 |
# along with Koha; if not, see <http://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 203-209
use C4::Biblio qw( GetMarcFromKohaField );
Link Here
|
| 203 |
} |
284 |
} |
| 204 |
} |
285 |
} |
| 205 |
|
286 |
|
| 206 |
{ |
287 |
sub CheckItemsFramework { |
| 207 |
my @framework_codes = Koha::BiblioFrameworks->search()->get_column('frameworkcode'); |
288 |
my @framework_codes = Koha::BiblioFrameworks->search()->get_column('frameworkcode'); |
| 208 |
push @framework_codes, ""; # The default is not stored in frameworks, we need to force it |
289 |
push @framework_codes, ""; # The default is not stored in frameworks, we need to force it |
| 209 |
|
290 |
|
|
Lines 268-279
use C4::Biblio qw( GetMarcFromKohaField );
Link Here
|
| 268 |
$table eq 'items' ? $i->$column |
349 |
$table eq 'items' ? $i->$column |
| 269 |
: $table eq 'biblio' ? $i->biblio->$column |
350 |
: $table eq 'biblio' ? $i->biblio->$column |
| 270 |
: $i->biblioitem->$column; |
351 |
: $i->biblioitem->$column; |
| 271 |
$output .= " {" . $i->itemnumber . " => " . $value . "}\n"; |
352 |
$output .= " {" . $i->itemnumber . " and " . $i->biblioitem->biblionumber . " => " . $value . "}\n"; |
| 272 |
} |
353 |
} |
| 273 |
new_item( |
354 |
new_item( |
| 274 |
sprintf( |
355 |
sprintf( |
| 275 |
"The Framework *%s* is using the authorised value's category *%s*, " |
356 |
"The Framework *%s* is using the authorised value's category *%s*, " |
| 276 |
. "but the following %s do not have a value defined ({itemnumber => value }):\n%s", |
357 |
. "but the following %s do not have a value defined ({itemnumber and biblionumber => value }):\n%s", |
| 277 |
$frameworkcode, $av_category, $kohafield, $output |
358 |
$frameworkcode, $av_category, $kohafield, $output |
| 278 |
) |
359 |
) |
| 279 |
); |
360 |
); |
|
Lines 282-288
use C4::Biblio qw( GetMarcFromKohaField );
Link Here
|
| 282 |
} |
363 |
} |
| 283 |
} |
364 |
} |
| 284 |
|
365 |
|
| 285 |
{ |
366 |
sub CheckItemsTitle { |
| 286 |
my $biblios = Koha::Biblios->search( |
367 |
my $biblios = Koha::Biblios->search( |
| 287 |
{ |
368 |
{ |
| 288 |
-or => [ |
369 |
-or => [ |
|
Lines 303-309
use C4::Biblio qw( GetMarcFromKohaField );
Link Here
|
| 303 |
} |
384 |
} |
| 304 |
} |
385 |
} |
| 305 |
|
386 |
|
| 306 |
{ |
387 |
sub CheckAgeForCategory { |
| 307 |
my $aging_patrons = Koha::Patrons->search( |
388 |
my $aging_patrons = Koha::Patrons->search( |
| 308 |
{ |
389 |
{ |
| 309 |
-not => { |
390 |
-not => { |
|
Lines 476-501
sub new_hint {
Link Here
|
| 476 |
say "=> $name"; |
557 |
say "=> $name"; |
| 477 |
} |
558 |
} |
| 478 |
|
559 |
|
| 479 |
=head1 NAME |
|
|
| 480 |
|
| 481 |
search_for_data_inconsistencies.pl |
| 482 |
|
| 483 |
=head1 SYNOPSIS |
560 |
=head1 SYNOPSIS |
| 484 |
|
561 |
|
| 485 |
perl search_for_data_inconsistencies.pl |
562 |
search_for_data_inconsistencies.pl [options] |
| 486 |
|
563 |
|
| 487 |
=head1 DESCRIPTION |
564 |
=head2 DESCRIPTION |
| 488 |
|
565 |
|
| 489 |
Catch data inconsistencies in Koha database |
566 |
Catch data inconsistencies in Koha database: |
| 490 |
|
567 |
|
| 491 |
* Items with undefined homebranch and/or holdingbranch |
568 |
* Items with undefined homebranch and/or holdingbranch |
| 492 |
* Authorities with undefined authtypecodes/authority types |
569 |
* Authorities with undefined authtypecodes/authority types |
| 493 |
* Item types: |
570 |
* Item types: |
| 494 |
* if item types are defined at item level (item-level_itypes=specific item), |
571 |
* if item types are defined at item level (item-level_itypes=specific item), |
| 495 |
then items.itype must be set else biblioitems.itemtype must be set |
572 |
then items.itype must be set else biblioitems.itemtype must be set |
| 496 |
* Item types defined in items or biblioitems must be defined in the itemtypes table |
573 |
* Item types defined in items or biblioitems must be defined in the itemtypes table |
| 497 |
* Invalid MARCXML in bibliographic records |
574 |
* Bibliographic records without a title |
| 498 |
* Patrons with invalid category types due to lower and upper age limits |
575 |
* Invalid MARCXML in bibliographic records |
|
|
576 |
* Patrons with invalid category types due to lower and upper age limits |
| 499 |
* Any date fields in the database (timestamp, datetime, date) set to 0000-00-00 |
577 |
* Any date fields in the database (timestamp, datetime, date) set to 0000-00-00 |
| 500 |
|
578 |
|
|
|
579 |
=head2 OPTIONS |
| 580 |
|
| 581 |
--check-branch Check for items without home or holding library |
| 582 |
--check-auth Check for authority records with invalid authority type |
| 583 |
--check-status Check for bibliographic records and items without an item type or with an invalid item type |
| 584 |
--check-framework Check for invalid values in fields where the framework limits to an authorized value category |
| 585 |
--check-title Check for bibliographic records without a title |
| 586 |
--check-age Check for patrons with invalid age for category |
| 587 |
--skip-branch Skip checking for items without home or holding library |
| 588 |
--skip-auth Skip checking for authority records with invalid authority type |
| 589 |
--skip-status Skip checking for bibliographic records and items without an item type or with an invalid item type |
| 590 |
--skip-framework Skip checking for invalid values in fields where the framework limits to an authorized value category |
| 591 |
--skip-title Skip checking for bibliographic records without a title |
| 592 |
--skip-age Skip checking for patrons with invalid age for category |
| 593 |
--help Print usage information |
| 594 |
|
| 595 |
Note: If no options are provided, all tests will be run. |
| 596 |
|
| 501 |
=cut |
597 |
=cut |
| 502 |
- |
|
|