Bug 25426: Allow return policy to be selected via syspref and not just home library
[koha.git] / C4 / Items.pm
1 package C4::Items;
2
3 # Copyright 2007 LibLime, Inc.
4 # Parts Copyright Biblibre 2010
5 #
6 # This file is part of Koha.
7 #
8 # Koha is free software; you can redistribute it and/or modify it
9 # under the terms of the GNU General Public License as published by
10 # the Free Software Foundation; either version 3 of the License, or
11 # (at your option) any later version.
12 #
13 # Koha is distributed in the hope that it will be useful, but
14 # WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
17 #
18 # You should have received a copy of the GNU General Public License
19 # along with Koha; if not, see <http://www.gnu.org/licenses>.
20
21 use Modern::Perl;
22
23 our (@ISA, @EXPORT_OK);
24 BEGIN {
25     require Exporter;
26     @ISA = qw(Exporter);
27
28     @EXPORT_OK = qw(
29         AddItemFromMarc
30         AddItemBatchFromMarc
31         ModItemFromMarc
32         Item2Marc
33         ModDateLastSeen
34         ModItemTransfer
35         CheckItemPreSave
36         GetItemsForInventory
37         get_hostitemnumbers_of
38         GetMarcItem
39         CartToShelf
40         GetAnalyticsCount
41         SearchItems
42         PrepareItemrecordDisplay
43         ToggleNewStatus
44     );
45 }
46
47 use Carp qw( croak );
48 use C4::Context;
49 use C4::Koha;
50 use C4::Biblio qw( GetMarcStructure TransformMarcToKoha );
51 use MARC::Record;
52 use C4::ClassSource qw( GetClassSort GetClassSources GetClassSource );
53 use C4::Log qw( logaction );
54 use List::MoreUtils qw( any );
55 use DateTime::Format::MySQL;
56                   # debugging; so please don't remove this
57
58 use Koha::AuthorisedValues;
59 use Koha::DateUtils qw( dt_from_string );
60 use Koha::Database;
61
62 use Koha::Biblios;
63 use Koha::Biblioitems;
64 use Koha::Items;
65 use Koha::ItemTypes;
66 use Koha::SearchEngine;
67 use Koha::SearchEngine::Indexer;
68 use Koha::SearchEngine::Search;
69 use Koha::Libraries;
70
71 =head1 NAME
72
73 C4::Items - item management functions
74
75 =head1 DESCRIPTION
76
77 This module contains an API for manipulating item 
78 records in Koha, and is used by cataloguing, circulation,
79 acquisitions, and serials management.
80
81 # FIXME This POD is not up-to-date
82 A Koha item record is stored in two places: the
83 items table and embedded in a MARC tag in the XML
84 version of the associated bib record in C<biblioitems.marcxml>.
85 This is done to allow the item information to be readily
86 indexed (e.g., by Zebra), but means that each item
87 modification transaction must keep the items table
88 and the MARC XML in sync at all times.
89
90 The items table will be considered authoritative.  In other
91 words, if there is ever a discrepancy between the items
92 table and the MARC XML, the items table should be considered
93 accurate.
94
95 =head1 HISTORICAL NOTE
96
97 Most of the functions in C<C4::Items> were originally in
98 the C<C4::Biblio> module.
99
100 =head1 CORE EXPORTED FUNCTIONS
101
102 The following functions are meant for use by users
103 of C<C4::Items>
104
105 =cut
106
107 =head2 CartToShelf
108
109   CartToShelf($itemnumber);
110
111 Set the current shelving location of the item record
112 to its stored permanent shelving location.  This is
113 primarily used to indicate when an item whose current
114 location is a special processing ('PROC') or shelving cart
115 ('CART') location is back in the stacks.
116
117 =cut
118
119 sub CartToShelf {
120     my ( $itemnumber ) = @_;
121
122     unless ( $itemnumber ) {
123         croak "FAILED CartToShelf() - no itemnumber supplied";
124     }
125
126     my $item = Koha::Items->find($itemnumber);
127     if ( $item->location eq 'CART' ) {
128         $item->location($item->permanent_location)->store;
129     }
130 }
131
132 =head2 AddItemFromMarc
133
134   my ($biblionumber, $biblioitemnumber, $itemnumber) 
135       = AddItemFromMarc($source_item_marc, $biblionumber[, $params]);
136
137 Given a MARC::Record object containing an embedded item
138 record and a biblionumber, create a new item record.
139
140 The final optional parameter, C<$params>, may contain
141 'skip_record_index' key, which relayed down to Koha::Item/store,
142 there it prevents calling of index_records,
143 which takes most of the time in batch adds/deletes: index_records
144 to be called later in C<additem.pl> after the whole loop.
145
146 You may also optionally pass biblioitemnumber in the params hash to
147 boost performance of inserts by preventing a lookup in Koha::Item.
148
149 $params:
150     skip_record_index => 1|0
151     biblioitemnumber => $biblioitemnumber
152
153 =cut
154
155 sub AddItemFromMarc {
156     my $source_item_marc = shift;
157     my $biblionumber     = shift;
158     my $params           = @_ ? shift : {};
159
160     my $dbh = C4::Context->dbh;
161
162     # parse item hash from MARC
163     my $frameworkcode = C4::Biblio::GetFrameworkCode($biblionumber);
164     my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
165     my $localitemmarc = MARC::Record->new;
166     $localitemmarc->append_fields( $source_item_marc->field($itemtag) );
167
168     my $item_values = C4::Biblio::TransformMarcToKoha({ record => $localitemmarc, limit_table => 'items' });
169     my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
170     $item_values->{more_subfields_xml} = _get_unlinked_subfields_xml($unlinked_item_subfields);
171     $item_values->{biblionumber} = $biblionumber;
172     $item_values->{biblioitemnumber} = $params->{biblioitemnumber};
173     $item_values->{cn_source} = delete $item_values->{'items.cn_source'}; # Because of C4::Biblio::_disambiguate
174     $item_values->{cn_sort}   = delete $item_values->{'items.cn_sort'};   # Because of C4::Biblio::_disambiguate
175     my $item = Koha::Item->new( $item_values )->store({ skip_record_index => $params->{skip_record_index} });
176     return ( $item->biblionumber, $item->biblioitemnumber, $item->itemnumber );
177 }
178
179 =head2 AddItemBatchFromMarc
180
181   ($itemnumber_ref, $error_ref) = AddItemBatchFromMarc($record, 
182              $biblionumber, $biblioitemnumber, $frameworkcode);
183
184 Efficiently create item records from a MARC biblio record with
185 embedded item fields.  This routine is suitable for batch jobs.
186
187 This API assumes that the bib record has already been
188 saved to the C<biblio> and C<biblioitems> tables.  It does
189 not expect that C<biblio_metadata.metadata> is populated, but it
190 will do so via a call to ModBibiloMarc.
191
192 The goal of this API is to have a similar effect to using AddBiblio
193 and AddItems in succession, but without inefficient repeated
194 parsing of the MARC XML bib record.
195
196 This function returns an arrayref of new itemsnumbers and an arrayref of item
197 errors encountered during the processing.  Each entry in the errors
198 list is a hashref containing the following keys:
199
200 =over
201
202 =item item_sequence
203
204 Sequence number of original item tag in the MARC record.
205
206 =item item_barcode
207
208 Item barcode, provide to assist in the construction of
209 useful error messages.
210
211 =item error_code
212
213 Code representing the error condition.  Can be 'duplicate_barcode',
214 'invalid_homebranch', or 'invalid_holdingbranch'.
215
216 =item error_information
217
218 Additional information appropriate to the error condition.
219
220 =back
221
222 =cut
223
224 sub AddItemBatchFromMarc {
225     my ($record, $biblionumber, $biblioitemnumber, $frameworkcode) = @_;
226     my @itemnumbers = ();
227     my @errors = ();
228     my $dbh = C4::Context->dbh;
229
230     # We modify the record, so lets work on a clone so we don't change the
231     # original.
232     $record = $record->clone();
233     # loop through the item tags and start creating items
234     my @bad_item_fields = ();
235     my ($itemtag, $itemsubfield) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
236     my $item_sequence_num = 0;
237     ITEMFIELD: foreach my $item_field ($record->field($itemtag)) {
238         $item_sequence_num++;
239         # we take the item field and stick it into a new
240         # MARC record -- this is required so far because (FIXME)
241         # TransformMarcToKoha requires a MARC::Record, not a MARC::Field
242         # and there is no TransformMarcFieldToKoha
243         my $temp_item_marc = MARC::Record->new();
244         $temp_item_marc->append_fields($item_field);
245     
246         # add biblionumber and biblioitemnumber
247         my $item = TransformMarcToKoha({ record => $temp_item_marc, limit_table => 'items' });
248         my $unlinked_item_subfields = _get_unlinked_item_subfields($temp_item_marc, $frameworkcode);
249         $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
250         $item->{'biblionumber'} = $biblionumber;
251         $item->{'biblioitemnumber'} = $biblioitemnumber;
252         $item->{cn_source} = delete $item->{'items.cn_source'}; # Because of C4::Biblio::_disambiguate
253         $item->{cn_sort}   = delete $item->{'items.cn_sort'};   # Because of C4::Biblio::_disambiguate
254
255         # check for duplicate barcode
256         my %item_errors = CheckItemPreSave($item);
257         if (%item_errors) {
258             push @errors, _repack_item_errors($item_sequence_num, $item, \%item_errors);
259             push @bad_item_fields, $item_field;
260             next ITEMFIELD;
261         }
262
263         my $item_object = Koha::Item->new($item)->store;
264         push @itemnumbers, $item_object->itemnumber; # FIXME not checking error
265
266         logaction("CATALOGUING", "ADD", $item_object->itemnumber, "item") if C4::Context->preference("CataloguingLog");
267
268         my $new_item_marc = _marc_from_item_hash($item_object->unblessed, $frameworkcode, $unlinked_item_subfields);
269         $item_field->replace_with($new_item_marc->field($itemtag));
270     }
271
272     # remove any MARC item fields for rejected items
273     foreach my $item_field (@bad_item_fields) {
274         $record->delete_field($item_field);
275     }
276
277     return (\@itemnumbers, \@errors);
278 }
279
280 =head2 ModItemFromMarc
281
282 my $item = ModItemFromMarc($item_marc, $biblionumber, $itemnumber[, $params]);
283
284 The final optional parameter, C<$params>, expected to contain
285 'skip_record_index' key, which relayed down to Koha::Item/store,
286 there it prevents calling of index_records,
287 which takes most of the time in batch adds/deletes: index_records better
288 to be called later in C<additem.pl> after the whole loop.
289
290 $params:
291     skip_record_index => 1|0
292
293 =cut
294
295 sub ModItemFromMarc {
296     my ( $item_marc, $biblionumber, $itemnumber, $params ) = @_;
297
298     my $frameworkcode = C4::Biblio::GetFrameworkCode($biblionumber);
299     my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
300
301     my $localitemmarc = MARC::Record->new;
302     $localitemmarc->append_fields( $item_marc->field($itemtag) );
303     my $item_object = Koha::Items->find($itemnumber);
304     my $item = TransformMarcToKoha({ record => $localitemmarc, limit_table => 'items' });
305
306     # When importing items we blank this column, we need to set it to the existing value
307     # to prevent it being blanked by set_or_blank
308     $item->{onloan} = $item_object->onloan if( $item_object->onloan && !defined $item->{onloan} );
309
310     my ( $perm_loc_tag, $perm_loc_subfield ) = C4::Biblio::GetMarcFromKohaField( "items.permanent_location" );
311     my $has_permanent_location = defined $perm_loc_tag && defined $item_marc->subfield( $perm_loc_tag, $perm_loc_subfield );
312
313     # Retrieving the values for the fields that are not linked
314     my @mapped_fields = Koha::MarcSubfieldStructures->search(
315         {
316             frameworkcode => $frameworkcode,
317             kohafield     => { -like => "items.%" }
318         }
319     )->get_column('kohafield');
320     for my $c ( $item_object->_result->result_source->columns ) {
321         next if grep { "items.$c" eq $_ } @mapped_fields;
322         $item->{$c} = $item_object->$c;
323     }
324
325     $item->{cn_source} = delete $item->{'items.cn_source'}; # Because of C4::Biblio::_disambiguate
326     delete $item->{'items.cn_sort'};   # Because of C4::Biblio::_disambiguate
327     $item->{itemnumber} = $itemnumber;
328     $item->{biblionumber} = $biblionumber;
329
330     my $existing_cn_sort = $item_object->cn_sort; # set_or_blank will reset cn_sort to undef as we are not passing it
331                                                   # We rely on Koha::Item->store to modify it if itemcallnumber or cn_source is modified
332     $item_object = $item_object->set_or_blank($item);
333     $item_object->cn_sort($existing_cn_sort); # Resetting to the existing value
334
335     $item_object->make_column_dirty('permanent_location') if $has_permanent_location;
336
337     my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
338     $item_object->more_subfields_xml(_get_unlinked_subfields_xml($unlinked_item_subfields));
339     $item_object->store({ skip_record_index => $params->{skip_record_index} });
340
341     return $item_object->unblessed;
342 }
343
344 =head2 ModItemTransfer
345
346   ModItemTransfer($itemnumber, $frombranch, $tobranch, $trigger, [$params]);
347
348 Marks an item as being transferred from one branch to another and records the trigger.
349
350 The last optional parameter allows for passing skip_record_index through to the items store call.
351
352 =cut
353
354 sub ModItemTransfer {
355     my ( $itemnumber, $frombranch, $tobranch, $trigger, $params ) = @_;
356
357     my $dbh = C4::Context->dbh;
358     my $item = Koha::Items->find( $itemnumber );
359
360     # NOTE: This retains the existing hard coded behaviour by ignoring transfer limits
361     # and always replacing any existing transfers. (In theory, calls to ModItemTransfer
362     # will have been preceded by a check of branch transfer limits)
363     my $to_library = Koha::Libraries->find($tobranch);
364     my $transfer = $item->request_transfer(
365         {
366             to            => $to_library,
367             reason        => $trigger,
368             ignore_limits => 1,
369             replace       => 1
370         }
371     );
372
373     # Immediately set the item to in transit if it is checked in
374     if ( !$item->checkout ) {
375         $item->holdingbranch($frombranch)->store(
376             {
377                 log_action        => 0,
378                 skip_record_index => $params->{skip_record_index}
379             }
380         );
381         $transfer->transit;
382     }
383
384     return;
385 }
386
387 =head2 ModDateLastSeen
388
389 ModDateLastSeen( $itemnumber, $leave_item_lost, $params );
390
391 Mark item as seen. Is called when an item is issued, returned or manually marked during inventory/stocktaking.
392 C<$itemnumber> is the item number
393 C<$leave_item_lost> determines if a lost item will be found or remain lost
394
395 The last optional parameter allows for passing skip_record_index through to the items store call.
396
397 =cut
398
399 sub ModDateLastSeen {
400     my ( $itemnumber, $leave_item_lost, $params ) = @_;
401
402     my $item = Koha::Items->find($itemnumber);
403     $item->datelastseen(dt_from_string->ymd);
404     my $log = $item->itemlost && !$leave_item_lost ? 1 : 0; # If item was lost, record the change to the item
405     $item->itemlost(0) unless $leave_item_lost;
406     $item->store({ log_action => $log, skip_record_index => $params->{skip_record_index}, skip_holds_queue => $params->{skip_holds_queue} });
407 }
408
409 =head2 CheckItemPreSave
410
411     my $item_ref = TransformMarcToKoha({ record => $marc, limit_table => 'items' });
412     # do stuff
413     my %errors = CheckItemPreSave($item_ref);
414     if (exists $errors{'duplicate_barcode'}) {
415         print "item has duplicate barcode: ", $errors{'duplicate_barcode'}, "\n";
416     } elsif (exists $errors{'invalid_homebranch'}) {
417         print "item has invalid home branch: ", $errors{'invalid_homebranch'}, "\n";
418     } elsif (exists $errors{'invalid_holdingbranch'}) {
419         print "item has invalid holding branch: ", $errors{'invalid_holdingbranch'}, "\n";
420     } else {
421         print "item is OK";
422     }
423
424 Given a hashref containing item fields, determine if it can be
425 inserted or updated in the database.  Specifically, checks for
426 database integrity issues, and returns a hash containing any
427 of the following keys, if applicable.
428
429 =over 2
430
431 =item duplicate_barcode
432
433 Barcode, if it duplicates one already found in the database.
434
435 =item invalid_homebranch
436
437 Home branch, if not defined in branches table.
438
439 =item invalid_holdingbranch
440
441 Holding branch, if not defined in branches table.
442
443 =back
444
445 This function does NOT implement any policy-related checks,
446 e.g., whether current operator is allowed to save an
447 item that has a given branch code.
448
449 =cut
450
451 sub CheckItemPreSave {
452     my $item_ref = shift;
453
454     my %errors = ();
455
456     # check for duplicate barcode
457     if (exists $item_ref->{'barcode'} and defined $item_ref->{'barcode'}) {
458         my $existing_item= Koha::Items->find({barcode => $item_ref->{'barcode'}});
459         if ($existing_item) {
460             if (!exists $item_ref->{'itemnumber'}                       # new item
461                 or $item_ref->{'itemnumber'} != $existing_item->itemnumber) { # existing item
462                 $errors{'duplicate_barcode'} = $item_ref->{'barcode'};
463             }
464         }
465     }
466
467     # check for valid home branch
468     if (exists $item_ref->{'homebranch'} and defined $item_ref->{'homebranch'}) {
469         my $home_library = Koha::Libraries->find( $item_ref->{homebranch} );
470         unless (defined $home_library) {
471             $errors{'invalid_homebranch'} = $item_ref->{'homebranch'};
472         }
473     }
474
475     # check for valid holding branch
476     if (exists $item_ref->{'holdingbranch'} and defined $item_ref->{'holdingbranch'}) {
477         my $holding_library = Koha::Libraries->find( $item_ref->{holdingbranch} );
478         unless (defined $holding_library) {
479             $errors{'invalid_holdingbranch'} = $item_ref->{'holdingbranch'};
480         }
481     }
482
483     return %errors;
484
485 }
486
487 =head1 EXPORTED SPECIAL ACCESSOR FUNCTIONS
488
489 The following functions provide various ways of 
490 getting an item record, a set of item records, or
491 lists of authorized values for certain item fields.
492
493 =cut
494
495 =head2 GetItemsForInventory
496
497 ($itemlist, $iTotalRecords) = GetItemsForInventory( {
498   minlocation  => $minlocation,
499   maxlocation  => $maxlocation,
500   location     => $location,
501   ignoreissued => $ignoreissued,
502   datelastseen => $datelastseen,
503   branchcode   => $branchcode,
504   branch       => $branch,
505   offset       => $offset,
506   size         => $size,
507   statushash   => $statushash,
508   itemtypes    => \@itemsarray,
509 } );
510
511 Retrieve a list of title/authors/barcode/callnumber, for biblio inventory.
512
513 The sub returns a reference to a list of hashes, each containing
514 itemnumber, author, title, barcode, item callnumber, and date last
515 seen. It is ordered by callnumber then title.
516
517 The required minlocation & maxlocation parameters are used to specify a range of item callnumbers
518 the datelastseen can be used to specify that you want to see items not seen since a past date only.
519 offset & size can be used to retrieve only a part of the whole listing (defaut behaviour)
520 $statushash requires a hashref that has the authorized values fieldname (intems.notforloan, etc...) as keys, and an arrayref of statuscodes we are searching for as values.
521
522 $iTotalRecords is the number of rows that would have been returned without the $offset, $size limit clause
523
524 =cut
525
526 sub GetItemsForInventory {
527     my ( $parameters ) = @_;
528     my $minlocation  = $parameters->{'minlocation'}  // '';
529     my $maxlocation  = $parameters->{'maxlocation'}  // '';
530     my $class_source = $parameters->{'class_source'}  // C4::Context->preference('DefaultClassificationSource');
531     my $location     = $parameters->{'location'}     // '';
532     my $itemtype     = $parameters->{'itemtype'}     // '';
533     my $ignoreissued = $parameters->{'ignoreissued'} // '';
534     my $datelastseen = $parameters->{'datelastseen'} // '';
535     my $branchcode   = $parameters->{'branchcode'}   // '';
536     my $branch       = $parameters->{'branch'}       // '';
537     my $offset       = $parameters->{'offset'}       // '';
538     my $size         = $parameters->{'size'}         // '';
539     my $statushash   = $parameters->{'statushash'}   // '';
540     my $ignore_waiting_holds = $parameters->{'ignore_waiting_holds'} // '';
541     my $itemtypes    = $parameters->{'itemtypes'}    || [];
542     my $ccode        = $parameters->{'ccode'}        // '';
543
544     my $dbh = C4::Context->dbh;
545     my ( @bind_params, @where_strings );
546
547     my $min_cnsort = GetClassSort($class_source,undef,$minlocation);
548     my $max_cnsort = GetClassSort($class_source,undef,$maxlocation);
549
550     my $select_columns = q{
551         SELECT DISTINCT(items.itemnumber), barcode, itemcallnumber, title, author, biblio.biblionumber, biblio.frameworkcode, datelastseen, homebranch, location, notforloan, damaged, itemlost, withdrawn, stocknumber, items.cn_sort, ccode
552
553     };
554     my $select_count = q{SELECT COUNT(DISTINCT(items.itemnumber))};
555     my $query = q{
556         FROM items
557         LEFT JOIN biblio ON items.biblionumber = biblio.biblionumber
558         LEFT JOIN biblioitems on items.biblionumber = biblioitems.biblionumber
559     };
560     if ($statushash){
561         for my $authvfield (keys %$statushash){
562             if ( scalar @{$statushash->{$authvfield}} > 0 ){
563                 my $joinedvals = join ',', @{$statushash->{$authvfield}};
564                 push @where_strings, "$authvfield in (" . $joinedvals . ")";
565             }
566         }
567     }
568
569     if ($ccode){
570         push @where_strings, 'ccode = ?';
571         push @bind_params, $ccode;
572     }
573
574     if ($minlocation) {
575         push @where_strings, 'items.cn_sort >= ?';
576         push @bind_params, $min_cnsort;
577     }
578
579     if ($maxlocation) {
580         push @where_strings, 'items.cn_sort <= ?';
581         push @bind_params, $max_cnsort;
582     }
583
584     if ($datelastseen) {
585         push @where_strings, '(datelastseen < ? OR datelastseen IS NULL)';
586         push @bind_params, $datelastseen;
587     }
588
589     if ( $location ) {
590         push @where_strings, 'items.location = ?';
591         push @bind_params, $location;
592     }
593
594     if ( $branchcode ) {
595         if($branch eq "homebranch"){
596         push @where_strings, 'items.homebranch = ?';
597         }else{
598             push @where_strings, 'items.holdingbranch = ?';
599         }
600         push @bind_params, $branchcode;
601     }
602
603     if ( $itemtype ) {
604         push @where_strings, 'biblioitems.itemtype = ?';
605         push @bind_params, $itemtype;
606     }
607     if ( $ignoreissued) {
608         $query .= "LEFT JOIN issues ON items.itemnumber = issues.itemnumber ";
609         push @where_strings, 'issues.date_due IS NULL';
610     }
611
612     if ( $ignore_waiting_holds ) {
613         $query .= "LEFT JOIN reserves ON items.itemnumber = reserves.itemnumber ";
614         push( @where_strings, q{(reserves.found != 'W' OR reserves.found IS NULL)} );
615     }
616
617     if ( @$itemtypes ) {
618         my $itemtypes_str = join ', ', @$itemtypes;
619         push @where_strings, "( biblioitems.itemtype IN (" . $itemtypes_str . ") OR items.itype IN (" . $itemtypes_str . ") )";
620     }
621
622     if ( @where_strings ) {
623         $query .= 'WHERE ';
624         $query .= join ' AND ', @where_strings;
625     }
626     my $count_query = $select_count . $query;
627     $query .= ' ORDER BY items.cn_sort, itemcallnumber, title';
628     $query .= " LIMIT $offset, $size" if ($offset and $size);
629     $query = $select_columns . $query;
630     my $sth = $dbh->prepare($query);
631     $sth->execute( @bind_params );
632
633     my @results = ();
634     my $tmpresults = $sth->fetchall_arrayref({});
635     $sth = $dbh->prepare( $count_query );
636     $sth->execute( @bind_params );
637     my ($iTotalRecords) = $sth->fetchrow_array();
638
639     my @avs = Koha::AuthorisedValues->search(
640         {   'marc_subfield_structures.kohafield' => { '>' => '' },
641             'me.authorised_value'                => { '>' => '' },
642         },
643         {   join     => { category => 'marc_subfield_structures' },
644             distinct => ['marc_subfield_structures.kohafield, me.category, frameworkcode, me.authorised_value'],
645             '+select' => [ 'marc_subfield_structures.kohafield', 'marc_subfield_structures.frameworkcode', 'me.authorised_value', 'me.lib' ],
646             '+as'     => [ 'kohafield',                          'frameworkcode',                          'authorised_value',    'lib' ],
647         }
648     )->as_list;
649
650     my $avmapping = { map { $_->get_column('kohafield') . ',' . $_->get_column('frameworkcode') . ',' . $_->get_column('authorised_value') => $_->get_column('lib') } @avs };
651
652     foreach my $row (@$tmpresults) {
653
654         # Auth values
655         foreach (keys %$row) {
656             if (
657                 defined(
658                     $avmapping->{ "items.$_," . $row->{'frameworkcode'} . "," . ( $row->{$_} // q{} ) }
659                 )
660             ) {
661                 $row->{$_} = $avmapping->{"items.$_,".$row->{'frameworkcode'}.",".$row->{$_}};
662             }
663         }
664         push @results, $row;
665     }
666
667     return (\@results, $iTotalRecords);
668 }
669
670 =head2 get_hostitemnumbers_of
671
672   my @itemnumbers_of = get_hostitemnumbers_of($biblionumber);
673
674 Given a biblionumber, return the list of corresponding itemnumbers that are linked to it via host fields
675
676 Return a reference on a hash where key is a biblionumber and values are
677 references on array of itemnumbers.
678
679 =cut
680
681
682 sub get_hostitemnumbers_of {
683     my ($biblionumber) = @_;
684
685     if( !C4::Context->preference('EasyAnalyticalRecords') ) {
686         return ();
687     }
688
689     my $biblio = Koha::Biblios->find($biblionumber);
690     my $marcrecord = $biblio->metadata->record;
691     return unless $marcrecord;
692
693     my ( @returnhostitemnumbers, $tag, $biblio_s, $item_s );
694
695     my $marcflavor = C4::Context->preference('marcflavour');
696     if ( $marcflavor eq 'MARC21' ) {
697         $tag      = '773';
698         $biblio_s = '0';
699         $item_s   = '9';
700     }
701     elsif ( $marcflavor eq 'UNIMARC' ) {
702         $tag      = '461';
703         $biblio_s = '0';
704         $item_s   = '9';
705     }
706
707     foreach my $hostfield ( $marcrecord->field($tag) ) {
708         my $hostbiblionumber = $hostfield->subfield($biblio_s);
709         next unless $hostbiblionumber; # have tag, don't have $biblio_s subfield
710         my $linkeditemnumber = $hostfield->subfield($item_s);
711         if ( ! $linkeditemnumber ) {
712             warn "ERROR biblionumber $biblionumber has 773^0, but doesn't have 9";
713             next;
714         }
715         my $is_from_biblio = Koha::Items->search({ itemnumber => $linkeditemnumber, biblionumber => $hostbiblionumber });
716         push @returnhostitemnumbers, $linkeditemnumber
717           if $is_from_biblio;
718     }
719
720     return @returnhostitemnumbers;
721 }
722
723 =head1 LIMITED USE FUNCTIONS
724
725 The following functions, while part of the public API,
726 are not exported.  This is generally because they are
727 meant to be used by only one script for a specific
728 purpose, and should not be used in any other context
729 without careful thought.
730
731 =cut
732
733 =head2 GetMarcItem
734
735   my $item_marc = GetMarcItem($biblionumber, $itemnumber);
736
737 Returns MARC::Record of the item passed in parameter.
738 This function is meant for use only in C<cataloguing/additem.pl>,
739 where it is needed to support that script's MARC-like
740 editor.
741
742 =cut
743
744 sub GetMarcItem {
745     my ( $biblionumber, $itemnumber ) = @_;
746
747     # GetMarcItem has been revised so that it does the following:
748     #  1. Gets the item information from the items table.
749     #  2. Converts it to a MARC field for storage in the bib record.
750     #
751     # The previous behavior was:
752     #  1. Get the bib record.
753     #  2. Return the MARC tag corresponding to the item record.
754     #
755     # The difference is that one treats the items row as authoritative,
756     # while the other treats the MARC representation as authoritative
757     # under certain circumstances.
758
759     my $item = Koha::Items->find($itemnumber) or return;
760
761     # Tack on 'items.' prefix to column names so that C4::Biblio::TransformKohaToMarc will work.
762     # Also, don't emit a subfield if the underlying field is blank.
763
764     return Item2Marc($item->unblessed, $biblionumber);
765
766 }
767 sub Item2Marc {
768         my ($itemrecord,$biblionumber)=@_;
769     my $mungeditem = { 
770         map {  
771             defined($itemrecord->{$_}) && $itemrecord->{$_} ne '' ? ("items.$_" => $itemrecord->{$_}) : ()  
772         } keys %{ $itemrecord } 
773     };
774     my $framework = C4::Biblio::GetFrameworkCode( $biblionumber );
775     my $itemmarc = C4::Biblio::TransformKohaToMarc( $mungeditem, { framework => $framework } );
776     my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField(
777         "items.itemnumber", $framework,
778     );
779
780     my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($mungeditem->{'items.more_subfields_xml'});
781     if (defined $unlinked_item_subfields and $#$unlinked_item_subfields > -1) {
782                 foreach my $field ($itemmarc->field($itemtag)){
783             $field->add_subfields(@$unlinked_item_subfields);
784         }
785     }
786         return $itemmarc;
787 }
788
789 =head1 PRIVATE FUNCTIONS AND VARIABLES
790
791 The following functions are not meant to be called
792 directly, but are documented in order to explain
793 the inner workings of C<C4::Items>.
794
795 =cut
796
797 =head2 _marc_from_item_hash
798
799   my $item_marc = _marc_from_item_hash($item, $frameworkcode[, $unlinked_item_subfields]);
800
801 Given an item hash representing a complete item record,
802 create a C<MARC::Record> object containing an embedded
803 tag representing that item.
804
805 The third, optional parameter C<$unlinked_item_subfields> is
806 an arrayref of subfields (not mapped to C<items> fields per the
807 framework) to be added to the MARC representation
808 of the item.
809
810 =cut
811
812 sub _marc_from_item_hash {
813     my $item = shift;
814     my $frameworkcode = shift;
815     my $unlinked_item_subfields;
816     if (@_) {
817         $unlinked_item_subfields = shift;
818     }
819    
820     # Tack on 'items.' prefix to column names so lookup from MARC frameworks will work
821     # Also, don't emit a subfield if the underlying field is blank.
822     my $mungeditem = { map {  (defined($item->{$_}) and $item->{$_} ne '') ? 
823                                 (/^items\./ ? ($_ => $item->{$_}) : ("items.$_" => $item->{$_})) 
824                                 : ()  } keys %{ $item } }; 
825
826     my $item_marc = MARC::Record->new();
827     foreach my $item_field ( keys %{$mungeditem} ) {
828         my ( $tag, $subfield ) = C4::Biblio::GetMarcFromKohaField( $item_field );
829         next unless defined $tag and defined $subfield;    # skip if not mapped to MARC field
830         my @values = split(/\s?\|\s?/, $mungeditem->{$item_field}, -1);
831         foreach my $value (@values){
832             if ( my $field = $item_marc->field($tag) ) {
833                     $field->add_subfields( $subfield => $value );
834             } else {
835                 my $add_subfields = [];
836                 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
837                     $add_subfields = $unlinked_item_subfields;
838             }
839             $item_marc->add_fields( $tag, " ", " ", $subfield => $value, @$add_subfields );
840             }
841         }
842     }
843
844     return $item_marc;
845 }
846
847 =head2 _repack_item_errors
848
849 Add an error message hash generated by C<CheckItemPreSave>
850 to a list of errors.
851
852 =cut
853
854 sub _repack_item_errors {
855     my $item_sequence_num = shift;
856     my $item_ref = shift;
857     my $error_ref = shift;
858
859     my @repacked_errors = ();
860
861     foreach my $error_code (sort keys %{ $error_ref }) {
862         my $repacked_error = {};
863         $repacked_error->{'item_sequence'} = $item_sequence_num;
864         $repacked_error->{'item_barcode'} = exists($item_ref->{'barcode'}) ? $item_ref->{'barcode'} : '';
865         $repacked_error->{'error_code'} = $error_code;
866         $repacked_error->{'error_information'} = $error_ref->{$error_code};
867         push @repacked_errors, $repacked_error;
868     } 
869
870     return @repacked_errors;
871 }
872
873 =head2 _get_unlinked_item_subfields
874
875   my $unlinked_item_subfields = _get_unlinked_item_subfields($original_item_marc, $frameworkcode);
876
877 =cut
878
879 sub _get_unlinked_item_subfields {
880     my $original_item_marc = shift;
881     my $frameworkcode = shift;
882
883     my $marcstructure = C4::Biblio::GetMarcStructure(1, $frameworkcode, { unsafe => 1 });
884
885     # assume that this record has only one field, and that that
886     # field contains only the item information
887     my $subfields = [];
888     my @fields = $original_item_marc->fields();
889     if ($#fields > -1) {
890         my $field = $fields[0];
891             my $tag = $field->tag();
892         foreach my $subfield ($field->subfields()) {
893             if (defined $subfield->[1] and
894                 $subfield->[1] ne '' and
895                 !$marcstructure->{$tag}->{$subfield->[0]}->{'kohafield'}) {
896                 push @$subfields, $subfield->[0] => $subfield->[1];
897             }
898         }
899     }
900     return $subfields;
901 }
902
903 =head2 _get_unlinked_subfields_xml
904
905   my $unlinked_subfields_xml = _get_unlinked_subfields_xml($unlinked_item_subfields);
906
907 =cut
908
909 sub _get_unlinked_subfields_xml {
910     my $unlinked_item_subfields = shift;
911
912     my $xml;
913     if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
914         my $marc = MARC::Record->new();
915         # use of tag 999 is arbitrary, and doesn't need to match the item tag
916         # used in the framework
917         $marc->append_fields(MARC::Field->new('999', ' ', ' ', @$unlinked_item_subfields));
918         $marc->encoding("UTF-8");    
919         $xml = $marc->as_xml("USMARC");
920     }
921
922     return $xml;
923 }
924
925 =head2 _parse_unlinked_item_subfields_from_xml
926
927   my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($whole_item->{'more_subfields_xml'}):
928
929 =cut
930
931 sub  _parse_unlinked_item_subfields_from_xml {
932     my $xml = shift;
933     require C4::Charset;
934     return unless defined $xml and $xml ne "";
935     my $marc = MARC::Record->new_from_xml(C4::Charset::StripNonXmlChars($xml),'UTF-8');
936     my $unlinked_subfields = [];
937     my @fields = $marc->fields();
938     if ($#fields > -1) {
939         foreach my $subfield ($fields[0]->subfields()) {
940             push @$unlinked_subfields, $subfield->[0] => $subfield->[1];
941         }
942     }
943     return $unlinked_subfields;
944 }
945
946 =head2 GetAnalyticsCount
947
948   $count= &GetAnalyticsCount($itemnumber)
949
950 counts Usage of itemnumber in Analytical bibliorecords. 
951
952 =cut
953
954 sub GetAnalyticsCount {
955     my ($itemnumber) = @_;
956
957     if ( !C4::Context->preference('EasyAnalyticalRecords') ) {
958         return 0;
959     }
960
961     ### ZOOM search here
962     my $query;
963     $query= "hi=".$itemnumber;
964     my $searcher = Koha::SearchEngine::Search->new({index => $Koha::SearchEngine::BIBLIOS_INDEX});
965     my ($err,$res,$result) = $searcher->simple_search_compat($query,0,10);
966     return ($result);
967 }
968
969 sub _SearchItems_build_where_fragment {
970     my ($filter) = @_;
971
972     my $dbh = C4::Context->dbh;
973
974     my $where_fragment;
975     if (exists($filter->{conjunction})) {
976         my (@where_strs, @where_args);
977         foreach my $f (@{ $filter->{filters} }) {
978             my $fragment = _SearchItems_build_where_fragment($f);
979             if ($fragment) {
980                 push @where_strs, $fragment->{str};
981                 push @where_args, @{ $fragment->{args} };
982             }
983         }
984         my $where_str = '';
985         if (@where_strs) {
986             $where_str = '(' . join (' ' . $filter->{conjunction} . ' ', @where_strs) . ')';
987             $where_fragment = {
988                 str => $where_str,
989                 args => \@where_args,
990             };
991         }
992     } else {
993         my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
994         push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
995         push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
996         push @columns, Koha::Database->new()->schema()->resultset('Issue')->result_source->columns;
997         my @operators = qw(= != > < >= <= is like);
998         push @operators, 'not like';
999         my $field = $filter->{field} // q{};
1000         if ( (0 < grep { $_ eq $field } @columns) or (substr($field, 0, 5) eq 'marc:') ) {
1001             my $op = $filter->{operator};
1002             my $query = $filter->{query};
1003             my $ifnull = $filter->{ifnull};
1004
1005             if (!$op or (0 == grep { $_ eq $op } @operators)) {
1006                 $op = '='; # default operator
1007             }
1008
1009             my $column;
1010             if ($field =~ /^marc:(\d{3})(?:\$(\w))?$/) {
1011                 my $marcfield = $1;
1012                 my $marcsubfield = $2;
1013                 my ($kohafield) = $dbh->selectrow_array(q|
1014                     SELECT kohafield FROM marc_subfield_structure
1015                     WHERE tagfield=? AND tagsubfield=? AND frameworkcode=''
1016                 |, undef, $marcfield, $marcsubfield);
1017
1018                 if ($kohafield) {
1019                     $column = $kohafield;
1020                 } else {
1021                     # MARC field is not linked to a DB field so we need to use
1022                     # ExtractValue on marcxml from biblio_metadata or
1023                     # items.more_subfields_xml, depending on the MARC field.
1024                     my $xpath;
1025                     my $sqlfield;
1026                     my ($itemfield) = C4::Biblio::GetMarcFromKohaField('items.itemnumber');
1027                     if ($marcfield eq $itemfield) {
1028                         $sqlfield = 'more_subfields_xml';
1029                         $xpath = '//record/datafield/subfield[@code="' . $marcsubfield . '"]';
1030                     } else {
1031                         $sqlfield = 'metadata'; # From biblio_metadata
1032                         if ($marcfield < 10) {
1033                             $xpath = "//record/controlfield[\@tag=\"$marcfield\"]";
1034                         } else {
1035                             $xpath = "//record/datafield[\@tag=\"$marcfield\"]/subfield[\@code=\"$marcsubfield\"]";
1036                         }
1037                     }
1038                     $column = "ExtractValue($sqlfield, '$xpath')";
1039                 }
1040             }
1041             elsif ($field eq 'isbn') {
1042                 if ( C4::Context->preference("SearchWithISBNVariations") and $query ) {
1043                     my @isbns = C4::Koha::GetVariationsOfISBN( $query );
1044                     $query = [];
1045                     push @$query, @isbns;
1046                 }
1047                 $column = $field;
1048             }
1049             elsif ($field eq 'issn') {
1050                 if ( C4::Context->preference("SearchWithISSNVariations") and $query ) {
1051                     my @issns = C4::Koha::GetVariationsOfISSN( $query );
1052                     $query = [];
1053                     push @$query, @issns;
1054                 }
1055                 $column = $field;
1056             } else {
1057                 $column = $field;
1058             }
1059
1060             if ( defined $ifnull ) {
1061                 $column = "COALESCE($column, ?)";
1062             }
1063
1064             if (ref $query eq 'ARRAY') {
1065                 if ($op eq 'like') {
1066                     $where_fragment = {
1067                         str => "($column LIKE " . join (" OR $column LIKE ", ('?') x @$query ) . ")",
1068                         args => $query,
1069                     };
1070                 }
1071                 else {
1072                     if ($op eq '=') {
1073                         $op = 'IN';
1074                     } elsif ($op eq '!=') {
1075                         $op = 'NOT IN';
1076                     }
1077                     $where_fragment = {
1078                         str => "$column $op (" . join (',', ('?') x @$query) . ")",
1079                         args => $query,
1080                     };
1081                 }
1082             } elsif ( $op eq 'is' ) {
1083                 $where_fragment = {
1084                     str => "$column $op $query",
1085                     args => [],
1086                 };
1087             } else {
1088                 $where_fragment = {
1089                     str => "$column $op ?",
1090                     args => [ $query ],
1091                 };
1092             }
1093
1094             if ( defined $ifnull ) {
1095                 unshift @{ $where_fragment->{args} }, $ifnull;
1096             }
1097         }
1098     }
1099
1100     return $where_fragment;
1101 }
1102
1103 =head2 SearchItems
1104
1105     my ($items, $total) = SearchItems($filter, $params);
1106
1107 Perform a search among items
1108
1109 $filter is a reference to a hash which can be a filter, or a combination of filters.
1110
1111 A filter has the following keys:
1112
1113 =over 2
1114
1115 =item * field: the name of a SQL column in table items
1116
1117 =item * query: the value to search in this column
1118
1119 =item * operator: comparison operator. Can be one of = != > < >= <= like 'not like' is
1120
1121 =back
1122
1123 A combination of filters hash the following keys:
1124
1125 =over 2
1126
1127 =item * conjunction: 'AND' or 'OR'
1128
1129 =item * filters: array ref of filters
1130
1131 =back
1132
1133 $params is a reference to a hash that can contain the following parameters:
1134
1135 =over 2
1136
1137 =item * rows: Number of items to return. 0 returns everything (default: 0)
1138
1139 =item * page: Page to return (return items from (page-1)*rows to (page*rows)-1)
1140                (default: 1)
1141
1142 =item * sortby: A SQL column name in items table to sort on
1143
1144 =item * sortorder: 'ASC' or 'DESC'
1145
1146 =back
1147
1148 =cut
1149
1150 sub SearchItems {
1151     my ($filter, $params) = @_;
1152
1153     $filter //= {};
1154     $params //= {};
1155     return unless ref $filter eq 'HASH';
1156     return unless ref $params eq 'HASH';
1157
1158     # Default parameters
1159     $params->{rows} ||= 0;
1160     $params->{page} ||= 1;
1161     $params->{sortby} ||= 'itemnumber';
1162     $params->{sortorder} ||= 'ASC';
1163
1164     my ($where_str, @where_args);
1165     my $where_fragment = _SearchItems_build_where_fragment($filter);
1166     if ($where_fragment) {
1167         $where_str = $where_fragment->{str};
1168         @where_args = @{ $where_fragment->{args} };
1169     }
1170
1171     my $dbh = C4::Context->dbh;
1172     my $query = q{
1173         SELECT SQL_CALC_FOUND_ROWS items.*
1174         FROM items
1175           LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
1176           LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
1177           LEFT JOIN biblio_metadata ON biblio_metadata.biblionumber = biblio.biblionumber
1178           LEFT JOIN issues ON issues.itemnumber = items.itemnumber
1179           WHERE 1
1180     };
1181     if (defined $where_str and $where_str ne '') {
1182         $query .= qq{ AND $where_str };
1183     }
1184
1185     $query .= q{ AND biblio_metadata.format = 'marcxml' AND biblio_metadata.schema = ? };
1186     push @where_args, C4::Context->preference('marcflavour');
1187
1188     my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
1189     push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
1190     push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
1191     push @columns, Koha::Database->new()->schema()->resultset('Issue')->result_source->columns;
1192
1193     if ( $params->{sortby} eq 'availability' ) {
1194         my $sortorder = (uc($params->{sortorder}) eq 'ASC') ? 'ASC' : 'DESC';
1195         $query .= qq{ ORDER BY onloan $sortorder };
1196     } else {
1197         my $sortby = (0 < grep {$params->{sortby} eq $_} @columns)
1198             ? $params->{sortby} : 'itemnumber';
1199         my $sortorder = (uc($params->{sortorder}) eq 'ASC') ? 'ASC' : 'DESC';
1200         $query .= qq{ ORDER BY $sortby $sortorder };
1201     }
1202
1203     my $rows = $params->{rows};
1204     my @limit_args;
1205     if ($rows > 0) {
1206         my $offset = $rows * ($params->{page}-1);
1207         $query .= qq { LIMIT ?, ? };
1208         push @limit_args, $offset, $rows;
1209     }
1210
1211     my $sth = $dbh->prepare($query);
1212     my $rv = $sth->execute(@where_args, @limit_args);
1213
1214     return unless ($rv);
1215     my ($total_rows) = $dbh->selectrow_array(q{ SELECT FOUND_ROWS() });
1216
1217     return ($sth->fetchall_arrayref({}), $total_rows);
1218 }
1219
1220
1221 =head1  OTHER FUNCTIONS
1222
1223 =head2 _find_value
1224
1225   ($indicators, $value) = _find_value($tag, $subfield, $record,$encoding);
1226
1227 Find the given $subfield in the given $tag in the given
1228 MARC::Record $record.  If the subfield is found, returns
1229 the (indicators, value) pair; otherwise, (undef, undef) is
1230 returned.
1231
1232 PROPOSITION :
1233 Such a function is used in addbiblio AND additem and serial-edit and maybe could be used in Authorities.
1234 I suggest we export it from this module.
1235
1236 =cut
1237
1238 sub _find_value {
1239     my ( $tagfield, $insubfield, $record, $encoding ) = @_;
1240     my @result;
1241     my $indicator;
1242     if ( $tagfield < 10 ) {
1243         if ( $record->field($tagfield) ) {
1244             push @result, $record->field($tagfield)->data();
1245         } else {
1246             push @result, "";
1247         }
1248     } else {
1249         foreach my $field ( $record->field($tagfield) ) {
1250             my @subfields = $field->subfields();
1251             foreach my $subfield (@subfields) {
1252                 if ( @$subfield[0] eq $insubfield ) {
1253                     push @result, @$subfield[1];
1254                     $indicator = $field->indicator(1) . $field->indicator(2);
1255                 }
1256             }
1257         }
1258     }
1259     return ( $indicator, @result );
1260 }
1261
1262
1263 =head2 PrepareItemrecordDisplay
1264
1265   PrepareItemrecordDisplay($bibnum,$itemumber,$defaultvalues,$frameworkcode);
1266
1267 Returns a hash with all the fields for Display a given item data in a template
1268
1269 $defaultvalues should either contain a hashref of values for the new item, or be undefined.
1270
1271 The $frameworkcode returns the item for the given frameworkcode, ONLY if bibnum is not provided
1272
1273 =cut
1274
1275 sub PrepareItemrecordDisplay {
1276
1277     my ( $bibnum, $itemnum, $defaultvalues, $frameworkcode ) = @_;
1278
1279     my $dbh = C4::Context->dbh;
1280     $frameworkcode = C4::Biblio::GetFrameworkCode($bibnum) if $bibnum;
1281     my ( $itemtagfield, $itemtagsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
1282
1283     # Note: $tagslib obtained from GetMarcStructure() in 'unsafe' mode is
1284     # a shared data structure. No plugin (including custom ones) should change
1285     # its contents. See also GetMarcStructure.
1286     my $tagslib = GetMarcStructure( 1, $frameworkcode, { unsafe => 1 } );
1287
1288     # Pick the default location from NewItemsDefaultLocation
1289     if ( C4::Context->preference('NewItemsDefaultLocation') ) {
1290         $defaultvalues //= {};
1291         $defaultvalues->{location} //= C4::Context->preference('NewItemsDefaultLocation');
1292     }
1293
1294     # return nothing if we don't have found an existing framework.
1295     return q{} unless $tagslib;
1296     my $itemrecord;
1297     if ($itemnum) {
1298         $itemrecord = C4::Items::GetMarcItem( $bibnum, $itemnum );
1299     }
1300     my @loop_data;
1301
1302     my $branch_limit = C4::Context->userenv ? C4::Context->userenv->{"branch"} : "";
1303     my $query = qq{
1304         SELECT authorised_value,lib FROM authorised_values
1305     };
1306     $query .= qq{
1307         LEFT JOIN authorised_values_branches ON ( id = av_id )
1308     } if $branch_limit;
1309     $query .= qq{
1310         WHERE category = ?
1311     };
1312     $query .= qq{ AND ( branchcode = ? OR branchcode IS NULL )} if $branch_limit;
1313     $query .= qq{ ORDER BY lib};
1314     my $authorised_values_sth = $dbh->prepare( $query );
1315     foreach my $tag ( sort keys %{$tagslib} ) {
1316         if ( $tag ne '' ) {
1317
1318             # loop through each subfield
1319             my $cntsubf;
1320             foreach my $subfield (
1321                 sort { $a->{display_order} <=> $b->{display_order} || $a->{subfield} cmp $b->{subfield} }
1322                 grep { ref($_) && %$_ } # Not a subfield (values for "important", "lib", "mandatory", etc.) or empty
1323                 values %{ $tagslib->{$tag} } )
1324             {
1325                 next unless ( $subfield->{'tab'} );
1326                 next if ( $subfield->{'tab'} ne "10" );
1327                 my %subfield_data;
1328                 $subfield_data{tag}           = $tag;
1329                 $subfield_data{subfield}      = $subfield->{subfield};
1330                 $subfield_data{countsubfield} = $cntsubf++;
1331                 $subfield_data{kohafield}     = $subfield->{kohafield};
1332                 $subfield_data{id}            = "tag_".$tag."_subfield_".$subfield->{subfield}."_".int(rand(1000000));
1333
1334                 #        $subfield_data{marc_lib}=$tagslib->{$tag}->{$subfield}->{lib};
1335                 $subfield_data{marc_lib}   = $subfield->{lib};
1336                 $subfield_data{mandatory}  = $subfield->{mandatory};
1337                 $subfield_data{repeatable} = $subfield->{repeatable};
1338                 $subfield_data{hidden}     = "display:none"
1339                   if ( ( $subfield->{hidden} > 4 )
1340                     || ( $subfield->{hidden} < -4 ) );
1341                 my ( $x, $defaultvalue );
1342                 if ($itemrecord) {
1343                     ( $x, $defaultvalue ) = _find_value( $tag, $subfield->{subfield}, $itemrecord );
1344                 }
1345                 $defaultvalue = $subfield->{defaultvalue} unless $defaultvalue;
1346                 if ( !defined $defaultvalue ) {
1347                     $defaultvalue = q||;
1348                 } else {
1349                     $defaultvalue =~ s/"/&quot;/g;
1350                     # get today date & replace <<YYYY>>, <<MM>>, <<DD>> if provided in the default value
1351                     my $today_dt = dt_from_string;
1352                     my $year     = $today_dt->strftime('%Y');
1353                     my $shortyear     = $today_dt->strftime('%y');
1354                     my $month    = $today_dt->strftime('%m');
1355                     my $day      = $today_dt->strftime('%d');
1356                     $defaultvalue =~ s/<<YYYY>>/$year/g;
1357                     $defaultvalue =~ s/<<YY>>/$shortyear/g;
1358                     $defaultvalue =~ s/<<MM>>/$month/g;
1359                     $defaultvalue =~ s/<<DD>>/$day/g;
1360
1361                     # And <<USER>> with surname (?)
1362                     my $username =
1363                       (   C4::Context->userenv
1364                         ? C4::Context->userenv->{'surname'}
1365                         : "superlibrarian" );
1366                     $defaultvalue =~ s/<<USER>>/$username/g;
1367                 }
1368
1369                 my $maxlength = $subfield->{maxlength};
1370
1371                 # search for itemcallnumber if applicable
1372                 if ( $subfield->{kohafield} eq 'items.itemcallnumber'
1373                     && C4::Context->preference('itemcallnumber') && $itemrecord) {
1374                     foreach my $itemcn_pref (split(/,/,C4::Context->preference('itemcallnumber'))){
1375                         my $CNtag      = substr( $itemcn_pref, 0, 3 );
1376                         next unless my $field = $itemrecord->field($CNtag);
1377                         my $CNsubfields = substr( $itemcn_pref, 3 );
1378                         $CNsubfields = undef if $CNsubfields eq '';
1379                         $defaultvalue = $field->as_string( $CNsubfields, ' ');
1380                         last if $defaultvalue;
1381                     }
1382                 }
1383                 if (   $subfield->{kohafield} eq 'items.itemcallnumber'
1384                     && $defaultvalues
1385                     && $defaultvalues->{'callnumber'} ) {
1386                     if( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield->{subfield}) ){
1387                         # if the item record exists, only use default value if the item has no callnumber
1388                         $defaultvalue = $defaultvalues->{callnumber};
1389                     } elsif ( !$itemrecord and $defaultvalues ) {
1390                         # if the item record *doesn't* exists, always use the default value
1391                         $defaultvalue = $defaultvalues->{callnumber};
1392                     }
1393                 }
1394                 if (   ( $subfield->{kohafield} eq 'items.holdingbranch' || $subfield->{kohafield} eq 'items.homebranch' )
1395                     && $defaultvalues
1396                     && $defaultvalues->{'branchcode'} ) {
1397                     if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) {
1398                         $defaultvalue = $defaultvalues->{branchcode};
1399                     }
1400                 }
1401                 if (   ( $subfield->{kohafield} eq 'items.location' )
1402                     && $defaultvalues
1403                     && $defaultvalues->{'location'} ) {
1404
1405                     if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield->{subfield}) ) {
1406                         # if the item record exists, only use default value if the item has no locationr
1407                         $defaultvalue = $defaultvalues->{location};
1408                     } elsif ( !$itemrecord and $defaultvalues ) {
1409                         # if the item record *doesn't* exists, always use the default value
1410                         $defaultvalue = $defaultvalues->{location};
1411                     }
1412                 }
1413                 if (   ( $subfield->{kohafield} eq 'items.ccode' )
1414                     && $defaultvalues
1415                     && $defaultvalues->{'ccode'} ) {
1416
1417                     if ( !$itemrecord and $defaultvalues ) {
1418                         # if the item record *doesn't* exists, always use the default value
1419                         $defaultvalue = $defaultvalues->{ccode};
1420                     }
1421                 }
1422                 if ( $subfield->{authorised_value} ) {
1423                     my @authorised_values;
1424                     my %authorised_lib;
1425
1426                     # builds list, depending on authorised value...
1427                     #---- branch
1428                     if ( $subfield->{'authorised_value'} eq "branches" ) {
1429                         if (   ( C4::Context->preference("IndependentBranches") )
1430                             && !C4::Context->IsSuperLibrarian() ) {
1431                             my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches WHERE branchcode = ? ORDER BY branchname" );
1432                             $sth->execute( C4::Context->userenv->{branch} );
1433                             push @authorised_values, ""
1434                               unless ( $subfield->{mandatory} );
1435                             while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
1436                                 push @authorised_values, $branchcode;
1437                                 $authorised_lib{$branchcode} = $branchname;
1438                             }
1439                         } else {
1440                             my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches ORDER BY branchname" );
1441                             $sth->execute;
1442                             push @authorised_values, ""
1443                               unless ( $subfield->{mandatory} );
1444                             while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
1445                                 push @authorised_values, $branchcode;
1446                                 $authorised_lib{$branchcode} = $branchname;
1447                             }
1448                         }
1449
1450                         $defaultvalue = C4::Context->userenv ? C4::Context->userenv->{branch} : undef;
1451                         if ( $defaultvalues and $defaultvalues->{branchcode} ) {
1452                             $defaultvalue = $defaultvalues->{branchcode};
1453                         }
1454
1455                         #----- itemtypes
1456                     } elsif ( $subfield->{authorised_value} eq "itemtypes" ) {
1457                         my $itemtypes = Koha::ItemTypes->search_with_localization;
1458                         push @authorised_values, "";
1459                         while ( my $itemtype = $itemtypes->next ) {
1460                             push @authorised_values, $itemtype->itemtype;
1461                             $authorised_lib{$itemtype->itemtype} = $itemtype->translated_description;
1462                         }
1463                         if ($defaultvalues && $defaultvalues->{'itemtype'}) {
1464                             $defaultvalue = $defaultvalues->{'itemtype'};
1465                         }
1466
1467                         #---- class_sources
1468                     } elsif ( $subfield->{authorised_value} eq "cn_source" ) {
1469                         push @authorised_values, "";
1470
1471                         my $class_sources = GetClassSources();
1472                         my $default_source = $defaultvalue || C4::Context->preference("DefaultClassificationSource");
1473
1474                         foreach my $class_source (sort keys %$class_sources) {
1475                             next unless $class_sources->{$class_source}->{'used'} or
1476                                         ($class_source eq $default_source);
1477                             push @authorised_values, $class_source;
1478                             $authorised_lib{$class_source} = $class_sources->{$class_source}->{'description'};
1479                         }
1480
1481                         $defaultvalue = $default_source;
1482
1483                         #---- "true" authorised value
1484                     } else {
1485                         $authorised_values_sth->execute(
1486                             $subfield->{authorised_value},
1487                             $branch_limit ? $branch_limit : ()
1488                         );
1489                         push @authorised_values, "";
1490                         while ( my ( $value, $lib ) = $authorised_values_sth->fetchrow_array ) {
1491                             push @authorised_values, $value;
1492                             $authorised_lib{$value} = $lib;
1493                         }
1494                     }
1495                     $subfield_data{marc_value} = {
1496                         type    => 'select',
1497                         values  => \@authorised_values,
1498                         default => $defaultvalue // q{},
1499                         labels  => \%authorised_lib,
1500                     };
1501                 } elsif ( $subfield->{value_builder} ) {
1502                 # it is a plugin
1503                     require Koha::FrameworkPlugin;
1504                     my $plugin = Koha::FrameworkPlugin->new({
1505                         name => $subfield->{value_builder},
1506                         item_style => 1,
1507                     });
1508                     my $pars = { dbh => $dbh, record => undef, tagslib =>$tagslib, id => $subfield_data{id} };
1509                     $plugin->build( $pars );
1510                     if ( $itemrecord and my $field = $itemrecord->field($tag) ) {
1511                         $defaultvalue = $field->subfield($subfield->{subfield}) || q{};
1512                     }
1513                     if( !$plugin->errstr ) {
1514                         #TODO Move html to template; see report 12176/13397
1515                         my $tab= $plugin->noclick? '-1': '';
1516                         my $class= $plugin->noclick? ' disabled': '';
1517                         my $title= $plugin->noclick? 'No popup': 'Tag editor';
1518                         $subfield_data{marc_value} = qq[<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" /><a href="#" id="buttonDot_$subfield_data{id}" class="buttonDot $class" title="$title">...</a>\n].$plugin->javascript;
1519                     } else {
1520                         warn $plugin->errstr;
1521                         $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />); # supply default input form
1522                     }
1523                 }
1524                 elsif ( $tag eq '' ) {       # it's an hidden field
1525                     $subfield_data{marc_value} = qq(<input type="hidden" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
1526                 }
1527                 elsif ( $subfield->{'hidden'} ) {   # FIXME: shouldn't input type be "hidden" ?
1528                     $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
1529                 }
1530                 elsif ( length($defaultvalue) > 100
1531                             or (C4::Context->preference("marcflavour") eq "UNIMARC" and
1532                                   300 <= $tag && $tag < 400 && $subfield->{subfield} eq 'a' )
1533                             or (C4::Context->preference("marcflavour") eq "MARC21"  and
1534                                   500 <= $tag && $tag < 600                     )
1535                           ) {
1536                     # oversize field (textarea)
1537                     $subfield_data{marc_value} = qq(<textarea id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength">$defaultvalue</textarea>\n");
1538                 } else {
1539                     $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
1540                 }
1541                 push( @loop_data, \%subfield_data );
1542             }
1543         }
1544     }
1545     my $itemnumber;
1546     if ( $itemrecord && $itemrecord->field($itemtagfield) ) {
1547         $itemnumber = $itemrecord->subfield( $itemtagfield, $itemtagsubfield );
1548     }
1549     return {
1550         'itemtagfield'    => $itemtagfield,
1551         'itemtagsubfield' => $itemtagsubfield,
1552         'itemnumber'      => $itemnumber,
1553         'iteminformation' => \@loop_data
1554     };
1555 }
1556
1557 sub ToggleNewStatus {
1558     my ( $params ) = @_;
1559     my @rules = @{ $params->{rules} };
1560     my $report_only = $params->{report_only};
1561
1562     my $dbh = C4::Context->dbh;
1563     my @errors;
1564     my @item_columns = map { "items.$_" } Koha::Items->columns;
1565     my @biblioitem_columns = map { "biblioitems.$_" } Koha::Biblioitems->columns;
1566     my $report;
1567     for my $rule ( @rules ) {
1568         my $age = $rule->{age};
1569         # Default to using items.dateaccessioned if there's an old item modification rule
1570         # missing an agefield value
1571         my $agefield = $rule->{agefield} ? $rule->{agefield} : 'items.dateaccessioned';
1572         my $conditions = $rule->{conditions};
1573         my $substitutions = $rule->{substitutions};
1574         foreach ( @$substitutions ) {
1575             ( $_->{item_field} ) = ( $_->{field} =~ /items\.(.*)/ );
1576         }
1577         my @params;
1578
1579         my $query = q|
1580             SELECT items.*
1581             FROM items
1582             LEFT JOIN biblioitems ON biblioitems.biblionumber = items.biblionumber
1583             WHERE 1
1584         |;
1585         for my $condition ( @$conditions ) {
1586             next unless $condition->{field};
1587             if (
1588                  grep { $_ eq $condition->{field} } @item_columns
1589               or grep { $_ eq $condition->{field} } @biblioitem_columns
1590             ) {
1591                 if ( $condition->{value} =~ /\|/ ) {
1592                     my @values = split /\|/, $condition->{value};
1593                     $query .= qq| AND $condition->{field} IN (|
1594                         . join( ',', ('?') x scalar @values )
1595                         . q|)|;
1596                     push @params, @values;
1597                 } else {
1598                     $query .= qq| AND $condition->{field} = ?|;
1599                     push @params, $condition->{value};
1600                 }
1601             }
1602         }
1603         if ( defined $age ) {
1604             $query .= qq| AND TO_DAYS(NOW()) - TO_DAYS($agefield) >= ? |;
1605             push @params, $age;
1606         }
1607         my $sth = $dbh->prepare($query);
1608         $sth->execute( @params );
1609         while ( my $values = $sth->fetchrow_hashref ) {
1610             my $biblionumber = $values->{biblionumber};
1611             my $itemnumber = $values->{itemnumber};
1612             my $item = Koha::Items->find($itemnumber);
1613             for my $substitution ( @$substitutions ) {
1614                 my $field = $substitution->{item_field};
1615                 my $value = $substitution->{value};
1616                 next unless $substitution->{field};
1617                 next if ( defined $values->{ $substitution->{item_field} } and $values->{ $substitution->{item_field} } eq $substitution->{value} );
1618                 $item->$field($value);
1619                 push @{ $report->{$itemnumber} }, $substitution;
1620             }
1621             $item->store unless $report_only;
1622         }
1623     }
1624
1625     return $report;
1626 }
1627
1628 1;