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