View | Details | Raw Unified | Return to bug 36027
Collapse All | Expand All

(-)a/misc/maintenance/search_for_data_inconsistencies.pl (-198 / +407 lines)
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-49 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
    'check-loop'      => 0,
40
    'skip-branch'     => 0,
41
    'skip-auth'       => 0,
42
    'skip-status'     => 0,
43
    'skip-framework'  => 0,
44
    'skip-title'      => 0,
45
    'skip-age'        => 0,
46
    'skip-loop'       => 0,
47
    'help'            => 0,
48
);
49
50
# Parse command line options
51
GetOptions(
52
    'check-branch'    => sub { $options{'check-branch'}    = 1 },
53
    'check-auth'      => sub { $options{'check-auth'}      = 1 },
54
    'check-status'    => sub { $options{'check-status'}    = 1 },
55
    'check-framework' => sub { $options{'check-framework'} = 1 },
56
    'check-title'     => sub { $options{'check-title'}     = 1 },
57
    'check-age'       => sub { $options{'check-age'}       = 1 },
58
    'check-loop'      => sub { $options{'check-loop'}      = 1 },
59
    'skip-branch'     => sub { $options{'skip-branch'}     = 1 },
60
    'skip-auth'       => sub { $options{'skip-auth'}       = 1 },
61
    'skip-status'     => sub { $options{'skip-status'}     = 1 },
62
    'skip-framework'  => sub { $options{'skip-framework'}  = 1 },
63
    'skip-title'      => sub { $options{'skip-title'}      = 1 },
64
    'skip-age'        => sub { $options{'skip-age'}        = 1 },
65
    'skip-loop'       => sub { $options{'skip-loop'}       = 1 },
66
    'help'            => sub { $options{'help'}            = 1; },
67
) or pod2usage(2);    # Print usage if invalid options are provided
68
69
# Call handle_invalid_options subroutine if invalid options are provided
70
Getopt::Long::Configure('pass_through');
71
GetOptions(
72
    '<>' => sub {
73
        my ($opt_name) = @_;
74
        handle_invalid_options($opt_name);
75
    }
76
);
77
78
# Print usage and exit if --help flag is provided
79
pod2usage(1) if $options{'help'};
80
81
# Set skip options based on check options
82
set_skip_options();
83
# Set all check options to 1 if none are provided, unless specified skip options
84
set_all_check_options_if_none_provided();
85
# Run checks based on options
86
CheckItemsBranch()       if $options{'check-branch'}    && !$options{'skip-branch'};
87
CheckItemsAuthHeader()   if $options{'check-auth'}      && !$options{'skip-auth'};
88
CheckItemsStatus()       if $options{'check-status'}    && !$options{'skip-status'};
89
CheckItemsFramework()    if $options{'check-framework'} && !$options{'skip-framework'};
90
CheckItemsTitle()        if $options{'check-title'}     && !$options{'skip-title'};
91
CheckAgeForCategory()    if $options{'check-age'}       && !$options{'skip-age'};
92
CheckRelationshipsLoop() if $options{'check-loop'}      && !$options{'skip-loop'};
93
94
# Handle invalid options
95
sub handle_invalid_options {
96
    my ($opt_name) = @_;
97
    print "Invalid option provided: $opt_name\n";
98
    pod2usage(2);
99
}
100
101
# Set skip options based on check options
102
sub set_skip_options {
103
    # If at least one check option is provided, set all skip options to 0
104
    my $has_check_option = grep { $options{$_} } grep { /^check-/ } keys %options;
105
    if ($has_check_option) {
106
        # if a least one skip option is provided, print a warning
107
        my $has_skip_option = grep { $options{$_} == 1 } grep { /^skip-/ } keys %options;
108
        if ($has_skip_option) {
109
            print("Warning : skip options are ignored when check options are provided\n")
110
        }
111
        # Set all skip options to 0
112
        foreach my $key (keys %options) {
113
            if ($key =~ /^skip-/) {
114
                $options{$key} = 0;
115
            }
116
        }
117
    }
118
    return %options;
119
}
30
120
31
{
121
# Set all check options to 1 if none are provided, considering skip options
122
sub set_all_check_options_if_none_provided {
123
    my $any_check_option_provided = grep { $options{$_} } grep { /^check-/ } keys %options;
124
    if (!$any_check_option_provided) {
125
        foreach my $key (keys %options) {
126
            if ($key =~ /^check-/) {
127
                my $skip_key = $key;
128
                $skip_key =~ s/^check-/skip-/;
129
                # Set check option to 1 unless the corresponding skip option indicated
130
                $options{$key} = 1 unless $options{$skip_key};
131
            }
132
        }
133
    }
134
}
135
136
sub CheckItemsBranch {
32
    my $items = Koha::Items->search({ -or => { homebranch => undef, holdingbranch => undef }});
137
    my $items = Koha::Items->search({ -or => { homebranch => undef, holdingbranch => undef }});
33
    if ( $items->count ) { new_section("Not defined items.homebranch and/or items.holdingbranch")}
138
    if ( $items->count ) { new_section("Not defined items.homebranch and/or items.holdingbranch")}
34
    while ( my $item = $items->next ) {
139
    while ( my $item = $items->next ) {
35
        if ( not $item->homebranch and not $item->holdingbranch ) {
140
        if ( not $item->homebranch and not $item->holdingbranch ) {
36
            new_item(sprintf "Item with itemnumber=%s does not have homebranch and holdingbranch defined", $item->itemnumber);
141
            new_item(sprintf "Item with itemnumber=%s and biblionumber=%s does not have homebranch and holdingbranch defined",  $item->itemnumber, $item->biblionumber);
37
        } elsif ( not $item->homebranch ) {
142
            } elsif ( not $item->homebranch ) {
38
            new_item(sprintf "Item with itemnumber=%s does not have homebranch defined", $item->itemnumber);
143
            new_item(sprintf "Item with itemnumber=%s and biblionumber=%s does not have homebranch defined", $item->itemnumber, $item->biblionumber);
39
        } else {
144
            } else {
40
            new_item(sprintf "Item with itemnumber=%s does not have holdingbranch defined", $item->itemnumber);
145
            new_item(sprintf "Item with itemnumber=%s and biblionumber=%s does not have holdingbranch defined", $item->itemnumber, $item->biblionumber);
41
        }
146
        }
42
    }
147
    }
43
    if ( $items->count ) { new_hint("Edit these items and set valid homebranch and/or holdingbranch")}
148
    if ( $items->count ) { new_hint("Edit these items and set valid homebranch and/or holdingbranch")}
44
}
149
}
45
150
46
{
151
sub CheckItemsAuthHeader {
47
    # No join possible, FK is missing at DB level
152
    # No join possible, FK is missing at DB level
48
    my @auth_types = Koha::Authority::Types->search->get_column('authtypecode');
153
    my @auth_types = Koha::Authority::Types->search->get_column('authtypecode');
49
    my $authorities = Koha::Authorities->search({authtypecode => { 'not in' => \@auth_types } });
154
    my $authorities = Koha::Authorities->search({authtypecode => { 'not in' => \@auth_types } });
Lines 51-279 use C4::Biblio qw( GetMarcFromKohaField ); Link Here
51
    while ( my $authority = $authorities->next ) {
156
    while ( my $authority = $authorities->next ) {
52
        new_item(sprintf "Authority with authid=%s does not have a code defined (%s)", $authority->authid, $authority->authtypecode);
157
        new_item(sprintf "Authority with authid=%s does not have a code defined (%s)", $authority->authid, $authority->authtypecode);
53
    }
158
    }
54
    if ( $authorities->count ) {new_hint("Go to 'Home › Administration › Authority types' to define them")}
159
    if ( $authorities->count ) {new_hint("Go to 'Home -> Administration -> Authority types' to define them")};
55
}
160
    };
56
161
57
{
162
    sub CheckItemsStatus {
58
    if ( C4::Context->preference('item-level_itypes') ) {
163
        if ( C4::Context->preference('item-level_itypes') ) {
59
        my $items_without_itype = Koha::Items->search( { -or => [itype => undef,itype => ''] } );
164
            my $items_without_itype = Koha::Items->search( { -or => [itype => undef,itype => ''] } );
60
        if ( $items_without_itype->count ) {
165
            if ( $items_without_itype->count ) {
61
            new_section("Items do not have itype defined");
166
                new_section("Items do not have itype defined");
62
            while ( my $item = $items_without_itype->next ) {
167
                while ( my $item = $items_without_itype->next ) {
63
                if (defined $item->biblioitem->itemtype && $item->biblioitem->itemtype ne '' ) {
168
                    if (defined $item->biblioitem->itemtype && $item->biblioitem->itemtype ne '' ) {
64
                    new_item(
169
                        new_item(
65
                        sprintf "Item with itemnumber=%s does not have a itype value, biblio's item type will be used (%s)",
170
                        sprintf "Item with itemnumber=%s and biblionumber = %s does not have a itype value, biblio's item type will be used (%s)",
66
                        $item->itemnumber, $item->biblioitem->itemtype
171
                        $item->itemnumber, $item->biblioitem->biblionumber, $item->biblioitem->itemtype
67
                    );
172
                        );
68
                } else {
173
                        } else {
69
                    new_item(
174
                        new_item(
70
                        sprintf "Item with itemnumber=%s does not have a itype value, additionally no item type defined for biblionumber=%s",
175
                        sprintf "Item with itemnumber=%s and biblionumber = %s does not have a itype value, additionally no item type defined for biblionumber=%s",
71
                        $item->itemnumber, $item->biblioitem->biblionumber
176
                        $item->itemnumber, $item->biblioitem->biblionumber, $item->biblioitem->biblionumber
72
                    );
177
                        );
73
               }
178
                    }
179
                }
180
                new_hint("The system preference item-level_itypes expects item types to be defined at item level");
74
            }
181
            }
75
            new_hint("The system preference item-level_itypes expects item types to be defined at item level");
182
        }else{
76
        }
183
            my $biblioitems_without_itemtype = Koha::Biblioitems->search( { itemtype => undef } );
77
    }
184
            if ( $biblioitems_without_itemtype->count ) {
78
    else {
185
                new_section("Biblioitems do not have itemtype defined");
79
        my $biblioitems_without_itemtype = Koha::Biblioitems->search( { itemtype => undef } );
186
                while ( my $biblioitem = $biblioitems_without_itemtype->next ) {
80
        if ( $biblioitems_without_itemtype->count ) {
187
                    new_item(
81
            new_section("Biblioitems do not have itemtype defined");
82
            while ( my $biblioitem = $biblioitems_without_itemtype->next ) {
83
                new_item(
84
                    sprintf "Biblioitem with biblioitemnumber=%s does not have a itemtype value",
188
                    sprintf "Biblioitem with biblioitemnumber=%s does not have a itemtype value",
85
                    $biblioitem->biblioitemnumber
189
                    $biblioitem->biblioitemnumber
86
                );
190
                    );
191
                }
192
                new_hint("The system preference item-level_itypes expects item types to be defined at biblio level");
87
            }
193
            }
88
            new_hint("The system preference item-level_itypes expects item types to be defined at biblio level");
89
        }
194
        }
90
    }
91
195
92
    my @itemtypes = Koha::ItemTypes->search->get_column('itemtype');
196
        my @itemtypes = Koha::ItemTypes->search->get_column('itemtype');
93
    if ( C4::Context->preference('item-level_itypes') ) {
197
        if ( C4::Context->preference('item-level_itypes') ) {
94
        my $items_with_invalid_itype = Koha::Items->search( { -and => [itype => { not_in => \@itemtypes }, itype => { '!=' => '' }] } );
198
            my $items_with_invalid_itype = Koha::Items->search( { -and => [itype => { not_in => \@itemtypes }, itype => { '!=' => '' }] } );
95
        if ( $items_with_invalid_itype->count ) {
199
            if ( $items_with_invalid_itype->count ) {
96
            new_section("Items have invalid itype defined");
200
                new_section("Items have invalid itype defined");
97
            while ( my $item = $items_with_invalid_itype->next ) {
201
                while ( my $item = $items_with_invalid_itype->next ) {
98
                new_item(
202
                    new_item(
99
                    sprintf "Item with itemnumber=%s, biblionumber=%s does not have a valid itype value (%s)",
203
                    sprintf "Item with itemnumber=%s, biblionumber=%s does not have a valid itype value (%s)",
100
                    $item->itemnumber, $item->biblionumber, $item->itype
204
                    $item->itemnumber, $item->biblionumber, $item->itype
101
                );
205
                    );
206
                }
207
                new_hint("The items must have a itype value that is defined in the item types of Koha (Home › Administration › Item types administration)");
102
            }
208
            }
103
            new_hint("The items must have a itype value that is defined in the item types of Koha (Home › Administration › Item types administration)");
209
        }else{
104
        }
210
            my $biblioitems_with_invalid_itemtype = Koha::Biblioitems->search(
105
    }
106
    else {
107
        my $biblioitems_with_invalid_itemtype = Koha::Biblioitems->search(
108
            { itemtype => { not_in => \@itemtypes } }
211
            { itemtype => { not_in => \@itemtypes } }
109
        );
212
            );
110
        if ( $biblioitems_with_invalid_itemtype->count ) {
213
            if ( $biblioitems_with_invalid_itemtype->count ) {
111
            new_section("Biblioitems do not have itemtype defined");
214
                new_section("Biblioitems do not have itemtype defined");
112
            while ( my $biblioitem = $biblioitems_with_invalid_itemtype->next ) {
215
                while ( my $biblioitem = $biblioitems_with_invalid_itemtype->next ) {
113
                new_item(
216
                    new_item(
114
                    sprintf "Biblioitem with biblioitemnumber=%s does not have a valid itemtype value",
217
                    sprintf "Biblioitem with biblioitemnumber=%s does not have a valid itemtype value",
115
                    $biblioitem->biblioitemnumber
218
                    $biblioitem->biblioitemnumber
116
                );
219
                    );
220
                }
221
                new_hint("The biblioitems must have a itemtype value that is defined in the item types of Koha (Home › Administration › Item types administration)");
117
            }
222
            }
118
            new_hint("The biblioitems must have a itemtype value that is defined in the item types of Koha (Home › Administration › Item types administration)");
119
        }
223
        }
120
    }
121
224
122
    my ( @decoding_errors, @ids_not_in_marc );
225
        my ( @decoding_errors, @ids_not_in_marc );
123
    my $biblios = Koha::Biblios->search;
226
        my $biblios = Koha::Biblios->search;
124
    my ( $biblio_tag,     $biblio_subfield )     = C4::Biblio::GetMarcFromKohaField( "biblio.biblionumber" );
227
        my ( $biblio_tag,     $biblio_subfield )     = C4::Biblio::GetMarcFromKohaField( "biblio.biblionumber" );
125
    my ( $biblioitem_tag, $biblioitem_subfield ) = C4::Biblio::GetMarcFromKohaField( "biblioitems.biblioitemnumber" );
228
        my ( $biblioitem_tag, $biblioitem_subfield ) = C4::Biblio::GetMarcFromKohaField( "biblioitems.biblioitemnumber" );
126
    while ( my $biblio = $biblios->next ) {
229
        while ( my $biblio = $biblios->next ) {
127
        my $record = eval{$biblio->metadata->record;};
230
            my $record = eval{$biblio->metadata->record;};
128
        if ($@) {
231
            if ($@) {
129
            push @decoding_errors, $@;
232
                push @decoding_errors, $@;
130
            next;
233
                next;
131
        }
234
            }
132
        my ( $biblionumber, $biblioitemnumber );
235
            my ( $biblionumber, $biblioitemnumber );
133
        if ( $biblio_tag < 10 ) {
236
            if ( $biblio_tag < 10 ) {
134
            my $biblio_control_field = $record->field($biblio_tag);
237
                my $biblio_control_field = $record->field($biblio_tag);
135
            $biblionumber = $biblio_control_field->data if $biblio_control_field;
238
                $biblionumber = $biblio_control_field->data if $biblio_control_field;
136
        } else {
239
                } else {
137
            $biblionumber = $record->subfield( $biblio_tag, $biblio_subfield );
240
                $biblionumber = $record->subfield( $biblio_tag, $biblio_subfield );
138
        }
241
            }
139
        if ( $biblioitem_tag < 10 ) {
242
            if ( $biblioitem_tag < 10 ) {
140
            my $biblioitem_control_field = $record->field($biblioitem_tag);
243
                my $biblioitem_control_field = $record->field($biblioitem_tag);
141
            $biblioitemnumber = $biblioitem_control_field->data if $biblioitem_control_field;
244
                $biblioitemnumber = $biblioitem_control_field->data if $biblioitem_control_field;
142
        } else {
245
                } else {
143
            $biblioitemnumber = $record->subfield( $biblioitem_tag, $biblioitem_subfield );
246
                $biblioitemnumber = $record->subfield( $biblioitem_tag, $biblioitem_subfield );
144
        }
247
            }
145
        if ( $biblionumber != $biblio->biblionumber ) {
248
            if ( $biblionumber != $biblio->biblionumber ) {
146
            push @ids_not_in_marc,
249
                push @ids_not_in_marc,
147
              {
250
                {
148
                biblionumber         => $biblio->biblionumber,
251
                biblionumber         => $biblio->biblionumber,
149
                biblionumber_in_marc => $biblionumber,
252
                biblionumber_in_marc => $biblionumber,
150
              };
253
                };
151
        }
254
            }
152
        if ( $biblioitemnumber != $biblio->biblioitem->biblioitemnumber ) {
255
            if ( $biblioitemnumber != $biblio->biblioitem->biblioitemnumber ) {
153
            push @ids_not_in_marc,
256
                push @ids_not_in_marc,
154
            {
257
                {
155
                biblionumber     => $biblio->biblionumber,
258
                biblionumber     => $biblio->biblionumber,
156
                biblioitemnumber => $biblio->biblioitem->biblioitemnumber,
259
                biblioitemnumber => $biblio->biblioitem->biblioitemnumber,
157
                biblioitemnumber_in_marc => $biblionumber,
260
                biblioitemnumber_in_marc => $biblionumber,
158
            };
261
                };
262
            }
159
        }
263
        }
160
    }
264
        if ( @decoding_errors ) {
161
    if ( @decoding_errors ) {
265
            new_section("Bibliographic records have invalid MARCXML");
162
        new_section("Bibliographic records have invalid MARCXML");
266
            new_item($_) for @decoding_errors;
163
        new_item($_) for @decoding_errors;
267
            new_hint("The bibliographic records must have a valid MARCXML or you will face encoding issues or wrong displays");
164
        new_hint("The bibliographic records must have a valid MARCXML or you will face encoding issues or wrong displays");
268
        }
165
    }
269
        if (@ids_not_in_marc) {
166
    if (@ids_not_in_marc) {
270
            new_section("Bibliographic records have MARCXML without biblionumber or biblioitemnumber");
167
        new_section("Bibliographic records have MARCXML without biblionumber or biblioitemnumber");
271
            for my $id (@ids_not_in_marc) {
168
        for my $id (@ids_not_in_marc) {
272
                if ( exists $id->{biblioitemnumber} ) {
169
            if ( exists $id->{biblioitemnumber} ) {
273
                    new_item(
170
                new_item(
171
                    sprintf(q{Biblionumber %s has biblioitemnumber '%s' but should be '%s' in %s$%s},
274
                    sprintf(q{Biblionumber %s has biblioitemnumber '%s' but should be '%s' in %s$%s},
172
                        $id->{biblionumber},
275
                    $id->{biblionumber},
173
                        $id->{biblioitemnumber},
276
                    $id->{biblioitemnumber},
174
                        $id->{biblioitemnumber_in_marc},
277
                    $id->{biblioitemnumber_in_marc},
175
                        $biblioitem_tag,
278
                    $biblioitem_tag,
176
                        $biblioitem_subfield,
279
                    $biblioitem_subfield,
177
                    )
280
                    )
178
                );
281
                    );
179
            }
282
                }else{
180
            else {
283
                    new_item(
181
                new_item(
182
                    sprintf(q{Biblionumber %s has '%s' in %s$%s},
284
                    sprintf(q{Biblionumber %s has '%s' in %s$%s},
183
                        $id->{biblionumber},
285
                    $id->{biblionumber},
184
                        $id->{biblionumber_in_marc},
286
                    $id->{biblionumber_in_marc},
185
                        $biblio_tag,
287
                    $biblio_tag,
186
                        $biblio_subfield,
288
                    $biblio_subfield,
187
                    )
289
                    )
188
                );
290
                    );
291
                }
189
            }
292
            }
293
            new_hint("The bibliographic records must have the biblionumber and biblioitemnumber in MARCXML");
190
        }
294
        }
191
        new_hint("The bibliographic records must have the biblionumber and biblioitemnumber in MARCXML");
192
    }
295
    }
193
}
194
296
195
{
297
    sub CheckItemsFramework {
196
    my @framework_codes = Koha::BiblioFrameworks->search()->get_column('frameworkcode');
298
        my @framework_codes = Koha::BiblioFrameworks->search()->get_column('frameworkcode');
197
    push @framework_codes,""; # The default is not stored in frameworks, we need to force it
299
        push @framework_codes,""; # The default is not stored in frameworks, we need to force it
198
300
199
    my $invalid_av_per_framework = {};
301
        my $invalid_av_per_framework = {};
200
    foreach my $frameworkcode ( @framework_codes ) {
302
        foreach my $frameworkcode ( @framework_codes ) {
201
        # We are only checking fields that are mapped to DB fields
303
            # We are only checking fields that are mapped to DB fields
202
        my $msss = Koha::MarcSubfieldStructures->search({
304
            my $msss = Koha::MarcSubfieldStructures->search({
203
            frameworkcode => $frameworkcode,
305
            frameworkcode => $frameworkcode,
204
            authorised_value => {
306
            authorised_value => {
205
                '!=' => [ -and => ( undef, '' ) ]
307
            '!=' => [ -and => ( undef, '' ) ]
206
            },
308
            },
207
            kohafield => {
309
            kohafield => {
208
                '!=' => [ -and => ( undef, '' ) ]
310
            '!=' => [ -and => ( undef, '' ) ]
209
            }
311
        }
210
        });
312
        });
211
        while ( my $mss = $msss->next ) {
313
        while ( my $mss = $msss->next ) {
212
            my $kohafield = $mss->kohafield;
314
            my $kohafield = $mss->kohafield;
213
            my $av = $mss->authorised_value;
315
            my $av = $mss->authorised_value;
214
            next if grep {$_ eq $av} qw( branches itemtypes cn_source ); # internal categories
316
            next if grep {$_ eq $av} qw( branches itemtypes cn_source ); # internal categories
215
317
            
216
            my $avs = Koha::AuthorisedValues->search_by_koha_field(
318
            my $avs = Koha::AuthorisedValues->search_by_koha_field(
217
                {
319
            {
218
                    frameworkcode => $frameworkcode,
320
            frameworkcode => $frameworkcode,
219
                    kohafield     => $kohafield,
321
            kohafield     => $kohafield,
220
                }
322
        }
221
            );
323
        );
222
            my $tmp_kohafield = $kohafield;
324
        my $tmp_kohafield = $kohafield;
223
            if ( $tmp_kohafield =~ /^biblioitems/ ) {
325
        if ( $tmp_kohafield =~ /^biblioitems/ ) {
224
                $tmp_kohafield =~ s|biblioitems|biblioitem|;
326
            $tmp_kohafield =~ s|biblioitems|biblioitem|;
225
            } else {
327
            } else {
226
                $tmp_kohafield =~ s|items|me|;
328
            $tmp_kohafield =~ s|items|me|;
227
            }
329
        }
228
            # replace items.attr with me.attr
330
        # replace items.attr with me.attr
229
331
230
            # We are only checking biblios with items
332
        # We are only checking biblios with items
231
            my $items = Koha::Items->search(
333
        my $items = Koha::Items->search(
232
                {
334
        {
233
                    $tmp_kohafield =>
335
        $tmp_kohafield =>
234
                      {
336
        {
235
                          -not_in => [ $avs->get_column('authorised_value'), '' ],
337
        -not_in => [ $avs->get_column('authorised_value'), '' ],
236
                          '!='    => undef,
338
        '!='    => undef,
237
                      },
339
        },
238
                    'biblio.frameworkcode' => $frameworkcode
340
        'biblio.frameworkcode' => $frameworkcode
239
                },
341
        },
240
                { join => [ 'biblioitem', 'biblio' ] }
342
        { join => [ 'biblioitem', 'biblio' ] }
241
            );
343
        );
242
            if ( $items->count ) {
344
        if ( $items->count ) {
243
                $invalid_av_per_framework->{ $frameworkcode }->{$av} =
345
            $invalid_av_per_framework->{ $frameworkcode }->{$av} =
244
                  { items => $items, kohafield => $kohafield };
346
            { items => $items, kohafield => $kohafield };
245
            }
246
        }
347
        }
247
    }
348
    }
248
    if (%$invalid_av_per_framework) {
349
}
249
        new_section('Wrong values linked to authorised values');
350
if (%$invalid_av_per_framework) {
250
        for my $frameworkcode ( keys %$invalid_av_per_framework ) {
351
    new_section('Wrong values linked to authorised values');
251
            while ( my ( $av_category, $v ) = each %{$invalid_av_per_framework->{$frameworkcode}} ) {
352
    for my $frameworkcode ( keys %$invalid_av_per_framework ) {
252
                my $items     = $v->{items};
353
        while ( my ( $av_category, $v ) = each %{$invalid_av_per_framework->{$frameworkcode}} ) {
253
                my $kohafield = $v->{kohafield};
354
            my $items     = $v->{items};
254
                my ( $table, $column ) = split '\.', $kohafield;
355
            my $kohafield = $v->{kohafield};
255
                my $output;
356
            my ( $table, $column ) = split '\.', $kohafield;
256
                while ( my $i = $items->next ) {
357
            my $output;
257
                    my $value = $table eq 'items'
358
            while ( my $i = $items->next ) {
258
                        ? $i->$column
359
                my $value = $table eq 'items'
259
                        : $table eq 'biblio'
360
                ? $i->$column
260
                        ? $i->biblio->$column
361
                : $table eq 'biblio'
261
                        : $i->biblioitem->$column;
362
                ? $i->biblio->$column
262
                    $output .= " {" . $i->itemnumber . " => " . $value . "}\n";
363
                : $i->biblioitem->$column;
263
                }
364
                $output .= " {" . $i->itemnumber . " and " . $i->biblioitem->biblionumber. " => " . $value . "}\n";
264
                new_item(
265
                    sprintf(
266
                        "The Framework *%s* is using the authorised value's category *%s*, "
267
                        . "but the following %s do not have a value defined ({itemnumber => value }):\n%s",
268
                        $frameworkcode, $av_category, $kohafield, $output
269
                    )
270
                );
271
            }
365
            }
366
            new_item(
367
            sprintf(
368
            "The Framework *%s* is using the authorised value's category *%s*, "
369
            . "but the following %s do not have a value defined ({itemnumber and biblionumber => value }):\n%s",
370
            $frameworkcode, $av_category, $kohafield, $output
371
            )
372
            );
272
        }
373
        }
273
    }
374
    }
274
}
375
}
376
}
275
377
276
{
378
sub CheckItemsTitle {
277
    my $biblios = Koha::Biblios->search({
379
    my $biblios = Koha::Biblios->search({
278
        -or => [
380
        -or => [
279
            title => '',
381
            title => '',
Lines 292-298 use C4::Biblio qw( GetMarcFromKohaField ); Link Here
292
    }
394
    }
293
}
395
}
294
396
295
{
397
sub CheckAgeForCategory {
296
    my $aging_patrons = Koha::Patrons->search(
398
    my $aging_patrons = Koha::Patrons->search(
297
        {
399
        {
298
            -not => {
400
            -not => {
Lines 326-331 use C4::Biblio qw( GetMarcFromKohaField ); Link Here
326
    }
428
    }
327
}
429
}
328
430
431
sub CheckRelationshipsLoop {
432
    my @loop_borrowers_relationships;
433
    my @guarantor_ids = Koha::Patron::Relationships->_resultset->get_column('guarantor_id')->all();
434
    my @guarantee_ids = Koha::Patron::Relationships->_resultset->get_column('guarantee_id')->all();
435
436
    foreach my $guarantor_id (@guarantor_ids) {
437
        foreach my $guarantee_id (@guarantee_ids) {
438
            if ( $guarantor_id == $guarantee_id ) {
439
440
                my $relation_guarantor_id;
441
                my $relation_guarantee_id;
442
                my $size_list;
443
                my $tmp_garantor_id = $guarantor_id;
444
                my @guarantor_ids;
445
446
                do {
447
                    my @relationship_for_go = Koha::Patron::Relationships->search(
448
                        {
449
                            -or => [
450
                                'guarantor_id' => { '=', $tmp_garantor_id },
451
                            ]
452
                        },
453
                    )->as_list;
454
                    $size_list = scalar @relationship_for_go;
455
456
                    foreach my $relation (@relationship_for_go) {
457
                        $relation_guarantor_id = $relation->guarantor_id;
458
                        unless ( grep { $_ == $relation_guarantor_id } @guarantor_ids ) {
459
                            push @guarantor_ids, $relation_guarantor_id;
460
                        }
461
                        $relation_guarantee_id = $relation->guarantee_id;
462
463
                        my @relationship_for_go = Koha::Patron::Relationships->search(
464
                            {
465
                                -or => [
466
                                    'guarantor_id' => { '=', $relation_guarantee_id },
467
                                ]
468
                            },
469
                        )->as_list;
470
                        $size_list = scalar @relationship_for_go;
471
472
                        if ( $guarantor_id == $relation_guarantee_id ) {
473
                            last;
474
                        }
475
476
                        foreach my $relation (@relationship_for_go) {
477
                            $relation_guarantor_id = $relation->guarantor_id;
478
                            unless ( grep { $_ == $relation_guarantor_id } @guarantor_ids ) {
479
                                push @guarantor_ids, $relation_guarantor_id;
480
                            }
481
                            $relation_guarantee_id = $relation->guarantee_id;
482
483
                            if ( $guarantor_id == $relation_guarantee_id ) {
484
                                last;
485
                            }
486
                        }
487
                        if ( $guarantor_id == $relation_guarantee_id ) {
488
                            last;
489
                        }
490
                    }
491
492
                    $tmp_garantor_id = $relation_guarantee_id;
493
                } while ( $guarantor_id != $relation_guarantee_id && $size_list > 0 );
494
495
                if ( $guarantor_id == $relation_guarantee_id ) {
496
                    unless ( grep { join( "", sort @$_ ) eq join( "", sort @guarantor_ids ) }
497
                        @loop_borrowers_relationships )
498
                    {
499
                        push @loop_borrowers_relationships, \@guarantor_ids;
500
                    }
501
                }
502
            }
503
        }
504
    }
505
506
    if ( scalar @loop_borrowers_relationships > 0 ) {
507
        new_section("The list of guarantors who are also guarantee.");
508
        my $count = 0;
509
        foreach my $table (@loop_borrowers_relationships) {
510
            $count++;
511
            print "Loop $count, borrowers id  : ";
512
            foreach my $borrower_id (@$table) {
513
                print "$borrower_id , ";
514
            }
515
            print "\n";
516
        }
517
        new_hint("Relationships that form guarantor loops must be deleted");
518
    }
519
}
520
329
sub new_section {
521
sub new_section {
330
    my ( $name ) = @_;
522
    my ( $name ) = @_;
331
    say "\n== $name ==";
523
    say "\n== $name ==";
Lines 340-364 sub new_hint { Link Here
340
    say "=> $name";
532
    say "=> $name";
341
}
533
}
342
534
343
=head1 NAME
535
=head1 SYNOPSIS
344
536
345
search_for_data_inconsistencies.pl
537
search_for_data_inconsistencies.pl [options]
346
538
347
=head1 SYNOPSIS
539
=head2 DESCRIPTION
540
541
Catch data inconsistencies in Koha database:
348
542
349
    perl search_for_data_inconsistencies.pl
543
    * Items with undefined homebranch and/or holdingbranch
544
    * Authorities with undefined authtypecodes/authority types
545
    * Item types:
546
        * if item types are defined at item level (item-level_itypes=specific item),
547
        then items.itype must be set else biblioitems.itemtype must be set
548
        * Item types defined in items or biblioitems must be defined in the itemtypes table
549
    * Bibliographic records without a title
550
    * Invalid MARCXML in bibliographic records
551
    * Patrons with invalid category types due to lower and upper age limits
552
    * Relationships that form guarantor loops
350
553
351
=head1 DESCRIPTION
554
=head2 OPTIONS
352
555
353
Catch data inconsistencies in Koha database
556
  --check-branch     Check for items without home or holding library
557
  --check-auth       Check for authority records with invalid authority type
558
  --check-status     Check for bibliographic records and items without an item type or with an invalid item type
559
  --check-framework  Check for invalid values in fields where the framework limits to an authorized value category
560
  --check-title      Check for bibliographic records without a title
561
  --check-age        Check for patrons with invalid age for category
562
  --check-loop       Check for relationships that form guarantor loops
563
  --skip-branch      Skip checking for items without home or holding library
564
  --skip-auth        Skip checking for authority records with invalid authority type
565
  --skip-status      Skip checking for bibliographic records and items without an item type or with an invalid item type
566
  --skip-framework   Skip checking for invalid values in fields where the framework limits to an authorized value category
567
  --skip-title       Skip checking for bibliographic records without a title
568
  --skip-age         Skip checking for patrons with invalid age for category
569
  --skip-loop        Skip checking for relationships that form guarantor loops
570
  --help             Print usage information
354
571
355
* Items with undefined homebranch and/or holdingbranch
572
Note: If no options are provided, all tests will be run.
356
* Authorities with undefined authtypecodes/authority types
357
* Item types:
358
  * if item types are defined at item level (item-level_itypes=specific item),
359
    then items.itype must be set else biblioitems.itemtype must be set
360
  * Item types defined in items or biblioitems must be defined in the itemtypes table
361
* Invalid MARCXML in bibliographic records
362
* Patrons with invalid category types due to lower and upper age limits
363
573
364
=cut
574
=cut
365
- 

Return to bug 36027