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