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