Bug 23463: _do_column_fixes_for_mod dealt with integer location and timestamp moved
[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 use vars qw(@ISA @EXPORT);
24 BEGIN {
25     require Exporter;
26     @ISA = qw(Exporter);
27
28     @EXPORT = qw(
29         AddItemFromMarc
30         AddItemBatchFromMarc
31         ModItemFromMarc
32         Item2Marc
33         ModItem
34         ModDateLastSeen
35         ModItemTransfer
36         DelItem
37         CheckItemPreSave
38         GetItemsForInventory
39         GetItemsInfo
40         GetItemsLocationInfo
41         GetHostItemsInfo
42         get_hostitemnumbers_of
43         GetHiddenItemnumbers
44         ItemSafeToDelete
45         DelItemCheck
46         MoveItemFromBiblio
47         CartToShelf
48         GetAnalyticsCount
49         SearchItems
50         PrepareItemrecordDisplay
51     );
52 }
53
54 use Carp;
55 use Try::Tiny;
56 use C4::Context;
57 use C4::Koha;
58 use C4::Biblio;
59 use Koha::DateUtils;
60 use MARC::Record;
61 use C4::ClassSource;
62 use C4::Log;
63 use List::MoreUtils qw(any);
64 use YAML qw(Load);
65 use DateTime::Format::MySQL;
66 use Data::Dumper; # used as part of logging item record changes, not just for
67                   # debugging; so please don't remove this
68
69 use Koha::AuthorisedValues;
70 use Koha::DateUtils qw(dt_from_string);
71 use Koha::Database;
72
73 use Koha::Biblioitems;
74 use Koha::Items;
75 use Koha::ItemTypes;
76 use Koha::SearchEngine;
77 use Koha::SearchEngine::Search;
78 use Koha::Libraries;
79
80 =head1 NAME
81
82 C4::Items - item management functions
83
84 =head1 DESCRIPTION
85
86 This module contains an API for manipulating item 
87 records in Koha, and is used by cataloguing, circulation,
88 acquisitions, and serials management.
89
90 # FIXME This POD is not up-to-date
91 A Koha item record is stored in two places: the
92 items table and embedded in a MARC tag in the XML
93 version of the associated bib record in C<biblioitems.marcxml>.
94 This is done to allow the item information to be readily
95 indexed (e.g., by Zebra), but means that each item
96 modification transaction must keep the items table
97 and the MARC XML in sync at all times.
98
99 The items table will be considered authoritative.  In other
100 words, if there is ever a discrepancy between the items
101 table and the MARC XML, the items table should be considered
102 accurate.
103
104 =head1 HISTORICAL NOTE
105
106 Most of the functions in C<C4::Items> were originally in
107 the C<C4::Biblio> module.
108
109 =head1 CORE EXPORTED FUNCTIONS
110
111 The following functions are meant for use by users
112 of C<C4::Items>
113
114 =cut
115
116 =head2 CartToShelf
117
118   CartToShelf($itemnumber);
119
120 Set the current shelving location of the item record
121 to its stored permanent shelving location.  This is
122 primarily used to indicate when an item whose current
123 location is a special processing ('PROC') or shelving cart
124 ('CART') location is back in the stacks.
125
126 =cut
127
128 sub CartToShelf {
129     my ( $itemnumber ) = @_;
130
131     unless ( $itemnumber ) {
132         croak "FAILED CartToShelf() - no itemnumber supplied";
133     }
134
135     my $item = Koha::Items->find($itemnumber);
136     if ( $item->location eq 'CART' ) {
137         $item->location($item->permanent_location)->store;
138     }
139 }
140
141 =head2 AddItemFromMarc
142
143   my ($biblionumber, $biblioitemnumber, $itemnumber) 
144       = AddItemFromMarc($source_item_marc, $biblionumber);
145
146 Given a MARC::Record object containing an embedded item
147 record and a biblionumber, create a new item record.
148
149 =cut
150
151 sub AddItemFromMarc {
152     my ( $source_item_marc, $biblionumber ) = @_;
153     my $dbh = C4::Context->dbh;
154
155     # parse item hash from MARC
156     my $frameworkcode = C4::Biblio::GetFrameworkCode($biblionumber);
157     my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
158
159     my $localitemmarc = MARC::Record->new;
160     $localitemmarc->append_fields( $source_item_marc->field($itemtag) );
161
162     my $item_values = C4::Biblio::TransformMarcToKoha( $localitemmarc, $frameworkcode, 'items' );
163     my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
164     $item_values->{more_subfields_xml} = _get_unlinked_subfields_xml($unlinked_item_subfields);
165     $item_values->{biblionumber} = $biblionumber;
166     my $item = Koha::Item->new( $item_values )->store;
167     return ( $item->biblionumber, $item->biblioitemnumber, $item->itemnumber );
168 }
169
170 =head2 AddItemBatchFromMarc
171
172   ($itemnumber_ref, $error_ref) = AddItemBatchFromMarc($record, 
173              $biblionumber, $biblioitemnumber, $frameworkcode);
174
175 Efficiently create item records from a MARC biblio record with
176 embedded item fields.  This routine is suitable for batch jobs.
177
178 This API assumes that the bib record has already been
179 saved to the C<biblio> and C<biblioitems> tables.  It does
180 not expect that C<biblio_metadata.metadata> is populated, but it
181 will do so via a call to ModBibiloMarc.
182
183 The goal of this API is to have a similar effect to using AddBiblio
184 and AddItems in succession, but without inefficient repeated
185 parsing of the MARC XML bib record.
186
187 This function returns an arrayref of new itemsnumbers and an arrayref of item
188 errors encountered during the processing.  Each entry in the errors
189 list is a hashref containing the following keys:
190
191 =over
192
193 =item item_sequence
194
195 Sequence number of original item tag in the MARC record.
196
197 =item item_barcode
198
199 Item barcode, provide to assist in the construction of
200 useful error messages.
201
202 =item error_code
203
204 Code representing the error condition.  Can be 'duplicate_barcode',
205 'invalid_homebranch', or 'invalid_holdingbranch'.
206
207 =item error_information
208
209 Additional information appropriate to the error condition.
210
211 =back
212
213 =cut
214
215 sub AddItemBatchFromMarc {
216     my ($record, $biblionumber, $biblioitemnumber, $frameworkcode) = @_;
217     my $error;
218     my @itemnumbers = ();
219     my @errors = ();
220     my $dbh = C4::Context->dbh;
221
222     # We modify the record, so lets work on a clone so we don't change the
223     # original.
224     $record = $record->clone();
225     # loop through the item tags and start creating items
226     my @bad_item_fields = ();
227     my ($itemtag, $itemsubfield) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
228     my $item_sequence_num = 0;
229     ITEMFIELD: foreach my $item_field ($record->field($itemtag)) {
230         $item_sequence_num++;
231         # we take the item field and stick it into a new
232         # MARC record -- this is required so far because (FIXME)
233         # TransformMarcToKoha requires a MARC::Record, not a MARC::Field
234         # and there is no TransformMarcFieldToKoha
235         my $temp_item_marc = MARC::Record->new();
236         $temp_item_marc->append_fields($item_field);
237     
238         # add biblionumber and biblioitemnumber
239         my $item = TransformMarcToKoha( $temp_item_marc, $frameworkcode, 'items' );
240         my $unlinked_item_subfields = _get_unlinked_item_subfields($temp_item_marc, $frameworkcode);
241         $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
242         $item->{'biblionumber'} = $biblionumber;
243         $item->{'biblioitemnumber'} = $biblioitemnumber;
244
245         # check for duplicate barcode
246         my %item_errors = CheckItemPreSave($item);
247         if (%item_errors) {
248             push @errors, _repack_item_errors($item_sequence_num, $item, \%item_errors);
249             push @bad_item_fields, $item_field;
250             next ITEMFIELD;
251         }
252
253         _set_derived_columns_for_add($item);
254         my ( $itemnumber, $error ) = _koha_new_item( $item, $item->{barcode} );
255         warn $error if $error;
256         push @itemnumbers, $itemnumber; # FIXME not checking error
257         $item->{'itemnumber'} = $itemnumber;
258
259         logaction("CATALOGUING", "ADD", $itemnumber, "item") if C4::Context->preference("CataloguingLog"); 
260
261         my $new_item_marc = _marc_from_item_hash($item, $frameworkcode, $unlinked_item_subfields);
262         $item_field->replace_with($new_item_marc->field($itemtag));
263     }
264
265     # remove any MARC item fields for rejected items
266     foreach my $item_field (@bad_item_fields) {
267         $record->delete_field($item_field);
268     }
269
270     # update the MARC biblio
271  #   $biblionumber = ModBiblioMarc( $record, $biblionumber, $frameworkcode );
272
273     return (\@itemnumbers, \@errors);
274 }
275
276 =head2 ModItemFromMarc
277
278   ModItemFromMarc($item_marc, $biblionumber, $itemnumber);
279
280 This function updates an item record based on a supplied
281 C<MARC::Record> object containing an embedded item field.
282 This API is meant for the use of C<additem.pl>; for 
283 other purposes, C<ModItem> should be used.
284
285 This function uses the hash %default_values_for_mod_from_marc,
286 which contains default values for item fields to
287 apply when modifying an item.  This is needed because
288 if an item field's value is cleared, TransformMarcToKoha
289 does not include the column in the
290 hash that's passed to ModItem, which without
291 use of this hash makes it impossible to clear
292 an item field's value.  See bug 2466.
293
294 Note that only columns that can be directly
295 changed from the cataloging and serials
296 item editors are included in this hash.
297
298 Returns item record
299
300 =cut
301
302 sub _build_default_values_for_mod_marc {
303     # Has no framework parameter anymore, since Default is authoritative
304     # for Koha to MARC mappings.
305
306     my $cache     = Koha::Caches->get_instance();
307     my $cache_key = "default_value_for_mod_marc-";
308     my $cached    = $cache->get_from_cache($cache_key);
309     return $cached if $cached;
310
311     my $default_values = {
312         barcode                  => undef,
313         booksellerid             => undef,
314         ccode                    => undef,
315         'items.cn_source'        => undef,
316         coded_location_qualifier => undef,
317         copynumber               => undef,
318         damaged                  => 0,
319         enumchron                => undef,
320         holdingbranch            => undef,
321         homebranch               => undef,
322         itemcallnumber           => undef,
323         itemlost                 => 0,
324         itemnotes                => undef,
325         itemnotes_nonpublic      => undef,
326         itype                    => undef,
327         location                 => undef,
328         permanent_location       => undef,
329         materials                => undef,
330         new_status               => undef,
331         notforloan               => 0,
332         price                    => undef,
333         replacementprice         => undef,
334         replacementpricedate     => undef,
335         restricted               => undef,
336         stack                    => undef,
337         stocknumber              => undef,
338         uri                      => undef,
339         withdrawn                => 0,
340     };
341     my %default_values_for_mod_from_marc;
342     while ( my ( $field, $default_value ) = each %$default_values ) {
343         my $kohafield = $field;
344         $kohafield =~ s|^([^\.]+)$|items.$1|;
345         $default_values_for_mod_from_marc{$field} = $default_value
346             if C4::Biblio::GetMarcFromKohaField( $kohafield );
347     }
348
349     $cache->set_in_cache($cache_key, \%default_values_for_mod_from_marc);
350     return \%default_values_for_mod_from_marc;
351 }
352
353 sub ModItemFromMarc {
354     my $item_marc = shift;
355     my $biblionumber = shift;
356     my $itemnumber = shift;
357
358     my $frameworkcode = C4::Biblio::GetFrameworkCode($biblionumber);
359     my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
360
361     my $localitemmarc = MARC::Record->new;
362     $localitemmarc->append_fields( $item_marc->field($itemtag) );
363     my $item = TransformMarcToKoha( $localitemmarc, $frameworkcode, 'items' );
364     my $default_values = _build_default_values_for_mod_marc();
365     foreach my $item_field ( keys %$default_values ) {
366         $item->{$item_field} = $default_values->{$item_field}
367           unless exists $item->{$item_field};
368     }
369     my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
370
371     my $item_object = Koha::Items->find($itemnumber);
372     $item_object->more_subfields_xml(_get_unlinked_subfields_xml($unlinked_item_subfields))->store;
373
374     return $item_object->get_from_storage->unblessed;
375 }
376
377 =head2 ModItem
378
379 ModItem(
380     { column => $newvalue },
381     $biblionumber,
382     $itemnumber,
383     {
384         [ unlinked_item_subfields => $unlinked_item_subfields, ]
385         [ log_action => 1, ]
386     }
387 );
388
389 Change one or more columns in an item record.
390
391 The first argument is a hashref mapping from item column
392 names to the new values.  The second and third arguments
393 are the biblionumber and itemnumber, respectively.
394 The fourth, optional parameter (additional_params) may contain the keys
395 unlinked_item_subfields and log_action.
396
397 C<$unlinked_item_subfields> contains an arrayref containing
398 subfields present in the original MARC
399 representation of the item (e.g., from the item editor) that are
400 not mapped to C<items> columns directly but should instead
401 be stored in C<items.more_subfields_xml> and included in 
402 the biblio items tag for display and indexing.
403
404 If one of the changed columns is used to calculate
405 the derived value of a column such as C<items.cn_sort>, 
406 this routine will perform the necessary calculation
407 and set the value.
408
409 If log_action is set to false, the action will not be logged.
410 If log_action is true or undefined, the action will be logged.
411
412 =cut
413
414 sub ModItem {
415     my ( $item, $biblionumber, $itemnumber, $additional_params ) = @_;
416     my $log_action = $additional_params->{log_action} // 1;
417
418     _set_derived_columns_for_mod($item);
419     _do_column_fixes_for_mod($item);
420     # FIXME add checks
421     # duplicate barcode
422     # attempt to change itemnumber
423     # attempt to change biblionumber (if we want
424     # an API to relink an item to a different bib,
425     # it should be a separate function)
426
427     # update items table
428     _koha_modify_item($item);
429
430     # request that bib be reindexed so that searching on current
431     # item status is possible
432     ModZebra( $biblionumber, "specialUpdate", "biblioserver" );
433
434     _after_item_action_hooks({ action => 'modify', item_id => $itemnumber });
435
436     logaction( "CATALOGUING", "MODIFY", $itemnumber, "item " . Dumper($item) )
437       if $log_action && C4::Context->preference("CataloguingLog");
438 }
439
440 =head2 ModItemTransfer
441
442   ModItemTransfer($itemnumber, $frombranch, $tobranch, $trigger);
443
444 Marks an item as being transferred from one branch to another and records the trigger.
445
446 =cut
447
448 sub ModItemTransfer {
449     my ( $itemnumber, $frombranch, $tobranch, $trigger ) = @_;
450
451     my $dbh = C4::Context->dbh;
452     my $item = Koha::Items->find( $itemnumber );
453
454     # Remove the 'shelving cart' location status if it is being used.
455     CartToShelf( $itemnumber ) if $item->location && $item->location eq 'CART' && ( !$item->permanent_location || $item->permanent_location ne 'CART' );
456
457     $dbh->do("UPDATE branchtransfers SET datearrived = NOW(), comments = ? WHERE itemnumber = ? AND datearrived IS NULL", undef, "Canceled, new transfer from $frombranch to $tobranch created", $itemnumber);
458
459     #new entry in branchtransfers....
460     my $sth = $dbh->prepare(
461         "INSERT INTO branchtransfers (itemnumber, frombranch, datesent, tobranch, reason)
462         VALUES (?, ?, NOW(), ?, ?)");
463     $sth->execute($itemnumber, $frombranch, $tobranch, $trigger);
464
465     # FIXME we are fetching the item twice in the 2 next statements!
466     Koha::Items->find($itemnumber)->holdingbranch($frombranch)->store({ log_action => 0 });
467     ModDateLastSeen($itemnumber);
468     return;
469 }
470
471 =head2 ModDateLastSeen
472
473 ModDateLastSeen( $itemnumber, $leave_item_lost );
474
475 Mark item as seen. Is called when an item is issued, returned or manually marked during inventory/stocktaking.
476 C<$itemnumber> is the item number
477 C<$leave_item_lost> determines if a lost item will be found or remain lost
478
479 =cut
480
481 sub ModDateLastSeen {
482     my ( $itemnumber, $leave_item_lost ) = @_;
483
484     my $today = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
485
486     my $item = Koha::Items->find($itemnumber);
487     $item->datelastseen($today);
488     $item->itemlost(0) unless $leave_item_lost;
489     $item->store({ log_action => 0 });
490 }
491
492 =head2 DelItem
493
494   DelItem({ itemnumber => $itemnumber, [ biblionumber => $biblionumber ] } );
495
496 Exported function (core API) for deleting an item record in Koha.
497
498 =cut
499
500 sub DelItem {
501     my ( $params ) = @_;
502
503     my $itemnumber   = $params->{itemnumber};
504     my $biblionumber = $params->{biblionumber};
505
506     unless ($biblionumber) {
507         my $item = Koha::Items->find( $itemnumber );
508         $biblionumber = $item ? $item->biblio->biblionumber : undef;
509     }
510
511     # If there is no biblionumber for the given itemnumber, there is nothing to delete
512     return 0 unless $biblionumber;
513
514     # FIXME check the item has no current issues
515     my $deleted = _koha_delete_item( $itemnumber );
516
517     ModZebra( $biblionumber, "specialUpdate", "biblioserver" );
518
519     _after_item_action_hooks({ action => 'delete', item_id => $itemnumber });
520
521     #search item field code
522     logaction("CATALOGUING", "DELETE", $itemnumber, "item") if C4::Context->preference("CataloguingLog");
523     return $deleted;
524 }
525
526 =head2 CheckItemPreSave
527
528     my $item_ref = TransformMarcToKoha($marc, 'items');
529     # do stuff
530     my %errors = CheckItemPreSave($item_ref);
531     if (exists $errors{'duplicate_barcode'}) {
532         print "item has duplicate barcode: ", $errors{'duplicate_barcode'}, "\n";
533     } elsif (exists $errors{'invalid_homebranch'}) {
534         print "item has invalid home branch: ", $errors{'invalid_homebranch'}, "\n";
535     } elsif (exists $errors{'invalid_holdingbranch'}) {
536         print "item has invalid holding branch: ", $errors{'invalid_holdingbranch'}, "\n";
537     } else {
538         print "item is OK";
539     }
540
541 Given a hashref containing item fields, determine if it can be
542 inserted or updated in the database.  Specifically, checks for
543 database integrity issues, and returns a hash containing any
544 of the following keys, if applicable.
545
546 =over 2
547
548 =item duplicate_barcode
549
550 Barcode, if it duplicates one already found in the database.
551
552 =item invalid_homebranch
553
554 Home branch, if not defined in branches table.
555
556 =item invalid_holdingbranch
557
558 Holding branch, if not defined in branches table.
559
560 =back
561
562 This function does NOT implement any policy-related checks,
563 e.g., whether current operator is allowed to save an
564 item that has a given branch code.
565
566 =cut
567
568 sub CheckItemPreSave {
569     my $item_ref = shift;
570
571     my %errors = ();
572
573     # check for duplicate barcode
574     if (exists $item_ref->{'barcode'} and defined $item_ref->{'barcode'}) {
575         my $existing_item= Koha::Items->find({barcode => $item_ref->{'barcode'}});
576         if ($existing_item) {
577             if (!exists $item_ref->{'itemnumber'}                       # new item
578                 or $item_ref->{'itemnumber'} != $existing_item->itemnumber) { # existing item
579                 $errors{'duplicate_barcode'} = $item_ref->{'barcode'};
580             }
581         }
582     }
583
584     # check for valid home branch
585     if (exists $item_ref->{'homebranch'} and defined $item_ref->{'homebranch'}) {
586         my $home_library = Koha::Libraries->find( $item_ref->{homebranch} );
587         unless (defined $home_library) {
588             $errors{'invalid_homebranch'} = $item_ref->{'homebranch'};
589         }
590     }
591
592     # check for valid holding branch
593     if (exists $item_ref->{'holdingbranch'} and defined $item_ref->{'holdingbranch'}) {
594         my $holding_library = Koha::Libraries->find( $item_ref->{holdingbranch} );
595         unless (defined $holding_library) {
596             $errors{'invalid_holdingbranch'} = $item_ref->{'holdingbranch'};
597         }
598     }
599
600     return %errors;
601
602 }
603
604 =head1 EXPORTED SPECIAL ACCESSOR FUNCTIONS
605
606 The following functions provide various ways of 
607 getting an item record, a set of item records, or
608 lists of authorized values for certain item fields.
609
610 =cut
611
612 =head2 GetItemsForInventory
613
614 ($itemlist, $iTotalRecords) = GetItemsForInventory( {
615   minlocation  => $minlocation,
616   maxlocation  => $maxlocation,
617   location     => $location,
618   itemtype     => $itemtype,
619   ignoreissued => $ignoreissued,
620   datelastseen => $datelastseen,
621   branchcode   => $branchcode,
622   branch       => $branch,
623   offset       => $offset,
624   size         => $size,
625   statushash   => $statushash,
626 } );
627
628 Retrieve a list of title/authors/barcode/callnumber, for biblio inventory.
629
630 The sub returns a reference to a list of hashes, each containing
631 itemnumber, author, title, barcode, item callnumber, and date last
632 seen. It is ordered by callnumber then title.
633
634 The required minlocation & maxlocation parameters are used to specify a range of item callnumbers
635 the datelastseen can be used to specify that you want to see items not seen since a past date only.
636 offset & size can be used to retrieve only a part of the whole listing (defaut behaviour)
637 $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.
638
639 $iTotalRecords is the number of rows that would have been returned without the $offset, $size limit clause
640
641 =cut
642
643 sub GetItemsForInventory {
644     my ( $parameters ) = @_;
645     my $minlocation  = $parameters->{'minlocation'}  // '';
646     my $maxlocation  = $parameters->{'maxlocation'}  // '';
647     my $class_source = $parameters->{'class_source'}  // C4::Context->preference('DefaultClassificationSource');
648     my $location     = $parameters->{'location'}     // '';
649     my $itemtype     = $parameters->{'itemtype'}     // '';
650     my $ignoreissued = $parameters->{'ignoreissued'} // '';
651     my $datelastseen = $parameters->{'datelastseen'} // '';
652     my $branchcode   = $parameters->{'branchcode'}   // '';
653     my $branch       = $parameters->{'branch'}       // '';
654     my $offset       = $parameters->{'offset'}       // '';
655     my $size         = $parameters->{'size'}         // '';
656     my $statushash   = $parameters->{'statushash'}   // '';
657     my $ignore_waiting_holds = $parameters->{'ignore_waiting_holds'} // '';
658
659     my $dbh = C4::Context->dbh;
660     my ( @bind_params, @where_strings );
661
662     my $min_cnsort = GetClassSort($class_source,undef,$minlocation);
663     my $max_cnsort = GetClassSort($class_source,undef,$maxlocation);
664
665     my $select_columns = q{
666         SELECT DISTINCT(items.itemnumber), barcode, itemcallnumber, title, author, biblio.biblionumber, biblio.frameworkcode, datelastseen, homebranch, location, notforloan, damaged, itemlost, withdrawn, stocknumber, items.cn_sort
667     };
668     my $select_count = q{SELECT COUNT(DISTINCT(items.itemnumber))};
669     my $query = q{
670         FROM items
671         LEFT JOIN biblio ON items.biblionumber = biblio.biblionumber
672         LEFT JOIN biblioitems on items.biblionumber = biblioitems.biblionumber
673     };
674     if ($statushash){
675         for my $authvfield (keys %$statushash){
676             if ( scalar @{$statushash->{$authvfield}} > 0 ){
677                 my $joinedvals = join ',', @{$statushash->{$authvfield}};
678                 push @where_strings, "$authvfield in (" . $joinedvals . ")";
679             }
680         }
681     }
682
683     if ($minlocation) {
684         push @where_strings, 'items.cn_sort >= ?';
685         push @bind_params, $min_cnsort;
686     }
687
688     if ($maxlocation) {
689         push @where_strings, 'items.cn_sort <= ?';
690         push @bind_params, $max_cnsort;
691     }
692
693     if ($datelastseen) {
694         $datelastseen = output_pref({ str => $datelastseen, dateformat => 'iso', dateonly => 1 });
695         push @where_strings, '(datelastseen < ? OR datelastseen IS NULL)';
696         push @bind_params, $datelastseen;
697     }
698
699     if ( $location ) {
700         push @where_strings, 'items.location = ?';
701         push @bind_params, $location;
702     }
703
704     if ( $branchcode ) {
705         if($branch eq "homebranch"){
706         push @where_strings, 'items.homebranch = ?';
707         }else{
708             push @where_strings, 'items.holdingbranch = ?';
709         }
710         push @bind_params, $branchcode;
711     }
712
713     if ( $itemtype ) {
714         push @where_strings, 'biblioitems.itemtype = ?';
715         push @bind_params, $itemtype;
716     }
717
718     if ( $ignoreissued) {
719         $query .= "LEFT JOIN issues ON items.itemnumber = issues.itemnumber ";
720         push @where_strings, 'issues.date_due IS NULL';
721     }
722
723     if ( $ignore_waiting_holds ) {
724         $query .= "LEFT JOIN reserves ON items.itemnumber = reserves.itemnumber ";
725         push( @where_strings, q{(reserves.found != 'W' OR reserves.found IS NULL)} );
726     }
727
728     if ( @where_strings ) {
729         $query .= 'WHERE ';
730         $query .= join ' AND ', @where_strings;
731     }
732     my $count_query = $select_count . $query;
733     $query .= ' ORDER BY items.cn_sort, itemcallnumber, title';
734     $query .= " LIMIT $offset, $size" if ($offset and $size);
735     $query = $select_columns . $query;
736     my $sth = $dbh->prepare($query);
737     $sth->execute( @bind_params );
738
739     my @results = ();
740     my $tmpresults = $sth->fetchall_arrayref({});
741     $sth = $dbh->prepare( $count_query );
742     $sth->execute( @bind_params );
743     my ($iTotalRecords) = $sth->fetchrow_array();
744
745     my @avs = Koha::AuthorisedValues->search(
746         {   'marc_subfield_structures.kohafield' => { '>' => '' },
747             'me.authorised_value'                => { '>' => '' },
748         },
749         {   join     => { category => 'marc_subfield_structures' },
750             distinct => ['marc_subfield_structures.kohafield, me.category, frameworkcode, me.authorised_value'],
751             '+select' => [ 'marc_subfield_structures.kohafield', 'marc_subfield_structures.frameworkcode', 'me.authorised_value', 'me.lib' ],
752             '+as'     => [ 'kohafield',                          'frameworkcode',                          'authorised_value',    'lib' ],
753         }
754     );
755
756     my $avmapping = { map { $_->get_column('kohafield') . ',' . $_->get_column('frameworkcode') . ',' . $_->get_column('authorised_value') => $_->get_column('lib') } @avs };
757
758     foreach my $row (@$tmpresults) {
759
760         # Auth values
761         foreach (keys %$row) {
762             if (
763                 defined(
764                     $avmapping->{ "items.$_," . $row->{'frameworkcode'} . "," . ( $row->{$_} // q{} ) }
765                 )
766             ) {
767                 $row->{$_} = $avmapping->{"items.$_,".$row->{'frameworkcode'}.",".$row->{$_}};
768             }
769         }
770         push @results, $row;
771     }
772
773     return (\@results, $iTotalRecords);
774 }
775
776 =head2 GetItemsInfo
777
778   @results = GetItemsInfo($biblionumber);
779
780 Returns information about items with the given biblionumber.
781
782 C<GetItemsInfo> returns a list of references-to-hash. Each element
783 contains a number of keys. Most of them are attributes from the
784 C<biblio>, C<biblioitems>, C<items>, and C<itemtypes> tables in the
785 Koha database. Other keys include:
786
787 =over 2
788
789 =item C<$data-E<gt>{branchname}>
790
791 The name (not the code) of the branch to which the book belongs.
792
793 =item C<$data-E<gt>{datelastseen}>
794
795 This is simply C<items.datelastseen>, except that while the date is
796 stored in YYYY-MM-DD format in the database, here it is converted to
797 DD/MM/YYYY format. A NULL date is returned as C<//>.
798
799 =item C<$data-E<gt>{datedue}>
800
801 =item C<$data-E<gt>{class}>
802
803 This is the concatenation of C<biblioitems.classification>, the book's
804 Dewey code, and C<biblioitems.subclass>.
805
806 =item C<$data-E<gt>{ocount}>
807
808 I think this is the number of copies of the book available.
809
810 =item C<$data-E<gt>{order}>
811
812 If this is set, it is set to C<One Order>.
813
814 =back
815
816 =cut
817
818 sub GetItemsInfo {
819     my ( $biblionumber ) = @_;
820     my $dbh   = C4::Context->dbh;
821     require C4::Languages;
822     my $language = C4::Languages::getlanguage();
823     my $query = "
824     SELECT items.*,
825            biblio.*,
826            biblioitems.volume,
827            biblioitems.number,
828            biblioitems.itemtype,
829            biblioitems.isbn,
830            biblioitems.issn,
831            biblioitems.publicationyear,
832            biblioitems.publishercode,
833            biblioitems.volumedate,
834            biblioitems.volumedesc,
835            biblioitems.lccn,
836            biblioitems.url,
837            items.notforloan as itemnotforloan,
838            issues.borrowernumber,
839            issues.date_due as datedue,
840            issues.onsite_checkout,
841            borrowers.cardnumber,
842            borrowers.surname,
843            borrowers.firstname,
844            borrowers.branchcode as bcode,
845            serial.serialseq,
846            serial.publisheddate,
847            itemtypes.description,
848            COALESCE( localization.translation, itemtypes.description ) AS translated_description,
849            itemtypes.notforloan as notforloan_per_itemtype,
850            holding.branchurl,
851            holding.branchcode,
852            holding.branchname,
853            holding.opac_info as holding_branch_opac_info,
854            home.opac_info as home_branch_opac_info,
855            IF(tmp_holdsqueue.itemnumber,1,0) AS has_pending_hold
856      FROM items
857      LEFT JOIN branches AS holding ON items.holdingbranch = holding.branchcode
858      LEFT JOIN branches AS home ON items.homebranch=home.branchcode
859      LEFT JOIN biblio      ON      biblio.biblionumber     = items.biblionumber
860      LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
861      LEFT JOIN issues USING (itemnumber)
862      LEFT JOIN borrowers USING (borrowernumber)
863      LEFT JOIN serialitems USING (itemnumber)
864      LEFT JOIN serial USING (serialid)
865      LEFT JOIN itemtypes   ON   itemtypes.itemtype         = "
866      . (C4::Context->preference('item-level_itypes') ? 'items.itype' : 'biblioitems.itemtype');
867     $query .= q|
868     LEFT JOIN tmp_holdsqueue USING (itemnumber)
869     LEFT JOIN localization ON itemtypes.itemtype = localization.code
870         AND localization.entity = 'itemtypes'
871         AND localization.lang = ?
872     |;
873
874     $query .= " WHERE items.biblionumber = ? ORDER BY home.branchname, items.enumchron, LPAD( items.copynumber, 8, '0' ), items.dateaccessioned DESC" ;
875     my $sth = $dbh->prepare($query);
876     $sth->execute($language, $biblionumber);
877     my $i = 0;
878     my @results;
879     my $serial;
880
881     my $userenv = C4::Context->userenv;
882     my $want_not_same_branch = C4::Context->preference("IndependentBranches") && !C4::Context->IsSuperLibrarian();
883     while ( my $data = $sth->fetchrow_hashref ) {
884         if ( $data->{borrowernumber} && $want_not_same_branch) {
885             $data->{'NOTSAMEBRANCH'} = $data->{'bcode'} ne $userenv->{branch};
886         }
887
888         $serial ||= $data->{'serial'};
889
890         my $descriptions;
891         # get notforloan complete status if applicable
892         $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.notforloan', authorised_value => $data->{itemnotforloan} });
893         $data->{notforloanvalue}     = $descriptions->{lib} // '';
894         $data->{notforloanvalueopac} = $descriptions->{opac_description} // '';
895
896         # get restricted status and description if applicable
897         $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.restricted', authorised_value => $data->{restricted} });
898         $data->{restrictedvalue}     = $descriptions->{lib} // '';
899         $data->{restrictedvalueopac} = $descriptions->{opac_description} // '';
900
901         # my stack procedures
902         $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.stack', authorised_value => $data->{stack} });
903         $data->{stack}          = $descriptions->{lib} // '';
904
905         # Find the last 3 people who borrowed this item.
906         my $sth2 = $dbh->prepare("SELECT * FROM old_issues,borrowers
907                                     WHERE itemnumber = ?
908                                     AND old_issues.borrowernumber = borrowers.borrowernumber
909                                     ORDER BY returndate DESC
910                                     LIMIT 3");
911         $sth2->execute($data->{'itemnumber'});
912         my $ii = 0;
913         while (my $data2 = $sth2->fetchrow_hashref()) {
914             $data->{"timestamp$ii"} = $data2->{'timestamp'} if $data2->{'timestamp'};
915             $data->{"card$ii"}      = $data2->{'cardnumber'} if $data2->{'cardnumber'};
916             $data->{"borrower$ii"}  = $data2->{'borrowernumber'} if $data2->{'borrowernumber'};
917             $ii++;
918         }
919
920         $results[$i] = $data;
921         $i++;
922     }
923
924     return $serial
925         ? sort { ($b->{'publisheddate'} || $b->{'enumchron'}) cmp ($a->{'publisheddate'} || $a->{'enumchron'}) } @results
926         : @results;
927 }
928
929 =head2 GetItemsLocationInfo
930
931   my @itemlocinfo = GetItemsLocationInfo($biblionumber);
932
933 Returns the branch names, shelving location and itemcallnumber for each item attached to the biblio in question
934
935 C<GetItemsInfo> returns a list of references-to-hash. Data returned:
936
937 =over 2
938
939 =item C<$data-E<gt>{homebranch}>
940
941 Branch Name of the item's homebranch
942
943 =item C<$data-E<gt>{holdingbranch}>
944
945 Branch Name of the item's holdingbranch
946
947 =item C<$data-E<gt>{location}>
948
949 Item's shelving location code
950
951 =item C<$data-E<gt>{location_intranet}>
952
953 The intranet description for the Shelving Location as set in authorised_values 'LOC'
954
955 =item C<$data-E<gt>{location_opac}>
956
957 The OPAC description for the Shelving Location as set in authorised_values 'LOC'.  Falls back to intranet description if no OPAC 
958 description is set.
959
960 =item C<$data-E<gt>{itemcallnumber}>
961
962 Item's itemcallnumber
963
964 =item C<$data-E<gt>{cn_sort}>
965
966 Item's call number normalized for sorting
967
968 =back
969   
970 =cut
971
972 sub GetItemsLocationInfo {
973         my $biblionumber = shift;
974         my @results;
975
976         my $dbh = C4::Context->dbh;
977         my $query = "SELECT a.branchname as homebranch, b.branchname as holdingbranch, 
978                             location, itemcallnumber, cn_sort
979                      FROM items, branches as a, branches as b
980                      WHERE homebranch = a.branchcode AND holdingbranch = b.branchcode 
981                      AND biblionumber = ?
982                      ORDER BY cn_sort ASC";
983         my $sth = $dbh->prepare($query);
984         $sth->execute($biblionumber);
985
986         while ( my $data = $sth->fetchrow_hashref ) {
987              my $av = Koha::AuthorisedValues->search({ category => 'LOC', authorised_value => $data->{location} });
988              $av = $av->count ? $av->next : undef;
989              $data->{location_intranet} = $av ? $av->lib : '';
990              $data->{location_opac}     = $av ? $av->opac_description : '';
991              push @results, $data;
992         }
993         return @results;
994 }
995
996 =head2 GetHostItemsInfo
997
998     $hostiteminfo = GetHostItemsInfo($hostfield);
999     Returns the iteminfo for items linked to records via a host field
1000
1001 =cut
1002
1003 sub GetHostItemsInfo {
1004     my ($record) = @_;
1005     my @returnitemsInfo;
1006
1007     if( !C4::Context->preference('EasyAnalyticalRecords') ) {
1008         return @returnitemsInfo;
1009     }
1010
1011     my @fields;
1012     if( C4::Context->preference('marcflavour') eq 'MARC21' ||
1013       C4::Context->preference('marcflavour') eq 'NORMARC') {
1014         @fields = $record->field('773');
1015     } elsif( C4::Context->preference('marcflavour') eq 'UNIMARC') {
1016         @fields = $record->field('461');
1017     }
1018
1019     foreach my $hostfield ( @fields ) {
1020         my $hostbiblionumber = $hostfield->subfield("0");
1021         my $linkeditemnumber = $hostfield->subfield("9");
1022         my @hostitemInfos = GetItemsInfo($hostbiblionumber);
1023         foreach my $hostitemInfo (@hostitemInfos) {
1024             if( $hostitemInfo->{itemnumber} eq $linkeditemnumber ) {
1025                 push @returnitemsInfo, $hostitemInfo;
1026                 last;
1027             }
1028         }
1029     }
1030     return @returnitemsInfo;
1031 }
1032
1033 =head2 get_hostitemnumbers_of
1034
1035   my @itemnumbers_of = get_hostitemnumbers_of($biblionumber);
1036
1037 Given a biblionumber, return the list of corresponding itemnumbers that are linked to it via host fields
1038
1039 Return a reference on a hash where key is a biblionumber and values are
1040 references on array of itemnumbers.
1041
1042 =cut
1043
1044
1045 sub get_hostitemnumbers_of {
1046     my ($biblionumber) = @_;
1047
1048     if( !C4::Context->preference('EasyAnalyticalRecords') ) {
1049         return ();
1050     }
1051
1052     my $marcrecord = C4::Biblio::GetMarcBiblio({ biblionumber => $biblionumber });
1053     return unless $marcrecord;
1054
1055     my ( @returnhostitemnumbers, $tag, $biblio_s, $item_s );
1056
1057     my $marcflavor = C4::Context->preference('marcflavour');
1058     if ( $marcflavor eq 'MARC21' || $marcflavor eq 'NORMARC' ) {
1059         $tag      = '773';
1060         $biblio_s = '0';
1061         $item_s   = '9';
1062     }
1063     elsif ( $marcflavor eq 'UNIMARC' ) {
1064         $tag      = '461';
1065         $biblio_s = '0';
1066         $item_s   = '9';
1067     }
1068
1069     foreach my $hostfield ( $marcrecord->field($tag) ) {
1070         my $hostbiblionumber = $hostfield->subfield($biblio_s);
1071         next unless $hostbiblionumber; # have tag, don't have $biblio_s subfield
1072         my $linkeditemnumber = $hostfield->subfield($item_s);
1073         if ( ! $linkeditemnumber ) {
1074             warn "ERROR biblionumber $biblionumber has 773^0, but doesn't have 9";
1075             next;
1076         }
1077         my $is_from_biblio = Koha::Items->search({ itemnumber => $linkeditemnumber, biblionumber => $hostbiblionumber });
1078         push @returnhostitemnumbers, $linkeditemnumber
1079           if $is_from_biblio;
1080     }
1081
1082     return @returnhostitemnumbers;
1083 }
1084
1085 =head2 GetHiddenItemnumbers
1086
1087     my @itemnumbers_to_hide = GetHiddenItemnumbers({ items => \@items, borcat => $category });
1088
1089 Given a list of items it checks which should be hidden from the OPAC given
1090 the current configuration. Returns a list of itemnumbers corresponding to
1091 those that should be hidden. Optionally takes a borcat parameter for certain borrower types
1092 to be excluded
1093
1094 =cut
1095
1096 sub GetHiddenItemnumbers {
1097     my $params = shift;
1098     my $items = $params->{items};
1099     if (my $exceptions = C4::Context->preference('OpacHiddenItemsExceptions') and $params->{'borcat'}){
1100         foreach my $except (split(/\|/, $exceptions)){
1101             if ($params->{'borcat'} eq $except){
1102                 return; # we don't hide anything for this borrower category
1103             }
1104         }
1105     }
1106     my @resultitems;
1107
1108     my $yaml = C4::Context->preference('OpacHiddenItems');
1109     return () if (! $yaml =~ /\S/ );
1110     $yaml = "$yaml\n\n"; # YAML is anal on ending \n. Surplus does not hurt
1111     my $hidingrules;
1112     eval {
1113         $hidingrules = YAML::Load($yaml);
1114     };
1115     if ($@) {
1116         warn "Unable to parse OpacHiddenItems syspref : $@";
1117         return ();
1118     }
1119     my $dbh = C4::Context->dbh;
1120
1121     # For each item
1122     foreach my $item (@$items) {
1123
1124         # We check each rule
1125         foreach my $field (keys %$hidingrules) {
1126             my $val;
1127             if (exists $item->{$field}) {
1128                 $val = $item->{$field};
1129             }
1130             else {
1131                 my $query = "SELECT $field from items where itemnumber = ?";
1132                 $val = $dbh->selectrow_array($query, undef, $item->{'itemnumber'});
1133             }
1134             $val = '' unless defined $val;
1135
1136             # If the results matches the values in the yaml file
1137             if (any { $val eq $_ } @{$hidingrules->{$field}}) {
1138
1139                 # We add the itemnumber to the list
1140                 push @resultitems, $item->{'itemnumber'};
1141
1142                 # If at least one rule matched for an item, no need to test the others
1143                 last;
1144             }
1145         }
1146     }
1147     return @resultitems;
1148 }
1149
1150 =head1 LIMITED USE FUNCTIONS
1151
1152 The following functions, while part of the public API,
1153 are not exported.  This is generally because they are
1154 meant to be used by only one script for a specific
1155 purpose, and should not be used in any other context
1156 without careful thought.
1157
1158 =cut
1159
1160 =head2 GetMarcItem
1161
1162   my $item_marc = GetMarcItem($biblionumber, $itemnumber);
1163
1164 Returns MARC::Record of the item passed in parameter.
1165 This function is meant for use only in C<cataloguing/additem.pl>,
1166 where it is needed to support that script's MARC-like
1167 editor.
1168
1169 =cut
1170
1171 sub GetMarcItem {
1172     my ( $biblionumber, $itemnumber ) = @_;
1173
1174     # GetMarcItem has been revised so that it does the following:
1175     #  1. Gets the item information from the items table.
1176     #  2. Converts it to a MARC field for storage in the bib record.
1177     #
1178     # The previous behavior was:
1179     #  1. Get the bib record.
1180     #  2. Return the MARC tag corresponding to the item record.
1181     #
1182     # The difference is that one treats the items row as authoritative,
1183     # while the other treats the MARC representation as authoritative
1184     # under certain circumstances.
1185
1186     my $item = Koha::Items->find($itemnumber) or return;
1187
1188     # Tack on 'items.' prefix to column names so that C4::Biblio::TransformKohaToMarc will work.
1189     # Also, don't emit a subfield if the underlying field is blank.
1190
1191     return Item2Marc($item->unblessed, $biblionumber);
1192
1193 }
1194 sub Item2Marc {
1195         my ($itemrecord,$biblionumber)=@_;
1196     my $mungeditem = { 
1197         map {  
1198             defined($itemrecord->{$_}) && $itemrecord->{$_} ne '' ? ("items.$_" => $itemrecord->{$_}) : ()  
1199         } keys %{ $itemrecord } 
1200     };
1201     my $framework = C4::Biblio::GetFrameworkCode( $biblionumber );
1202     my $itemmarc = C4::Biblio::TransformKohaToMarc( $mungeditem ); # Bug 21774: no_split parameter removed to allow cloned subfields
1203     my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField(
1204         "items.itemnumber", $framework,
1205     );
1206
1207     my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($mungeditem->{'items.more_subfields_xml'});
1208     if (defined $unlinked_item_subfields and $#$unlinked_item_subfields > -1) {
1209                 foreach my $field ($itemmarc->field($itemtag)){
1210             $field->add_subfields(@$unlinked_item_subfields);
1211         }
1212     }
1213         return $itemmarc;
1214 }
1215
1216 =head1 PRIVATE FUNCTIONS AND VARIABLES
1217
1218 The following functions are not meant to be called
1219 directly, but are documented in order to explain
1220 the inner workings of C<C4::Items>.
1221
1222 =cut
1223
1224 =head2 %derived_columns
1225
1226 This hash keeps track of item columns that
1227 are strictly derived from other columns in
1228 the item record and are not meant to be set
1229 independently.
1230
1231 Each key in the hash should be the name of a
1232 column (as named by TransformMarcToKoha).  Each
1233 value should be hashref whose keys are the
1234 columns on which the derived column depends.  The
1235 hashref should also contain a 'BUILDER' key
1236 that is a reference to a sub that calculates
1237 the derived value.
1238
1239 =cut
1240
1241 my %derived_columns = (
1242     'items.cn_sort' => {
1243         'itemcallnumber' => 1,
1244         'items.cn_source' => 1,
1245         'BUILDER' => \&_calc_items_cn_sort,
1246     }
1247 );
1248
1249 =head2 _set_derived_columns_for_add 
1250
1251   _set_derived_column_for_add($item);
1252
1253 Given an item hash representing a new item to be added,
1254 calculate any derived columns.  Currently the only
1255 such column is C<items.cn_sort>.
1256
1257 =cut
1258
1259 sub _set_derived_columns_for_add {
1260     my $item = shift;
1261
1262     foreach my $column (keys %derived_columns) {
1263         my $builder = $derived_columns{$column}->{'BUILDER'};
1264         my $source_values = {};
1265         foreach my $source_column (keys %{ $derived_columns{$column} }) {
1266             next if $source_column eq 'BUILDER';
1267             $source_values->{$source_column} = $item->{$source_column};
1268         }
1269         $builder->($item, $source_values);
1270     }
1271 }
1272
1273 =head2 _get_single_item_column
1274
1275   _get_single_item_column($column, $itemnumber);
1276
1277 Retrieves the value of a single column from an C<items>
1278 row specified by C<$itemnumber>.
1279
1280 =cut
1281
1282 sub _get_single_item_column {
1283     my $column = shift;
1284     my $itemnumber = shift;
1285     
1286     my $dbh = C4::Context->dbh;
1287     my $sth = $dbh->prepare("SELECT $column FROM items WHERE itemnumber = ?");
1288     $sth->execute($itemnumber);
1289     my ($value) = $sth->fetchrow();
1290     return $value; 
1291 }
1292
1293 =head2 _calc_items_cn_sort
1294
1295   _calc_items_cn_sort($item, $source_values);
1296
1297 Helper routine to calculate C<items.cn_sort>.
1298
1299 =cut
1300
1301 sub _calc_items_cn_sort {
1302     my $item = shift;
1303     my $source_values = shift;
1304
1305     $item->{'items.cn_sort'} = GetClassSort($source_values->{'items.cn_source'}, $source_values->{'itemcallnumber'}, "");
1306 }
1307
1308 =head2 _koha_new_item
1309
1310   my ($itemnumber,$error) = _koha_new_item( $item, $barcode );
1311
1312 Perform the actual insert into the C<items> table.
1313
1314 =cut
1315
1316 sub _koha_new_item {
1317     my ( $item, $barcode ) = @_;
1318     my $dbh=C4::Context->dbh;  
1319     my $error;
1320     $item->{permanent_location} //= $item->{location};
1321     _mod_item_dates( $item );
1322     my $query =
1323            "INSERT INTO items SET
1324             biblionumber        = ?,
1325             biblioitemnumber    = ?,
1326             barcode             = ?,
1327             dateaccessioned     = ?,
1328             booksellerid        = ?,
1329             homebranch          = ?,
1330             price               = ?,
1331             replacementprice    = ?,
1332             replacementpricedate = ?,
1333             datelastborrowed    = ?,
1334             datelastseen        = ?,
1335             stack               = ?,
1336             notforloan          = ?,
1337             damaged             = ?,
1338             itemlost            = ?,
1339             withdrawn           = ?,
1340             itemcallnumber      = ?,
1341             coded_location_qualifier = ?,
1342             restricted          = ?,
1343             itemnotes           = ?,
1344             itemnotes_nonpublic = ?,
1345             holdingbranch       = ?,
1346             location            = ?,
1347             permanent_location  = ?,
1348             onloan              = ?,
1349             issues              = ?,
1350             renewals            = ?,
1351             reserves            = ?,
1352             cn_source           = ?,
1353             cn_sort             = ?,
1354             ccode               = ?,
1355             itype               = ?,
1356             materials           = ?,
1357             uri                 = ?,
1358             enumchron           = ?,
1359             more_subfields_xml  = ?,
1360             copynumber          = ?,
1361             stocknumber         = ?,
1362             new_status          = ?
1363           ";
1364     my $sth = $dbh->prepare($query);
1365     my $today = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
1366    $sth->execute(
1367             $item->{'biblionumber'},
1368             $item->{'biblioitemnumber'},
1369             $barcode,
1370             $item->{'dateaccessioned'},
1371             $item->{'booksellerid'},
1372             $item->{'homebranch'},
1373             $item->{'price'},
1374             $item->{'replacementprice'},
1375             $item->{'replacementpricedate'} || $today,
1376             $item->{datelastborrowed},
1377             $item->{datelastseen} || $today,
1378             $item->{stack},
1379             $item->{'notforloan'},
1380             $item->{'damaged'},
1381             $item->{'itemlost'},
1382             $item->{'withdrawn'},
1383             $item->{'itemcallnumber'},
1384             $item->{'coded_location_qualifier'},
1385             $item->{'restricted'},
1386             $item->{'itemnotes'},
1387             $item->{'itemnotes_nonpublic'},
1388             $item->{'holdingbranch'},
1389             $item->{'location'},
1390             $item->{'permanent_location'},
1391             $item->{'onloan'},
1392             $item->{'issues'},
1393             $item->{'renewals'},
1394             $item->{'reserves'},
1395             $item->{'items.cn_source'},
1396             $item->{'items.cn_sort'},
1397             $item->{'ccode'},
1398             $item->{'itype'},
1399             $item->{'materials'},
1400             $item->{'uri'},
1401             $item->{'enumchron'},
1402             $item->{'more_subfields_xml'},
1403             $item->{'copynumber'},
1404             $item->{'stocknumber'},
1405             $item->{'new_status'},
1406     );
1407
1408     my $itemnumber;
1409     if ( defined $sth->errstr ) {
1410         $error.="ERROR in _koha_new_item $query".$sth->errstr;
1411     }
1412     else {
1413         $itemnumber = $dbh->{'mysql_insertid'};
1414     }
1415
1416     return ( $itemnumber, $error );
1417 }
1418
1419 =head2 MoveItemFromBiblio
1420
1421   MoveItemFromBiblio($itenumber, $frombiblio, $tobiblio);
1422
1423 Moves an item from a biblio to another
1424
1425 Returns undef if the move failed or the biblionumber of the destination record otherwise
1426
1427 =cut
1428
1429 sub MoveItemFromBiblio {
1430     my ($itemnumber, $frombiblio, $tobiblio) = @_;
1431     my $dbh = C4::Context->dbh;
1432     my ( $tobiblioitem ) = $dbh->selectrow_array(q|
1433         SELECT biblioitemnumber
1434         FROM biblioitems
1435         WHERE biblionumber = ?
1436     |, undef, $tobiblio );
1437     my $return = $dbh->do(q|
1438         UPDATE items
1439         SET biblioitemnumber = ?,
1440             biblionumber = ?
1441         WHERE itemnumber = ?
1442             AND biblionumber = ?
1443     |, undef, $tobiblioitem, $tobiblio, $itemnumber, $frombiblio );
1444     if ($return == 1) {
1445         ModZebra( $tobiblio, "specialUpdate", "biblioserver" );
1446         ModZebra( $frombiblio, "specialUpdate", "biblioserver" );
1447             # Checking if the item we want to move is in an order 
1448         require C4::Acquisition;
1449         my $order = C4::Acquisition::GetOrderFromItemnumber($itemnumber);
1450             if ($order) {
1451                     # Replacing the biblionumber within the order if necessary
1452                     $order->{'biblionumber'} = $tobiblio;
1453                 C4::Acquisition::ModOrder($order);
1454             }
1455
1456         # Update reserves, hold_fill_targets, tmp_holdsqueue and linktracker tables
1457         for my $table_name ( qw( reserves hold_fill_targets tmp_holdsqueue linktracker ) ) {
1458             $dbh->do( qq|
1459                 UPDATE $table_name
1460                 SET biblionumber = ?
1461                 WHERE itemnumber = ?
1462             |, undef, $tobiblio, $itemnumber );
1463         }
1464         return $tobiblio;
1465         }
1466     return;
1467 }
1468
1469 =head2 ItemSafeToDelete
1470
1471    ItemSafeToDelete( $biblionumber, $itemnumber);
1472
1473 Exported function (core API) for checking whether an item record is safe to delete.
1474
1475 returns 1 if the item is safe to delete,
1476
1477 "book_on_loan" if the item is checked out,
1478
1479 "not_same_branch" if the item is blocked by independent branches,
1480
1481 "book_reserved" if the there are holds aganst the item, or
1482
1483 "linked_analytics" if the item has linked analytic records.
1484
1485 =cut
1486
1487 sub ItemSafeToDelete {
1488     my ( $biblionumber, $itemnumber ) = @_;
1489     my $status;
1490     my $dbh = C4::Context->dbh;
1491
1492     my $error;
1493
1494     my $countanalytics = GetAnalyticsCount($itemnumber);
1495
1496     my $item = Koha::Items->find($itemnumber) or return;
1497
1498     if ($item->checkout) {
1499         $status = "book_on_loan";
1500     }
1501     elsif ( defined C4::Context->userenv
1502         and !C4::Context->IsSuperLibrarian()
1503         and C4::Context->preference("IndependentBranches")
1504         and ( C4::Context->userenv->{branch} ne $item->homebranch ) )
1505     {
1506         $status = "not_same_branch";
1507     }
1508     else {
1509         # check it doesn't have a waiting reserve
1510         my $sth = $dbh->prepare(
1511             q{
1512             SELECT COUNT(*) FROM reserves
1513             WHERE (found = 'W' OR found = 'T')
1514             AND itemnumber = ?
1515         }
1516         );
1517         $sth->execute($itemnumber);
1518         my ($reserve) = $sth->fetchrow;
1519         if ($reserve) {
1520             $status = "book_reserved";
1521         }
1522         elsif ( $countanalytics > 0 ) {
1523             $status = "linked_analytics";
1524         }
1525         else {
1526             $status = 1;
1527         }
1528     }
1529     return $status;
1530 }
1531
1532 =head2 DelItemCheck
1533
1534    DelItemCheck( $biblionumber, $itemnumber);
1535
1536 Exported function (core API) for deleting an item record in Koha if there no current issue.
1537
1538 DelItemCheck wraps ItemSafeToDelete around DelItem.
1539
1540 =cut
1541
1542 sub DelItemCheck {
1543     my ( $biblionumber, $itemnumber ) = @_;
1544     my $status = ItemSafeToDelete( $biblionumber, $itemnumber );
1545
1546     if ( $status == 1 ) {
1547         DelItem(
1548             {
1549                 biblionumber => $biblionumber,
1550                 itemnumber   => $itemnumber
1551             }
1552         );
1553     }
1554     return $status;
1555 }
1556
1557 =head2 _koha_modify_item
1558
1559   my ($itemnumber,$error) =_koha_modify_item( $item );
1560
1561 Perform the actual update of the C<items> row.  Note that this
1562 routine accepts a hashref specifying the columns to update.
1563
1564 =cut
1565
1566 sub _koha_modify_item {
1567     my ( $item ) = @_;
1568     my $dbh=C4::Context->dbh;  
1569     my $error;
1570
1571     my $query = "UPDATE items SET ";
1572     my @bind;
1573     _mod_item_dates( $item );
1574     for my $key ( keys %$item ) {
1575         next if ( $key eq 'itemnumber' );
1576         $query.="$key=?,";
1577         push @bind, $item->{$key};
1578     }
1579     $query =~ s/,$//;
1580     $query .= " WHERE itemnumber=?";
1581     push @bind, $item->{'itemnumber'};
1582     my $sth = $dbh->prepare($query);
1583     $sth->execute(@bind);
1584     if ( $sth->err ) {
1585         $error.="ERROR in _koha_modify_item $query: ".$sth->errstr;
1586         warn $error;
1587     }
1588     return ($item->{'itemnumber'},$error);
1589 }
1590
1591 sub _mod_item_dates { # date formatting for date fields in item hash
1592     my ( $item ) = @_;
1593     return if !$item || ref($item) ne 'HASH';
1594
1595     my @keys = grep
1596         { $_ =~ /^onloan$|^date|date$|datetime$/ }
1597         keys %$item;
1598     # Incl. dateaccessioned,replacementpricedate,datelastborrowed,datelastseen
1599     # NOTE: We do not (yet) have items fields ending with datetime
1600     # Fields with _on$ have been handled already
1601
1602     foreach my $key ( @keys ) {
1603         next if !defined $item->{$key}; # skip undefs
1604         my $dt = eval { dt_from_string( $item->{$key} ) };
1605             # eval: dt_from_string will die on us if we pass illegal dates
1606
1607         my $newstr;
1608         if( defined $dt  && ref($dt) eq 'DateTime' ) {
1609             if( $key =~ /datetime/ ) {
1610                 $newstr = DateTime::Format::MySQL->format_datetime($dt);
1611             } else {
1612                 $newstr = DateTime::Format::MySQL->format_date($dt);
1613             }
1614         }
1615         $item->{$key} = $newstr; # might be undef to clear garbage
1616     }
1617 }
1618
1619 =head2 _koha_delete_item
1620
1621   _koha_delete_item( $itemnum );
1622
1623 Internal function to delete an item record from the koha tables
1624
1625 =cut
1626
1627 sub _koha_delete_item {
1628     my ( $itemnum ) = @_;
1629
1630     my $dbh = C4::Context->dbh;
1631     # save the deleted item to deleteditems table
1632     my $sth = $dbh->prepare("SELECT * FROM items WHERE itemnumber=?");
1633     $sth->execute($itemnum);
1634     my $data = $sth->fetchrow_hashref();
1635
1636     # There is no item to delete
1637     return 0 unless $data;
1638
1639     my $query = "INSERT INTO deleteditems SET ";
1640     my @bind  = ();
1641     foreach my $key ( keys %$data ) {
1642         next if ( $key eq 'timestamp' ); # timestamp will be set by db
1643         $query .= "$key = ?,";
1644         push( @bind, $data->{$key} );
1645     }
1646     $query =~ s/\,$//;
1647     $sth = $dbh->prepare($query);
1648     $sth->execute(@bind);
1649
1650     # delete from items table
1651     $sth = $dbh->prepare("DELETE FROM items WHERE itemnumber=?");
1652     my $deleted = $sth->execute($itemnum);
1653     return ( $deleted == 1 ) ? 1 : 0;
1654 }
1655
1656 =head2 _marc_from_item_hash
1657
1658   my $item_marc = _marc_from_item_hash($item, $frameworkcode[, $unlinked_item_subfields]);
1659
1660 Given an item hash representing a complete item record,
1661 create a C<MARC::Record> object containing an embedded
1662 tag representing that item.
1663
1664 The third, optional parameter C<$unlinked_item_subfields> is
1665 an arrayref of subfields (not mapped to C<items> fields per the
1666 framework) to be added to the MARC representation
1667 of the item.
1668
1669 =cut
1670
1671 sub _marc_from_item_hash {
1672     my $item = shift;
1673     my $frameworkcode = shift;
1674     my $unlinked_item_subfields;
1675     if (@_) {
1676         $unlinked_item_subfields = shift;
1677     }
1678    
1679     # Tack on 'items.' prefix to column names so lookup from MARC frameworks will work
1680     # Also, don't emit a subfield if the underlying field is blank.
1681     my $mungeditem = { map {  (defined($item->{$_}) and $item->{$_} ne '') ? 
1682                                 (/^items\./ ? ($_ => $item->{$_}) : ("items.$_" => $item->{$_})) 
1683                                 : ()  } keys %{ $item } }; 
1684
1685     my $item_marc = MARC::Record->new();
1686     foreach my $item_field ( keys %{$mungeditem} ) {
1687         my ( $tag, $subfield ) = C4::Biblio::GetMarcFromKohaField( $item_field );
1688         next unless defined $tag and defined $subfield;    # skip if not mapped to MARC field
1689         my @values = split(/\s?\|\s?/, $mungeditem->{$item_field}, -1);
1690         foreach my $value (@values){
1691             if ( my $field = $item_marc->field($tag) ) {
1692                     $field->add_subfields( $subfield => $value );
1693             } else {
1694                 my $add_subfields = [];
1695                 if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
1696                     $add_subfields = $unlinked_item_subfields;
1697             }
1698             $item_marc->add_fields( $tag, " ", " ", $subfield => $value, @$add_subfields );
1699             }
1700         }
1701     }
1702
1703     return $item_marc;
1704 }
1705
1706 =head2 _repack_item_errors
1707
1708 Add an error message hash generated by C<CheckItemPreSave>
1709 to a list of errors.
1710
1711 =cut
1712
1713 sub _repack_item_errors {
1714     my $item_sequence_num = shift;
1715     my $item_ref = shift;
1716     my $error_ref = shift;
1717
1718     my @repacked_errors = ();
1719
1720     foreach my $error_code (sort keys %{ $error_ref }) {
1721         my $repacked_error = {};
1722         $repacked_error->{'item_sequence'} = $item_sequence_num;
1723         $repacked_error->{'item_barcode'} = exists($item_ref->{'barcode'}) ? $item_ref->{'barcode'} : '';
1724         $repacked_error->{'error_code'} = $error_code;
1725         $repacked_error->{'error_information'} = $error_ref->{$error_code};
1726         push @repacked_errors, $repacked_error;
1727     } 
1728
1729     return @repacked_errors;
1730 }
1731
1732 =head2 _get_unlinked_item_subfields
1733
1734   my $unlinked_item_subfields = _get_unlinked_item_subfields($original_item_marc, $frameworkcode);
1735
1736 =cut
1737
1738 sub _get_unlinked_item_subfields {
1739     my $original_item_marc = shift;
1740     my $frameworkcode = shift;
1741
1742     my $marcstructure = C4::Biblio::GetMarcStructure(1, $frameworkcode, { unsafe => 1 });
1743
1744     # assume that this record has only one field, and that that
1745     # field contains only the item information
1746     my $subfields = [];
1747     my @fields = $original_item_marc->fields();
1748     if ($#fields > -1) {
1749         my $field = $fields[0];
1750             my $tag = $field->tag();
1751         foreach my $subfield ($field->subfields()) {
1752             if (defined $subfield->[1] and
1753                 $subfield->[1] ne '' and
1754                 !$marcstructure->{$tag}->{$subfield->[0]}->{'kohafield'}) {
1755                 push @$subfields, $subfield->[0] => $subfield->[1];
1756             }
1757         }
1758     }
1759     return $subfields;
1760 }
1761
1762 =head2 _get_unlinked_subfields_xml
1763
1764   my $unlinked_subfields_xml = _get_unlinked_subfields_xml($unlinked_item_subfields);
1765
1766 =cut
1767
1768 sub _get_unlinked_subfields_xml {
1769     my $unlinked_item_subfields = shift;
1770
1771     my $xml;
1772     if (defined $unlinked_item_subfields and ref($unlinked_item_subfields) eq 'ARRAY' and $#$unlinked_item_subfields > -1) {
1773         my $marc = MARC::Record->new();
1774         # use of tag 999 is arbitrary, and doesn't need to match the item tag
1775         # used in the framework
1776         $marc->append_fields(MARC::Field->new('999', ' ', ' ', @$unlinked_item_subfields));
1777         $marc->encoding("UTF-8");    
1778         $xml = $marc->as_xml("USMARC");
1779     }
1780
1781     return $xml;
1782 }
1783
1784 =head2 _parse_unlinked_item_subfields_from_xml
1785
1786   my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($whole_item->{'more_subfields_xml'}):
1787
1788 =cut
1789
1790 sub  _parse_unlinked_item_subfields_from_xml {
1791     my $xml = shift;
1792     require C4::Charset;
1793     return unless defined $xml and $xml ne "";
1794     my $marc = MARC::Record->new_from_xml(C4::Charset::StripNonXmlChars($xml),'UTF-8');
1795     my $unlinked_subfields = [];
1796     my @fields = $marc->fields();
1797     if ($#fields > -1) {
1798         foreach my $subfield ($fields[0]->subfields()) {
1799             push @$unlinked_subfields, $subfield->[0] => $subfield->[1];
1800         }
1801     }
1802     return $unlinked_subfields;
1803 }
1804
1805 =head2 GetAnalyticsCount
1806
1807   $count= &GetAnalyticsCount($itemnumber)
1808
1809 counts Usage of itemnumber in Analytical bibliorecords. 
1810
1811 =cut
1812
1813 sub GetAnalyticsCount {
1814     my ($itemnumber) = @_;
1815
1816     ### ZOOM search here
1817     my $query;
1818     $query= "hi=".$itemnumber;
1819     my $searcher = Koha::SearchEngine::Search->new({index => $Koha::SearchEngine::BIBLIOS_INDEX});
1820     my ($err,$res,$result) = $searcher->simple_search_compat($query,0,10);
1821     return ($result);
1822 }
1823
1824 sub _SearchItems_build_where_fragment {
1825     my ($filter) = @_;
1826
1827     my $dbh = C4::Context->dbh;
1828
1829     my $where_fragment;
1830     if (exists($filter->{conjunction})) {
1831         my (@where_strs, @where_args);
1832         foreach my $f (@{ $filter->{filters} }) {
1833             my $fragment = _SearchItems_build_where_fragment($f);
1834             if ($fragment) {
1835                 push @where_strs, $fragment->{str};
1836                 push @where_args, @{ $fragment->{args} };
1837             }
1838         }
1839         my $where_str = '';
1840         if (@where_strs) {
1841             $where_str = '(' . join (' ' . $filter->{conjunction} . ' ', @where_strs) . ')';
1842             $where_fragment = {
1843                 str => $where_str,
1844                 args => \@where_args,
1845             };
1846         }
1847     } else {
1848         my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
1849         push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
1850         push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
1851         my @operators = qw(= != > < >= <= like);
1852         my $field = $filter->{field} // q{};
1853         if ( (0 < grep { $_ eq $field } @columns) or (substr($field, 0, 5) eq 'marc:') ) {
1854             my $op = $filter->{operator};
1855             my $query = $filter->{query};
1856
1857             if (!$op or (0 == grep { $_ eq $op } @operators)) {
1858                 $op = '='; # default operator
1859             }
1860
1861             my $column;
1862             if ($field =~ /^marc:(\d{3})(?:\$(\w))?$/) {
1863                 my $marcfield = $1;
1864                 my $marcsubfield = $2;
1865                 my ($kohafield) = $dbh->selectrow_array(q|
1866                     SELECT kohafield FROM marc_subfield_structure
1867                     WHERE tagfield=? AND tagsubfield=? AND frameworkcode=''
1868                 |, undef, $marcfield, $marcsubfield);
1869
1870                 if ($kohafield) {
1871                     $column = $kohafield;
1872                 } else {
1873                     # MARC field is not linked to a DB field so we need to use
1874                     # ExtractValue on marcxml from biblio_metadata or
1875                     # items.more_subfields_xml, depending on the MARC field.
1876                     my $xpath;
1877                     my $sqlfield;
1878                     my ($itemfield) = C4::Biblio::GetMarcFromKohaField('items.itemnumber');
1879                     if ($marcfield eq $itemfield) {
1880                         $sqlfield = 'more_subfields_xml';
1881                         $xpath = '//record/datafield/subfield[@code="' . $marcsubfield . '"]';
1882                     } else {
1883                         $sqlfield = 'metadata'; # From biblio_metadata
1884                         if ($marcfield < 10) {
1885                             $xpath = "//record/controlfield[\@tag=\"$marcfield\"]";
1886                         } else {
1887                             $xpath = "//record/datafield[\@tag=\"$marcfield\"]/subfield[\@code=\"$marcsubfield\"]";
1888                         }
1889                     }
1890                     $column = "ExtractValue($sqlfield, '$xpath')";
1891                 }
1892             } elsif ($field eq 'issues') {
1893                 # Consider NULL as 0 for issues count
1894                 $column = 'COALESCE(issues,0)';
1895             } else {
1896                 $column = $field;
1897             }
1898
1899             if (ref $query eq 'ARRAY') {
1900                 if ($op eq '=') {
1901                     $op = 'IN';
1902                 } elsif ($op eq '!=') {
1903                     $op = 'NOT IN';
1904                 }
1905                 $where_fragment = {
1906                     str => "$column $op (" . join (',', ('?') x @$query) . ")",
1907                     args => $query,
1908                 };
1909             } else {
1910                 $where_fragment = {
1911                     str => "$column $op ?",
1912                     args => [ $query ],
1913                 };
1914             }
1915         }
1916     }
1917
1918     return $where_fragment;
1919 }
1920
1921 =head2 SearchItems
1922
1923     my ($items, $total) = SearchItems($filter, $params);
1924
1925 Perform a search among items
1926
1927 $filter is a reference to a hash which can be a filter, or a combination of filters.
1928
1929 A filter has the following keys:
1930
1931 =over 2
1932
1933 =item * field: the name of a SQL column in table items
1934
1935 =item * query: the value to search in this column
1936
1937 =item * operator: comparison operator. Can be one of = != > < >= <= like
1938
1939 =back
1940
1941 A combination of filters hash the following keys:
1942
1943 =over 2
1944
1945 =item * conjunction: 'AND' or 'OR'
1946
1947 =item * filters: array ref of filters
1948
1949 =back
1950
1951 $params is a reference to a hash that can contain the following parameters:
1952
1953 =over 2
1954
1955 =item * rows: Number of items to return. 0 returns everything (default: 0)
1956
1957 =item * page: Page to return (return items from (page-1)*rows to (page*rows)-1)
1958                (default: 1)
1959
1960 =item * sortby: A SQL column name in items table to sort on
1961
1962 =item * sortorder: 'ASC' or 'DESC'
1963
1964 =back
1965
1966 =cut
1967
1968 sub SearchItems {
1969     my ($filter, $params) = @_;
1970
1971     $filter //= {};
1972     $params //= {};
1973     return unless ref $filter eq 'HASH';
1974     return unless ref $params eq 'HASH';
1975
1976     # Default parameters
1977     $params->{rows} ||= 0;
1978     $params->{page} ||= 1;
1979     $params->{sortby} ||= 'itemnumber';
1980     $params->{sortorder} ||= 'ASC';
1981
1982     my ($where_str, @where_args);
1983     my $where_fragment = _SearchItems_build_where_fragment($filter);
1984     if ($where_fragment) {
1985         $where_str = $where_fragment->{str};
1986         @where_args = @{ $where_fragment->{args} };
1987     }
1988
1989     my $dbh = C4::Context->dbh;
1990     my $query = q{
1991         SELECT SQL_CALC_FOUND_ROWS items.*
1992         FROM items
1993           LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
1994           LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
1995           LEFT JOIN biblio_metadata ON biblio_metadata.biblionumber = biblio.biblionumber
1996           WHERE 1
1997     };
1998     if (defined $where_str and $where_str ne '') {
1999         $query .= qq{ AND $where_str };
2000     }
2001
2002     $query .= q{ AND biblio_metadata.format = 'marcxml' AND biblio_metadata.schema = ? };
2003     push @where_args, C4::Context->preference('marcflavour');
2004
2005     my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
2006     push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
2007     push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
2008     my $sortby = (0 < grep {$params->{sortby} eq $_} @columns)
2009         ? $params->{sortby} : 'itemnumber';
2010     my $sortorder = (uc($params->{sortorder}) eq 'ASC') ? 'ASC' : 'DESC';
2011     $query .= qq{ ORDER BY $sortby $sortorder };
2012
2013     my $rows = $params->{rows};
2014     my @limit_args;
2015     if ($rows > 0) {
2016         my $offset = $rows * ($params->{page}-1);
2017         $query .= qq { LIMIT ?, ? };
2018         push @limit_args, $offset, $rows;
2019     }
2020
2021     my $sth = $dbh->prepare($query);
2022     my $rv = $sth->execute(@where_args, @limit_args);
2023
2024     return unless ($rv);
2025     my ($total_rows) = $dbh->selectrow_array(q{ SELECT FOUND_ROWS() });
2026
2027     return ($sth->fetchall_arrayref({}), $total_rows);
2028 }
2029
2030
2031 =head1  OTHER FUNCTIONS
2032
2033 =head2 _find_value
2034
2035   ($indicators, $value) = _find_value($tag, $subfield, $record,$encoding);
2036
2037 Find the given $subfield in the given $tag in the given
2038 MARC::Record $record.  If the subfield is found, returns
2039 the (indicators, value) pair; otherwise, (undef, undef) is
2040 returned.
2041
2042 PROPOSITION :
2043 Such a function is used in addbiblio AND additem and serial-edit and maybe could be used in Authorities.
2044 I suggest we export it from this module.
2045
2046 =cut
2047
2048 sub _find_value {
2049     my ( $tagfield, $insubfield, $record, $encoding ) = @_;
2050     my @result;
2051     my $indicator;
2052     if ( $tagfield < 10 ) {
2053         if ( $record->field($tagfield) ) {
2054             push @result, $record->field($tagfield)->data();
2055         } else {
2056             push @result, "";
2057         }
2058     } else {
2059         foreach my $field ( $record->field($tagfield) ) {
2060             my @subfields = $field->subfields();
2061             foreach my $subfield (@subfields) {
2062                 if ( @$subfield[0] eq $insubfield ) {
2063                     push @result, @$subfield[1];
2064                     $indicator = $field->indicator(1) . $field->indicator(2);
2065                 }
2066             }
2067         }
2068     }
2069     return ( $indicator, @result );
2070 }
2071
2072
2073 =head2 PrepareItemrecordDisplay
2074
2075   PrepareItemrecordDisplay($itemrecord,$bibnum,$itemumber,$frameworkcode);
2076
2077 Returns a hash with all the fields for Display a given item data in a template
2078
2079 The $frameworkcode returns the item for the given frameworkcode, ONLY if bibnum is not provided
2080
2081 =cut
2082
2083 sub PrepareItemrecordDisplay {
2084
2085     my ( $bibnum, $itemnum, $defaultvalues, $frameworkcode ) = @_;
2086
2087     my $dbh = C4::Context->dbh;
2088     $frameworkcode = C4::Biblio::GetFrameworkCode($bibnum) if $bibnum;
2089     my ( $itemtagfield, $itemtagsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
2090
2091     # Note: $tagslib obtained from GetMarcStructure() in 'unsafe' mode is
2092     # a shared data structure. No plugin (including custom ones) should change
2093     # its contents. See also GetMarcStructure.
2094     my $tagslib = GetMarcStructure( 1, $frameworkcode, { unsafe => 1 } );
2095
2096     # return nothing if we don't have found an existing framework.
2097     return q{} unless $tagslib;
2098     my $itemrecord;
2099     if ($itemnum) {
2100         $itemrecord = C4::Items::GetMarcItem( $bibnum, $itemnum );
2101     }
2102     my @loop_data;
2103
2104     my $branch_limit = C4::Context->userenv ? C4::Context->userenv->{"branch"} : "";
2105     my $query = qq{
2106         SELECT authorised_value,lib FROM authorised_values
2107     };
2108     $query .= qq{
2109         LEFT JOIN authorised_values_branches ON ( id = av_id )
2110     } if $branch_limit;
2111     $query .= qq{
2112         WHERE category = ?
2113     };
2114     $query .= qq{ AND ( branchcode = ? OR branchcode IS NULL )} if $branch_limit;
2115     $query .= qq{ ORDER BY lib};
2116     my $authorised_values_sth = $dbh->prepare( $query );
2117     foreach my $tag ( sort keys %{$tagslib} ) {
2118         if ( $tag ne '' ) {
2119
2120             # loop through each subfield
2121             my $cntsubf;
2122             foreach my $subfield ( sort keys %{ $tagslib->{$tag} } ) {
2123                 next if IsMarcStructureInternal($tagslib->{$tag}{$subfield});
2124                 next unless ( $tagslib->{$tag}->{$subfield}->{'tab'} );
2125                 next if ( $tagslib->{$tag}->{$subfield}->{'tab'} ne "10" );
2126                 my %subfield_data;
2127                 $subfield_data{tag}           = $tag;
2128                 $subfield_data{subfield}      = $subfield;
2129                 $subfield_data{countsubfield} = $cntsubf++;
2130                 $subfield_data{kohafield}     = $tagslib->{$tag}->{$subfield}->{'kohafield'};
2131                 $subfield_data{id}            = "tag_".$tag."_subfield_".$subfield."_".int(rand(1000000));
2132
2133                 #        $subfield_data{marc_lib}=$tagslib->{$tag}->{$subfield}->{lib};
2134                 $subfield_data{marc_lib}   = $tagslib->{$tag}->{$subfield}->{lib};
2135                 $subfield_data{mandatory}  = $tagslib->{$tag}->{$subfield}->{mandatory};
2136                 $subfield_data{repeatable} = $tagslib->{$tag}->{$subfield}->{repeatable};
2137                 $subfield_data{hidden}     = "display:none"
2138                   if ( ( $tagslib->{$tag}->{$subfield}->{hidden} > 4 )
2139                     || ( $tagslib->{$tag}->{$subfield}->{hidden} < -4 ) );
2140                 my ( $x, $defaultvalue );
2141                 if ($itemrecord) {
2142                     ( $x, $defaultvalue ) = _find_value( $tag, $subfield, $itemrecord );
2143                 }
2144                 $defaultvalue = $tagslib->{$tag}->{$subfield}->{defaultvalue} unless $defaultvalue;
2145                 if ( !defined $defaultvalue ) {
2146                     $defaultvalue = q||;
2147                 } else {
2148                     $defaultvalue =~ s/"/&quot;/g;
2149                 }
2150
2151                 my $maxlength = $tagslib->{$tag}->{$subfield}->{maxlength};
2152
2153                 # search for itemcallnumber if applicable
2154                 if ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
2155                     && C4::Context->preference('itemcallnumber') && $itemrecord) {
2156                     foreach my $itemcn_pref (split(/,/,C4::Context->preference('itemcallnumber'))){
2157                         my $CNtag      = substr( $itemcn_pref, 0, 3 );
2158                         next unless my $field = $itemrecord->field($CNtag);
2159                         my $CNsubfields = substr( $itemcn_pref, 3 );
2160                         $defaultvalue = $field->as_string( $CNsubfields, ' ');
2161                         last if $defaultvalue;
2162                     }
2163                 }
2164                 if (   $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
2165                     && $defaultvalues
2166                     && $defaultvalues->{'callnumber'} ) {
2167                     if( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ){
2168                         # if the item record exists, only use default value if the item has no callnumber
2169                         $defaultvalue = $defaultvalues->{callnumber};
2170                     } elsif ( !$itemrecord and $defaultvalues ) {
2171                         # if the item record *doesn't* exists, always use the default value
2172                         $defaultvalue = $defaultvalues->{callnumber};
2173                     }
2174                 }
2175                 if (   ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.holdingbranch' || $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.homebranch' )
2176                     && $defaultvalues
2177                     && $defaultvalues->{'branchcode'} ) {
2178                     if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) {
2179                         $defaultvalue = $defaultvalues->{branchcode};
2180                     }
2181                 }
2182                 if (   ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.location' )
2183                     && $defaultvalues
2184                     && $defaultvalues->{'location'} ) {
2185
2186                     if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) {
2187                         # if the item record exists, only use default value if the item has no locationr
2188                         $defaultvalue = $defaultvalues->{location};
2189                     } elsif ( !$itemrecord and $defaultvalues ) {
2190                         # if the item record *doesn't* exists, always use the default value
2191                         $defaultvalue = $defaultvalues->{location};
2192                     }
2193                 }
2194                 if ( $tagslib->{$tag}->{$subfield}->{authorised_value} ) {
2195                     my @authorised_values;
2196                     my %authorised_lib;
2197
2198                     # builds list, depending on authorised value...
2199                     #---- branch
2200                     if ( $tagslib->{$tag}->{$subfield}->{'authorised_value'} eq "branches" ) {
2201                         if (   ( C4::Context->preference("IndependentBranches") )
2202                             && !C4::Context->IsSuperLibrarian() ) {
2203                             my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches WHERE branchcode = ? ORDER BY branchname" );
2204                             $sth->execute( C4::Context->userenv->{branch} );
2205                             push @authorised_values, ""
2206                               unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2207                             while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
2208                                 push @authorised_values, $branchcode;
2209                                 $authorised_lib{$branchcode} = $branchname;
2210                             }
2211                         } else {
2212                             my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches ORDER BY branchname" );
2213                             $sth->execute;
2214                             push @authorised_values, ""
2215                               unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2216                             while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
2217                                 push @authorised_values, $branchcode;
2218                                 $authorised_lib{$branchcode} = $branchname;
2219                             }
2220                         }
2221
2222                         $defaultvalue = C4::Context->userenv ? C4::Context->userenv->{branch} : undef;
2223                         if ( $defaultvalues and $defaultvalues->{branchcode} ) {
2224                             $defaultvalue = $defaultvalues->{branchcode};
2225                         }
2226
2227                         #----- itemtypes
2228                     } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "itemtypes" ) {
2229                         my $itemtypes = Koha::ItemTypes->search_with_localization;
2230                         push @authorised_values, ""
2231                           unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2232                         while ( my $itemtype = $itemtypes->next ) {
2233                             push @authorised_values, $itemtype->itemtype;
2234                             $authorised_lib{$itemtype->itemtype} = $itemtype->translated_description;
2235                         }
2236                         if ($defaultvalues && $defaultvalues->{'itemtype'}) {
2237                             $defaultvalue = $defaultvalues->{'itemtype'};
2238                         }
2239
2240                         #---- class_sources
2241                     } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "cn_source" ) {
2242                         push @authorised_values, "" unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2243
2244                         my $class_sources = GetClassSources();
2245                         my $default_source = $defaultvalue || C4::Context->preference("DefaultClassificationSource");
2246
2247                         foreach my $class_source (sort keys %$class_sources) {
2248                             next unless $class_sources->{$class_source}->{'used'} or
2249                                         ($class_source eq $default_source);
2250                             push @authorised_values, $class_source;
2251                             $authorised_lib{$class_source} = $class_sources->{$class_source}->{'description'};
2252                         }
2253
2254                         $defaultvalue = $default_source;
2255
2256                         #---- "true" authorised value
2257                     } else {
2258                         $authorised_values_sth->execute(
2259                             $tagslib->{$tag}->{$subfield}->{authorised_value},
2260                             $branch_limit ? $branch_limit : ()
2261                         );
2262                         push @authorised_values, ""
2263                           unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
2264                         while ( my ( $value, $lib ) = $authorised_values_sth->fetchrow_array ) {
2265                             push @authorised_values, $value;
2266                             $authorised_lib{$value} = $lib;
2267                         }
2268                     }
2269                     $subfield_data{marc_value} = {
2270                         type    => 'select',
2271                         values  => \@authorised_values,
2272                         default => $defaultvalue // q{},
2273                         labels  => \%authorised_lib,
2274                     };
2275                 } elsif ( $tagslib->{$tag}->{$subfield}->{value_builder} ) {
2276                 # it is a plugin
2277                     require Koha::FrameworkPlugin;
2278                     my $plugin = Koha::FrameworkPlugin->new({
2279                         name => $tagslib->{$tag}->{$subfield}->{value_builder},
2280                         item_style => 1,
2281                     });
2282                     my $pars = { dbh => $dbh, record => undef, tagslib =>$tagslib, id => $subfield_data{id}, tabloop => undef };
2283                     $plugin->build( $pars );
2284                     if ( $itemrecord and my $field = $itemrecord->field($tag) ) {
2285                         $defaultvalue = $field->subfield($subfield) || q{};
2286                     }
2287                     if( !$plugin->errstr ) {
2288                         #TODO Move html to template; see report 12176/13397
2289                         my $tab= $plugin->noclick? '-1': '';
2290                         my $class= $plugin->noclick? ' disabled': '';
2291                         my $title= $plugin->noclick? 'No popup': 'Tag editor';
2292                         $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;
2293                     } else {
2294                         warn $plugin->errstr;
2295                         $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
2296                     }
2297                 }
2298                 elsif ( $tag eq '' ) {       # it's an hidden field
2299                     $subfield_data{marc_value} = qq(<input type="hidden" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
2300                 }
2301                 elsif ( $tagslib->{$tag}->{$subfield}->{'hidden'} ) {   # FIXME: shouldn't input type be "hidden" ?
2302                     $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
2303                 }
2304                 elsif ( length($defaultvalue) > 100
2305                             or (C4::Context->preference("marcflavour") eq "UNIMARC" and
2306                                   300 <= $tag && $tag < 400 && $subfield eq 'a' )
2307                             or (C4::Context->preference("marcflavour") eq "MARC21"  and
2308                                   500 <= $tag && $tag < 600                     )
2309                           ) {
2310                     # oversize field (textarea)
2311                     $subfield_data{marc_value} = qq(<textarea id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength">$defaultvalue</textarea>\n");
2312                 } else {
2313                     $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
2314                 }
2315                 push( @loop_data, \%subfield_data );
2316             }
2317         }
2318     }
2319     my $itemnumber;
2320     if ( $itemrecord && $itemrecord->field($itemtagfield) ) {
2321         $itemnumber = $itemrecord->subfield( $itemtagfield, $itemtagsubfield );
2322     }
2323     return {
2324         'itemtagfield'    => $itemtagfield,
2325         'itemtagsubfield' => $itemtagsubfield,
2326         'itemnumber'      => $itemnumber,
2327         'iteminformation' => \@loop_data
2328     };
2329 }
2330
2331 sub ToggleNewStatus {
2332     my ( $params ) = @_;
2333     my @rules = @{ $params->{rules} };
2334     my $report_only = $params->{report_only};
2335
2336     my $dbh = C4::Context->dbh;
2337     my @errors;
2338     my @item_columns = map { "items.$_" } Koha::Items->columns;
2339     my @biblioitem_columns = map { "biblioitems.$_" } Koha::Biblioitems->columns;
2340     my $report;
2341     for my $rule ( @rules ) {
2342         my $age = $rule->{age};
2343         my $conditions = $rule->{conditions};
2344         my $substitutions = $rule->{substitutions};
2345         foreach ( @$substitutions ) {
2346             ( $_->{item_field} ) = ( $_->{field} =~ /items\.(.*)/ );
2347         }
2348         my @params;
2349
2350         my $query = q|
2351             SELECT items.*
2352             FROM items
2353             LEFT JOIN biblioitems ON biblioitems.biblionumber = items.biblionumber
2354             WHERE 1
2355         |;
2356         for my $condition ( @$conditions ) {
2357             if (
2358                  grep { $_ eq $condition->{field} } @item_columns
2359               or grep { $_ eq $condition->{field} } @biblioitem_columns
2360             ) {
2361                 if ( $condition->{value} =~ /\|/ ) {
2362                     my @values = split /\|/, $condition->{value};
2363                     $query .= qq| AND $condition->{field} IN (|
2364                         . join( ',', ('?') x scalar @values )
2365                         . q|)|;
2366                     push @params, @values;
2367                 } else {
2368                     $query .= qq| AND $condition->{field} = ?|;
2369                     push @params, $condition->{value};
2370                 }
2371             }
2372         }
2373         if ( defined $age ) {
2374             $query .= q| AND TO_DAYS(NOW()) - TO_DAYS(dateaccessioned) >= ? |;
2375             push @params, $age;
2376         }
2377         my $sth = $dbh->prepare($query);
2378         $sth->execute( @params );
2379         while ( my $values = $sth->fetchrow_hashref ) {
2380             my $biblionumber = $values->{biblionumber};
2381             my $itemnumber = $values->{itemnumber};
2382             my $item = Koha::Items->find($itemnumber);
2383             for my $substitution ( @$substitutions ) {
2384                 my $field = $substitution->{item_field};
2385                 my $value = $substitution->{value};
2386                 next unless $substitution->{field};
2387                 next if ( defined $values->{ $substitution->{item_field} } and $values->{ $substitution->{item_field} } eq $substitution->{value} );
2388                 $item->$field($value);
2389                 push @{ $report->{$itemnumber} }, $substitution;
2390             }
2391             $item->store unless $report_only;
2392         }
2393     }
2394
2395     return $report;
2396 }
2397
2398 1;