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