|
Lines 18-24
package Koha::Items;
Link Here
|
| 18 |
# along with Koha; if not, see <http://www.gnu.org/licenses>. |
18 |
# along with Koha; if not, see <http://www.gnu.org/licenses>. |
| 19 |
|
19 |
|
| 20 |
use Modern::Perl; |
20 |
use Modern::Perl; |
| 21 |
use Array::Utils qw( array_minus ); |
21 |
use Array::Utils qw( array_minus ); |
| 22 |
use List::MoreUtils qw( uniq ); |
22 |
use List::MoreUtils qw( uniq ); |
| 23 |
use Try::Tiny; |
23 |
use Try::Tiny; |
| 24 |
|
24 |
|
|
Lines 69-89
sub filter_by_for_hold {
Link Here
|
| 69 |
rule_value => 'not_allowed', |
69 |
rule_value => 'not_allowed', |
| 70 |
} |
70 |
} |
| 71 |
)->get_column('itemtype'); |
71 |
)->get_column('itemtype'); |
| 72 |
push @hold_not_allowed_itypes, Koha::ItemTypes->search({ notforloan => 1 })->get_column('itemtype'); |
72 |
push @hold_not_allowed_itypes, Koha::ItemTypes->search( { notforloan => 1 } )->get_column('itemtype'); |
| 73 |
|
73 |
|
| 74 |
my $params = { |
74 |
my $params = { |
| 75 |
itemlost => 0, |
75 |
itemlost => 0, |
| 76 |
withdrawn => 0, |
76 |
withdrawn => 0, |
| 77 |
notforloan => { '<=' => 0 }, # items with negative or zero notforloan value are holdable |
77 |
notforloan => { '<=' => 0 }, # items with negative or zero notforloan value are holdable |
| 78 |
( C4::Context->preference('AllowHoldsOnDamagedItems')? (): ( damaged => 0 ) ), |
78 |
( C4::Context->preference('AllowHoldsOnDamagedItems') ? () : ( damaged => 0 ) ), |
| 79 |
( C4::Context->only_my_library() ? ( homebranch => C4::Context::mybranch() ) : () ), |
79 |
( C4::Context->only_my_library() ? ( homebranch => C4::Context::mybranch() ) : () ), |
| 80 |
}; |
80 |
}; |
| 81 |
|
81 |
|
| 82 |
if ( C4::Context->preference("item-level_itypes") ) { |
82 |
if ( C4::Context->preference("item-level_itypes") ) { |
| 83 |
return $self->search( |
83 |
return $self->search( |
| 84 |
{ |
84 |
{ |
| 85 |
%$params, |
85 |
%$params, |
| 86 |
itype => { -not_in => \@hold_not_allowed_itypes }, |
86 |
itype => { -not_in => \@hold_not_allowed_itypes }, |
| 87 |
} |
87 |
} |
| 88 |
); |
88 |
); |
| 89 |
} else { |
89 |
} else { |
|
Lines 116-122
are honoured.
Link Here
|
| 116 |
=cut |
116 |
=cut |
| 117 |
|
117 |
|
| 118 |
sub filter_by_visible_in_opac { |
118 |
sub filter_by_visible_in_opac { |
| 119 |
my ($self, $params) = @_; |
119 |
my ( $self, $params ) = @_; |
| 120 |
|
120 |
|
| 121 |
my $patron = $params->{patron}; |
121 |
my $patron = $params->{patron}; |
| 122 |
|
122 |
|
|
Lines 128-141
sub filter_by_visible_in_opac {
Link Here
|
| 128 |
|
128 |
|
| 129 |
my $rules_params; |
129 |
my $rules_params; |
| 130 |
foreach my $field ( keys %$rules ) { |
130 |
foreach my $field ( keys %$rules ) { |
| 131 |
$rules_params->{'me.'.$field} = |
131 |
$rules_params->{ 'me.' . $field } = |
| 132 |
[ { '-not_in' => $rules->{$field} }, undef ]; |
132 |
[ { '-not_in' => $rules->{$field} }, undef ]; |
| 133 |
} |
133 |
} |
| 134 |
|
134 |
|
| 135 |
$result = $result->search( $rules_params ); |
135 |
$result = $result->search($rules_params); |
| 136 |
} |
136 |
} |
| 137 |
|
137 |
|
| 138 |
if (C4::Context->preference('hidelostitems')) { |
138 |
if ( C4::Context->preference('hidelostitems') ) { |
| 139 |
$result = $result->filter_out_lost; |
139 |
$result = $result->filter_out_lost; |
| 140 |
} |
140 |
} |
| 141 |
|
141 |
|
|
Lines 155-164
sub filter_out_lost {
Link Here
|
| 155 |
|
155 |
|
| 156 |
my $params = { itemlost => 0 }; |
156 |
my $params = { itemlost => 0 }; |
| 157 |
|
157 |
|
| 158 |
return $self->search( $params ); |
158 |
return $self->search($params); |
| 159 |
} |
159 |
} |
| 160 |
|
160 |
|
| 161 |
|
|
|
| 162 |
=head3 move_to_biblio |
161 |
=head3 move_to_biblio |
| 163 |
|
162 |
|
| 164 |
$items->move_to_biblio($to_biblio); |
163 |
$items->move_to_biblio($to_biblio); |
|
Lines 175-181
sub move_to_biblio {
Link Here
|
| 175 |
$biblionumbers->{ $item->biblionumber } = 1; |
174 |
$biblionumbers->{ $item->biblionumber } = 1; |
| 176 |
$item->move_to_biblio( $to_biblio, { skip_record_index => 1 } ); |
175 |
$item->move_to_biblio( $to_biblio, { skip_record_index => 1 } ); |
| 177 |
} |
176 |
} |
| 178 |
my $indexer = Koha::SearchEngine::Indexer->new({ index => $Koha::SearchEngine::BIBLIOS_INDEX }); |
177 |
my $indexer = Koha::SearchEngine::Indexer->new( { index => $Koha::SearchEngine::BIBLIOS_INDEX } ); |
| 179 |
for my $biblionumber ( keys %{$biblionumbers} ) { |
178 |
for my $biblionumber ( keys %{$biblionumbers} ) { |
| 180 |
$indexer->index_records( $biblionumber, "specialUpdate", "biblioserver" ); |
179 |
$indexer->index_records( $biblionumber, "specialUpdate", "biblioserver" ); |
| 181 |
} |
180 |
} |
|
Lines 251-398
sub batch_update {
Link Here
|
| 251 |
my $sort2 = $params->{sort2}; |
250 |
my $sort2 = $params->{sort2}; |
| 252 |
my $callback = $params->{callback}; |
251 |
my $callback = $params->{callback}; |
| 253 |
|
252 |
|
| 254 |
my (@modified_itemnumbers, $modified_fields); |
253 |
my ( @modified_itemnumbers, $modified_fields ); |
| 255 |
my $i; |
254 |
my $i; |
| 256 |
my $schema = Koha::Database->new->schema; |
255 |
my $schema = Koha::Database->new->schema; |
| 257 |
while ( my $item = $self->next ) { |
256 |
while ( my $item = $self->next ) { |
| 258 |
|
257 |
|
| 259 |
try {$schema->txn_do(sub { |
258 |
try { |
| 260 |
my $modified_holds_priority = 0; |
259 |
$schema->txn_do( |
| 261 |
my $item_returned = 0; |
260 |
sub { |
| 262 |
my $sort_fields_modified = 0; |
261 |
my $modified_holds_priority = 0; |
| 263 |
if ( defined $exclude_from_local_holds_priority ) { |
262 |
my $item_returned = 0; |
| 264 |
if(!defined $item->exclude_from_local_holds_priority || $item->exclude_from_local_holds_priority != $exclude_from_local_holds_priority) { |
263 |
my $sort_fields_modified = 0; |
| 265 |
$item->exclude_from_local_holds_priority($exclude_from_local_holds_priority)->store; |
264 |
if ( defined $exclude_from_local_holds_priority ) { |
| 266 |
$modified_holds_priority = 1; |
265 |
if ( !defined $item->exclude_from_local_holds_priority |
| 267 |
} |
266 |
|| $item->exclude_from_local_holds_priority != $exclude_from_local_holds_priority ) |
| 268 |
} |
267 |
{ |
| 269 |
if ( defined $sort1 || defined $sort2 ) { |
268 |
$item->exclude_from_local_holds_priority($exclude_from_local_holds_priority)->store; |
| 270 |
$item->sort1($sort1)->store if $sort1 ne ""; |
269 |
$modified_holds_priority = 1; |
| 271 |
$sort_fields_modified++ if $sort1 ne ""; |
270 |
} |
| 272 |
$item->sort2($sort2)->store if $sort2 ne ""; |
271 |
} |
| 273 |
$sort_fields_modified++ if $sort2 ne ""; |
272 |
if ( defined $sort1 || defined $sort2 ) { |
| 274 |
} |
273 |
$item->sort1($sort1)->store if $sort1 ne ""; |
|
|
274 |
$sort_fields_modified++ if $sort1 ne ""; |
| 275 |
$item->sort2($sort2)->store if $sort2 ne ""; |
| 276 |
$sort_fields_modified++ if $sort2 ne ""; |
| 277 |
} |
| 275 |
|
278 |
|
| 276 |
my $modified = 0; |
279 |
my $modified = 0; |
| 277 |
my $new_values = {%$new_values}; # Don't modify the original |
280 |
my $new_values = {%$new_values}; # Don't modify the original |
| 278 |
|
281 |
|
| 279 |
my $old_values = $item->unblessed; |
282 |
my $old_values = $item->unblessed; |
| 280 |
if ( $item->more_subfields_xml ) { |
283 |
if ( $item->more_subfields_xml ) { |
| 281 |
$old_values = { |
284 |
$old_values = { |
| 282 |
%$old_values, |
285 |
%$old_values, |
| 283 |
%{$item->additional_attributes->to_hashref}, |
286 |
%{ $item->additional_attributes->to_hashref }, |
| 284 |
}; |
287 |
}; |
| 285 |
} |
288 |
} |
|
|
289 |
|
| 290 |
for my $attr ( keys %$regex_mod ) { |
| 291 |
my $old_value = $old_values->{$attr}; |
| 286 |
|
292 |
|
| 287 |
for my $attr ( keys %$regex_mod ) { |
293 |
next unless $old_value; |
| 288 |
my $old_value = $old_values->{$attr}; |
|
|
| 289 |
|
294 |
|
| 290 |
next unless $old_value; |
295 |
my $value = apply_regex( |
|
|
296 |
{ |
| 297 |
%{ $regex_mod->{$attr} }, |
| 298 |
value => $old_value, |
| 299 |
} |
| 300 |
); |
| 291 |
|
301 |
|
| 292 |
my $value = apply_regex( |
302 |
$new_values->{$attr} = $value; |
| 293 |
{ |
|
|
| 294 |
%{ $regex_mod->{$attr} }, |
| 295 |
value => $old_value, |
| 296 |
} |
303 |
} |
| 297 |
); |
|
|
| 298 |
|
304 |
|
| 299 |
$new_values->{$attr} = $value; |
305 |
for my $attribute ( keys %$new_values ) { |
| 300 |
} |
306 |
next if $attribute eq 'more_subfields_xml'; # Already counted before |
| 301 |
|
307 |
|
| 302 |
for my $attribute ( keys %$new_values ) { |
308 |
my $old = $old_values->{$attribute}; |
| 303 |
next if $attribute eq 'more_subfields_xml'; # Already counted before |
309 |
my $new = $new_values->{$attribute}; |
|
|
310 |
$modified++ |
| 311 |
if ( defined $old xor defined $new ) |
| 312 |
|| ( defined $old && defined $new && $new ne $old ); |
| 313 |
} |
| 304 |
|
314 |
|
| 305 |
my $old = $old_values->{$attribute}; |
315 |
{ # Dealing with more_subfields_xml |
| 306 |
my $new = $new_values->{$attribute}; |
|
|
| 307 |
$modified++ |
| 308 |
if ( defined $old xor defined $new ) |
| 309 |
|| ( defined $old && defined $new && $new ne $old ); |
| 310 |
} |
| 311 |
|
316 |
|
| 312 |
{ # Dealing with more_subfields_xml |
317 |
my $frameworkcode = $item->biblio->frameworkcode; |
| 313 |
|
318 |
my $tagslib = C4::Biblio::GetMarcStructure( 1, $frameworkcode, { unsafe => 1 } ); |
| 314 |
my $frameworkcode = $item->biblio->frameworkcode; |
319 |
my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField("items.itemnumber"); |
| 315 |
my $tagslib = C4::Biblio::GetMarcStructure( 1, $frameworkcode, { unsafe => 1 }); |
|
|
| 316 |
my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" ); |
| 317 |
|
| 318 |
my @more_subfield_tags = map { |
| 319 |
( |
| 320 |
ref($_) |
| 321 |
&& %$_ |
| 322 |
&& !$_->{kohafield} # Get subfields that are not mapped |
| 323 |
) |
| 324 |
? $_->{tagsubfield} |
| 325 |
: () |
| 326 |
} values %{ $tagslib->{$itemtag} }; |
| 327 |
|
| 328 |
my $more_subfields_xml = Koha::Item::Attributes->new( |
| 329 |
{ |
| 330 |
map { |
| 331 |
exists $new_values->{$_} ? ( $_ => $new_values->{$_} ) |
| 332 |
: exists $old_values->{$_} |
| 333 |
? ( $_ => $old_values->{$_} ) |
| 334 |
: () |
| 335 |
} @more_subfield_tags |
| 336 |
} |
| 337 |
)->to_marcxml($frameworkcode); |
| 338 |
|
320 |
|
| 339 |
$new_values->{more_subfields_xml} = $more_subfields_xml; |
321 |
my @more_subfield_tags = map { |
|
|
322 |
( |
| 323 |
ref($_) |
| 324 |
&& %$_ |
| 325 |
&& !$_->{kohafield} # Get subfields that are not mapped |
| 326 |
) |
| 327 |
? $_->{tagsubfield} |
| 328 |
: () |
| 329 |
} values %{ $tagslib->{$itemtag} }; |
| 340 |
|
330 |
|
| 341 |
delete $new_values->{$_} for @more_subfield_tags; # Clean the hash |
331 |
my $more_subfields_xml = Koha::Item::Attributes->new( |
|
|
332 |
{ |
| 333 |
map { |
| 334 |
exists $new_values->{$_} ? ( $_ => $new_values->{$_} ) |
| 335 |
: exists $old_values->{$_} ? ( $_ => $old_values->{$_} ) |
| 336 |
: () |
| 337 |
} @more_subfield_tags |
| 338 |
} |
| 339 |
)->to_marcxml($frameworkcode); |
| 342 |
|
340 |
|
| 343 |
} |
341 |
$new_values->{more_subfields_xml} = $more_subfields_xml; |
| 344 |
|
342 |
|
| 345 |
if ( $modified ) { |
343 |
delete $new_values->{$_} for @more_subfield_tags; # Clean the hash |
| 346 |
my $itemlost_pre = $item->itemlost; |
|
|
| 347 |
$item->set($new_values)->store({skip_record_index => 1}); |
| 348 |
|
344 |
|
| 349 |
C4::Circulation::LostItem( |
345 |
} |
| 350 |
$item->itemnumber, 'batchmod', undef, |
346 |
|
| 351 |
{ skip_record_index => 1 } |
347 |
if ($modified) { |
| 352 |
) if $item->itemlost |
348 |
my $itemlost_pre = $item->itemlost; |
| 353 |
and not $itemlost_pre; |
349 |
$item->set($new_values)->store( { skip_record_index => 1 } ); |
| 354 |
} |
350 |
|
| 355 |
if ( $mark_items_returned ){ |
351 |
C4::Circulation::LostItem( |
| 356 |
my $issue = $item->checkout; |
352 |
$item->itemnumber, 'batchmod', undef, |
| 357 |
if( $issue ){ |
353 |
{ skip_record_index => 1 } |
| 358 |
$item_returned = 1; |
354 |
) |
| 359 |
C4::Circulation::MarkIssueReturned( |
355 |
if $item->itemlost |
| 360 |
$issue->borrowernumber, |
356 |
and not $itemlost_pre; |
| 361 |
$item->itemnumber, |
357 |
} |
| 362 |
undef, |
358 |
if ($mark_items_returned) { |
| 363 |
$issue->patron->privacy, |
359 |
my $issue = $item->checkout; |
| 364 |
{ |
360 |
if ($issue) { |
| 365 |
skip_record_index => 1, |
361 |
$item_returned = 1; |
| 366 |
skip_holds_queue => 1, |
362 |
C4::Circulation::MarkIssueReturned( |
|
|
363 |
$issue->borrowernumber, |
| 364 |
$item->itemnumber, |
| 365 |
undef, |
| 366 |
$issue->patron->privacy, |
| 367 |
{ |
| 368 |
skip_record_index => 1, |
| 369 |
skip_holds_queue => 1, |
| 370 |
} |
| 371 |
); |
| 367 |
} |
372 |
} |
| 368 |
); |
373 |
} |
| 369 |
} |
|
|
| 370 |
} |
| 371 |
|
374 |
|
| 372 |
push @modified_itemnumbers, $item->itemnumber |
375 |
push @modified_itemnumbers, $item->itemnumber |
| 373 |
if $modified || $modified_holds_priority || $item_returned || $sort_fields_modified; |
376 |
if $modified || $modified_holds_priority || $item_returned || $sort_fields_modified; |
| 374 |
$modified_fields += $modified + $modified_holds_priority + $item_returned + $sort_fields_modified; |
377 |
$modified_fields += $modified + $modified_holds_priority + $item_returned + $sort_fields_modified; |
| 375 |
})} |
378 |
} |
| 376 |
catch { |
379 |
) |
|
|
380 |
} catch { |
| 377 |
warn $_ |
381 |
warn $_ |
| 378 |
}; |
382 |
}; |
| 379 |
|
383 |
|
| 380 |
if ( $callback ) { |
384 |
if ($callback) { |
| 381 |
$callback->(++$i); |
385 |
$callback->( ++$i ); |
| 382 |
} |
386 |
} |
| 383 |
} |
387 |
} |
| 384 |
|
388 |
|
| 385 |
if (@modified_itemnumbers) { |
389 |
if (@modified_itemnumbers) { |
| 386 |
my @biblionumbers = uniq( |
390 |
my @biblionumbers = |
| 387 |
Koha::Items->search( { itemnumber => \@modified_itemnumbers } ) |
391 |
uniq( Koha::Items->search( { itemnumber => \@modified_itemnumbers } )->get_column('biblionumber') ); |
| 388 |
->get_column('biblionumber')); |
|
|
| 389 |
|
392 |
|
| 390 |
if ( @biblionumbers ) { |
393 |
if (@biblionumbers) { |
| 391 |
my $indexer = Koha::SearchEngine::Indexer->new( |
394 |
my $indexer = Koha::SearchEngine::Indexer->new( { index => $Koha::SearchEngine::BIBLIOS_INDEX } ); |
| 392 |
{ index => $Koha::SearchEngine::BIBLIOS_INDEX } ); |
|
|
| 393 |
|
395 |
|
| 394 |
$indexer->index_records( \@biblionumbers, 'specialUpdate', |
396 |
$indexer->index_records( |
| 395 |
"biblioserver", undef ); |
397 |
\@biblionumbers, 'specialUpdate', |
|
|
398 |
"biblioserver", undef |
| 399 |
); |
| 396 |
} |
400 |
} |
| 397 |
} |
401 |
} |
| 398 |
|
402 |
|
|
Lines 400-431
sub batch_update {
Link Here
|
| 400 |
} |
404 |
} |
| 401 |
|
405 |
|
| 402 |
sub apply_regex { |
406 |
sub apply_regex { |
|
|
407 |
|
| 403 |
# FIXME Should be moved outside of Koha::Items |
408 |
# FIXME Should be moved outside of Koha::Items |
| 404 |
# FIXME This is nearly identical to Koha::SimpleMARC::_modify_values |
409 |
# FIXME This is nearly identical to Koha::SimpleMARC::_modify_values |
| 405 |
my ($params) = @_; |
410 |
my ($params) = @_; |
| 406 |
my $search = $params->{search}; |
411 |
my $search = $params->{search}; |
| 407 |
my $replace = $params->{replace}; |
412 |
my $replace = $params->{replace}; |
| 408 |
my $modifiers = $params->{modifiers} || q{}; |
413 |
my $modifiers = $params->{modifiers} || q{}; |
| 409 |
my $value = $params->{value}; |
414 |
my $value = $params->{value}; |
| 410 |
|
415 |
|
| 411 |
$replace =~ s/"/\\"/g; # Protection from embedded code |
416 |
$replace =~ s/"/\\"/g; # Protection from embedded code |
| 412 |
$replace = '"' . $replace . '"'; # Put in a string for /ee |
417 |
$replace = '"' . $replace . '"'; # Put in a string for /ee |
| 413 |
my @available_modifiers = qw( i g ); |
418 |
my @available_modifiers = qw( i g ); |
| 414 |
my $retained_modifiers = q||; |
419 |
my $retained_modifiers = q||; |
| 415 |
for my $modifier ( split //, $modifiers ) { |
420 |
for my $modifier ( split //, $modifiers ) { |
| 416 |
$retained_modifiers .= $modifier |
421 |
$retained_modifiers .= $modifier |
| 417 |
if grep { /$modifier/ } @available_modifiers; |
422 |
if grep { /$modifier/ } @available_modifiers; |
| 418 |
} |
423 |
} |
| 419 |
if ( $retained_modifiers =~ m/^(ig|gi)$/ ) { |
424 |
if ( $retained_modifiers =~ m/^(ig|gi)$/ ) { |
| 420 |
$value =~ s/$search/$replace/igee; |
425 |
$value =~ s/$search/$replace/igee; |
| 421 |
} |
426 |
} elsif ( $retained_modifiers eq 'i' ) { |
| 422 |
elsif ( $retained_modifiers eq 'i' ) { |
|
|
| 423 |
$value =~ s/$search/$replace/iee; |
427 |
$value =~ s/$search/$replace/iee; |
| 424 |
} |
428 |
} elsif ( $retained_modifiers eq 'g' ) { |
| 425 |
elsif ( $retained_modifiers eq 'g' ) { |
|
|
| 426 |
$value =~ s/$search/$replace/gee; |
429 |
$value =~ s/$search/$replace/gee; |
| 427 |
} |
430 |
} else { |
| 428 |
else { |
|
|
| 429 |
$value =~ s/$search/$replace/ee; |
431 |
$value =~ s/$search/$replace/ee; |
| 430 |
} |
432 |
} |
| 431 |
|
433 |
|
|
Lines 441-453
Search and sort items in a specific order, depending if serials are present or n
Link Here
|
| 441 |
=cut |
443 |
=cut |
| 442 |
|
444 |
|
| 443 |
sub search_ordered { |
445 |
sub search_ordered { |
| 444 |
my ($self, $params, $attributes) = @_; |
446 |
my ( $self, $params, $attributes ) = @_; |
| 445 |
|
447 |
|
| 446 |
$self = $self->search($params, $attributes); |
448 |
$self = $self->search( $params, $attributes ); |
| 447 |
|
449 |
|
| 448 |
my @biblionumbers = uniq $self->get_column('biblionumber'); |
450 |
my @biblionumbers = uniq $self->get_column('biblionumber'); |
| 449 |
|
451 |
|
| 450 |
if ( scalar ( @biblionumbers ) == 1 |
452 |
if ( scalar(@biblionumbers) == 1 |
| 451 |
&& Koha::Biblios->find( $biblionumbers[0] )->serial ) |
453 |
&& Koha::Biblios->find( $biblionumbers[0] )->serial ) |
| 452 |
{ |
454 |
{ |
| 453 |
return $self->search( |
455 |
return $self->search( |
|
Lines 465-471
sub search_ordered {
Link Here
|
| 465 |
'homebranch.branchname', |
467 |
'homebranch.branchname', |
| 466 |
'me.enumchron', |
468 |
'me.enumchron', |
| 467 |
\"LPAD( me.copynumber, 8, '0' )", |
469 |
\"LPAD( me.copynumber, 8, '0' )", |
| 468 |
{-desc => 'me.dateaccessioned'} |
470 |
{ -desc => 'me.dateaccessioned' } |
| 469 |
], |
471 |
], |
| 470 |
join => ['homebranch'] |
472 |
join => ['homebranch'] |
| 471 |
} |
473 |
} |
| 472 |
- |
|
|