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 |
- |
|
|