X-Git-Url: http://git.rot13.org/?a=blobdiff_plain;f=C4%2FItems.pm;h=8f3ef3346900fe53aa77e05c7ed6b51f7c9f57b5;hb=529ed6faa43b6c2543695ec594fe891139cca83f;hp=0194787c50989b7a399d308cfc2b1bf1d4de5ff2;hpb=c7a6745bc0e0241e5f39a12e2ff4e17678288b14;p=koha.git diff --git a/C4/Items.pm b/C4/Items.pm index 0194787c50..8f3ef33469 100644 --- a/C4/Items.pm +++ b/C4/Items.pm @@ -21,80 +21,67 @@ package C4::Items; use strict; #use warnings; FIXME - Bug 2505 -use Carp; -use C4::Context; -use C4::Koha; -use C4::Biblio; -use C4::Dates qw/format_date format_date_in_iso/; -use MARC::Record; -use C4::ClassSource; -use C4::Log; -use List::MoreUtils qw/any/; -use YAML qw/Load/; -use DateTime::Format::MySQL; -use Data::Dumper; # used as part of logging item record changes, not just for - # debugging; so please don't remove this -use Koha::DateUtils qw/dt_from_string/; - -use Koha::Database; - -use vars qw($VERSION @ISA @EXPORT); - +use vars qw(@ISA @EXPORT); BEGIN { - $VERSION = 3.07.00.049; - - require Exporter; - @ISA = qw( Exporter ); + require Exporter; + @ISA = qw(Exporter); - # function exports @EXPORT = qw( GetItem AddItemFromMarc AddItem AddItemBatchFromMarc ModItemFromMarc - Item2Marc + Item2Marc ModItem ModDateLastSeen ModItemTransfer DelItem - CheckItemPreSave - - GetItemStatus - GetItemLocation - GetLostItems GetItemsForInventory - GetItemsCount - GetItemInfosOf - GetItemsByBiblioitemnumber GetItemsInfo - GetItemsLocationInfo - GetHostItemsInfo - GetItemnumbersForBiblio - get_itemnumbers_of - get_hostitemnumbers_of - GetItemnumberFromBarcode - GetBarcodeFromItemnumber + GetItemsLocationInfo + GetHostItemsInfo + get_hostitemnumbers_of GetHiddenItemnumbers + ItemSafeToDelete DelItemCheck - MoveItemFromBiblio - GetLatestAcquisitions - + MoveItemFromBiblio + GetLastAcquisitions CartToShelf ShelfToCart - - GetAnalyticsCount - GetItemHolds - + GetAnalyticsCount SearchItemsByField SearchItems - PrepareItemrecordDisplay - ); } +use Carp; +use C4::Context; +use C4::Koha; +use C4::Biblio; +use Koha::DateUtils; +use MARC::Record; +use C4::ClassSource; +use C4::Log; +use List::MoreUtils qw(any); +use YAML qw(Load); +use DateTime::Format::MySQL; +use Data::Dumper; # used as part of logging item record changes, not just for + # debugging; so please don't remove this + +use Koha::AuthorisedValues; +use Koha::DateUtils qw(dt_from_string); +use Koha::Database; + +use Koha::Biblioitems; +use Koha::Items; +use Koha::ItemTypes; +use Koha::SearchEngine; +use Koha::SearchEngine::Search; +use Koha::Libraries; + =head1 NAME C4::Items - item management functions @@ -105,6 +92,7 @@ This module contains an API for manipulating item records in Koha, and is used by cataloguing, circulation, acquisitions, and serials management. +# FIXME This POD is not up-to-date A Koha item record is stored in two places: the items table and embedded in a MARC tag in the XML version of the associated bib record in C. @@ -149,38 +137,25 @@ names to values. If C<$serial> is true, include serial publication data. sub GetItem { my ($itemnumber,$barcode, $serial) = @_; my $dbh = C4::Context->dbh; - my $data; + my $item; if ($itemnumber) { - my $sth = $dbh->prepare(" - SELECT * FROM items - WHERE itemnumber = ?"); - $sth->execute($itemnumber); - $data = $sth->fetchrow_hashref; + $item = Koha::Items->find( $itemnumber ); } else { - my $sth = $dbh->prepare(" - SELECT * FROM items - WHERE barcode = ?" - ); - $sth->execute($barcode); - $data = $sth->fetchrow_hashref; - } - - return unless ( $data ); - - if ( $serial) { - my $ssth = $dbh->prepare("SELECT serialseq,publisheddate from serialitems left join serial on serialitems.serialid=serial.serialid where serialitems.itemnumber=?"); - $ssth->execute($data->{'itemnumber'}) ; - ($data->{'serialseq'} , $data->{'publisheddate'}) = $ssth->fetchrow_array(); - } - #if we don't have an items.itype, use biblioitems.itemtype. - # FIXME this should respect the itypes systempreference - # if (C4::Context->preference('item-level_itypes')) { - if( ! $data->{'itype'} ) { - my $sth = $dbh->prepare("SELECT itemtype FROM biblioitems WHERE biblionumber = ?"); - $sth->execute($data->{'biblionumber'}); - ($data->{'itype'}) = $sth->fetchrow_array; - } + $item = Koha::Items->find( { barcode => $barcode } ); + } + + return unless ( $item ); + + my $data = $item->unblessed(); + $data->{itype} = $item->effective_itemtype(); # set the correct itype + + if ($serial) { + my $ssth = $dbh->prepare("SELECT serialseq,publisheddate from serialitems left join serial on serialitems.serialid=serial.serialid where serialitems.itemnumber=?"); + $ssth->execute( $data->{'itemnumber'} ); + ( $data->{'serialseq'}, $data->{'publisheddate'} ) = $ssth->fetchrow_array(); + } + return $data; } # sub GetItem @@ -246,12 +221,12 @@ sub AddItemFromMarc { my $dbh = C4::Context->dbh; # parse item hash from MARC - my $frameworkcode = GetFrameworkCode( $biblionumber ); - my ($itemtag,$itemsubfield)=GetMarcFromKohaField("items.itemnumber",$frameworkcode); - - my $localitemmarc=MARC::Record->new; - $localitemmarc->append_fields($source_item_marc->field($itemtag)); - my $item = &TransformMarcToKoha( $dbh, $localitemmarc, $frameworkcode ,'items'); + my $frameworkcode = C4::Biblio::GetFrameworkCode( $biblionumber ); + my ($itemtag,$itemsubfield)=C4::Biblio::GetMarcFromKohaField("items.itemnumber",$frameworkcode); + + my $localitemmarc=MARC::Record->new; + $localitemmarc->append_fields($source_item_marc->field($itemtag)); + my $item = TransformMarcToKoha( $localitemmarc, $frameworkcode ,'items'); my $unlinked_item_subfields = _get_unlinked_item_subfields($localitemmarc, $frameworkcode); return AddItem($item, $biblionumber, $dbh, $frameworkcode, $unlinked_item_subfields); } @@ -278,40 +253,44 @@ the biblio items tag for display and indexing. =cut sub AddItem { - my $item = shift; + my $item = shift; my $biblionumber = shift; my $dbh = @_ ? shift : C4::Context->dbh; - my $frameworkcode = @_ ? shift : GetFrameworkCode( $biblionumber ); - my $unlinked_item_subfields; + my $frameworkcode = @_ ? shift : C4::Biblio::GetFrameworkCode($biblionumber); + my $unlinked_item_subfields; if (@_) { - $unlinked_item_subfields = shift - }; + $unlinked_item_subfields = shift; + } # needs old biblionumber and biblioitemnumber $item->{'biblionumber'} = $biblionumber; my $sth = $dbh->prepare("SELECT biblioitemnumber FROM biblioitems WHERE biblionumber=?"); $sth->execute( $item->{'biblionumber'} ); - ($item->{'biblioitemnumber'}) = $sth->fetchrow; + ( $item->{'biblioitemnumber'} ) = $sth->fetchrow; _set_defaults_for_add($item); _set_derived_columns_for_add($item); $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields); + # FIXME - checks here - unless ( $item->{itype} ) { # default to biblioitem.itemtype if no itype + unless ( $item->{itype} ) { # default to biblioitem.itemtype if no itype my $itype_sth = $dbh->prepare("SELECT itemtype FROM biblioitems WHERE biblionumber = ?"); $itype_sth->execute( $item->{'biblionumber'} ); ( $item->{'itype'} ) = $itype_sth->fetchrow_array; } - my ( $itemnumber, $error ) = _koha_new_item( $item, $item->{barcode} ); + my ( $itemnumber, $error ) = _koha_new_item( $item, $item->{barcode} ); + return if $error; + $item->{'itemnumber'} = $itemnumber; ModZebra( $item->{biblionumber}, "specialUpdate", "biblioserver" ); - - logaction("CATALOGUING", "ADD", $itemnumber, "item") if C4::Context->preference("CataloguingLog"); - - return ($item->{biblionumber}, $item->{biblioitemnumber}, $itemnumber); + + logaction( "CATALOGUING", "ADD", $itemnumber, "item" ) + if C4::Context->preference("CataloguingLog"); + + return ( $item->{biblionumber}, $item->{biblioitemnumber}, $itemnumber ); } =head2 AddItemBatchFromMarc @@ -324,8 +303,8 @@ embedded item fields. This routine is suitable for batch jobs. This API assumes that the bib record has already been saved to the C and C tables. It does -not expect that C and C -are populated, but it will do so via a call to ModBibiloMarc. +not expect that C is populated, but it +will do so via a call to ModBibiloMarc. The goal of this API is to have a similar effect to using AddBiblio and AddItems in succession, but without inefficient repeated @@ -371,7 +350,7 @@ sub AddItemBatchFromMarc { $record = $record->clone(); # loop through the item tags and start creating items my @bad_item_fields = (); - my ($itemtag, $itemsubfield) = &GetMarcFromKohaField("items.itemnumber",''); + my ($itemtag, $itemsubfield) = C4::Biblio::GetMarcFromKohaField("items.itemnumber",''); my $item_sequence_num = 0; ITEMFIELD: foreach my $item_field ($record->field($itemtag)) { $item_sequence_num++; @@ -383,7 +362,7 @@ sub AddItemBatchFromMarc { $temp_item_marc->append_fields($item_field); # add biblionumber and biblioitemnumber - my $item = TransformMarcToKoha( $dbh, $temp_item_marc, $frameworkcode, 'items' ); + my $item = TransformMarcToKoha( $temp_item_marc, $frameworkcode, 'items' ); my $unlinked_item_subfields = _get_unlinked_item_subfields($temp_item_marc, $frameworkcode); $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields); $item->{'biblionumber'} = $biblionumber; @@ -447,13 +426,15 @@ Returns item record =cut -our %default_values_for_mod_from_marc; - sub _build_default_values_for_mod_marc { - my ($frameworkcode) = @_; - return $default_values_for_mod_from_marc{$frameworkcode} - if exists $default_values_for_mod_from_marc{$frameworkcode}; - my $marc_structure = C4::Biblio::GetMarcStructure( 1, $frameworkcode ); + # Has no framework parameter anymore, since Default is authoritative + # for Koha to MARC mappings. + + my $cache = Koha::Caches->get_instance(); + my $cache_key = "default_value_for_mod_marc-"; + my $cached = $cache->get_from_cache($cache_key); + return $cached if $cached; + my $default_values = { barcode => undef, booksellerid => undef, @@ -473,6 +454,7 @@ sub _build_default_values_for_mod_marc { location => undef, permanent_location => undef, materials => undef, + new_status => undef, notforloan => 0, # paidfor => undef, # commented, see bug 12817 price => undef, @@ -484,15 +466,16 @@ sub _build_default_values_for_mod_marc { uri => undef, withdrawn => 0, }; + my %default_values_for_mod_from_marc; while ( my ( $field, $default_value ) = each %$default_values ) { my $kohafield = $field; $kohafield =~ s|^([^\.]+)$|items.$1|; - $default_values_for_mod_from_marc{$frameworkcode}{$field} = - $default_value - if C4::Koha::IsKohaFieldLinked( - { kohafield => $kohafield, frameworkcode => $frameworkcode } ); + $default_values_for_mod_from_marc{$field} = $default_value + if C4::Biblio::GetMarcFromKohaField( $kohafield ); } - return $default_values_for_mod_from_marc{$frameworkcode}; + + $cache->set_in_cache($cache_key, \%default_values_for_mod_from_marc); + return \%default_values_for_mod_from_marc; } sub ModItemFromMarc { @@ -500,13 +483,12 @@ sub ModItemFromMarc { my $biblionumber = shift; my $itemnumber = shift; - my $dbh = C4::Context->dbh; - my $frameworkcode = GetFrameworkCode($biblionumber); - my ( $itemtag, $itemsubfield ) = GetMarcFromKohaField( "items.itemnumber", $frameworkcode ); + my $frameworkcode = C4::Biblio::GetFrameworkCode($biblionumber); + my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber", $frameworkcode ); my $localitemmarc = MARC::Record->new; $localitemmarc->append_fields( $item_marc->field($itemtag) ); - my $item = &TransformMarcToKoha( $dbh, $localitemmarc, $frameworkcode, 'items' ); + my $item = TransformMarcToKoha( $localitemmarc, $frameworkcode, 'items' ); my $default_values = _build_default_values_for_mod_marc(); foreach my $item_field ( keys %$default_values ) { $item->{$item_field} = $default_values->{$item_field} @@ -514,13 +496,21 @@ sub ModItemFromMarc { } my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode ); - ModItem($item, $biblionumber, $itemnumber, $dbh, $frameworkcode, $unlinked_item_subfields); + ModItem( $item, $biblionumber, $itemnumber, { unlinked_item_subfields => $unlinked_item_subfields } ); return $item; } =head2 ModItem - ModItem({ column => $newvalue }, $biblionumber, $itemnumber); +ModItem( + { column => $newvalue }, + $biblionumber, + $itemnumber, + { + [ unlinked_item_subfields => $unlinked_item_subfields, ] + [ log_action => 1, ] + } +); Change one or more columns in an item record and update the MARC representation of the item. @@ -528,9 +518,11 @@ the MARC representation of the item. The first argument is a hashref mapping from item column names to the new values. The second and third arguments are the biblionumber and itemnumber, respectively. +The fourth, optional parameter (additional_params) may contain the keys +unlinked_item_subfields and log_action. -The fourth, optional parameter, C<$unlinked_item_subfields>, contains -an arrayref containing subfields present in the original MARC +C<$unlinked_item_subfields> contains an arrayref containing +subfields present in the original MARC representation of the item (e.g., from the item editor) that are not mapped to C columns directly but should instead be stored in C and included in @@ -541,33 +533,32 @@ the derived value of a column such as C, this routine will perform the necessary calculation and set the value. +If log_action is set to false, the action will not be logged. +If log_action is true or undefined, the action will be logged. + =cut sub ModItem { - my $item = shift; - my $biblionumber = shift; - my $itemnumber = shift; + my ( $item, $biblionumber, $itemnumber, $additional_params ) = @_; + my $log_action = $additional_params->{log_action} // 1; + my $unlinked_item_subfields = $additional_params->{unlinked_item_subfields}; + + return unless %$item; + $item->{'itemnumber'} = $itemnumber or return; # if $biblionumber is undefined, get it from the current item unless (defined $biblionumber) { $biblionumber = _get_single_item_column('biblionumber', $itemnumber); } - my $dbh = @_ ? shift : C4::Context->dbh; - my $frameworkcode = @_ ? shift : GetFrameworkCode( $biblionumber ); - - my $unlinked_item_subfields; - if (@_) { - $unlinked_item_subfields = shift; + if ($unlinked_item_subfields) { $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields); }; - $item->{'itemnumber'} = $itemnumber or return; - - my @fields = qw( itemlost withdrawn ); + my @fields = qw( itemlost withdrawn damaged ); # Only call GetItem if we need to set an "on" date field - if ( $item->{itemlost} || $item->{withdrawn} ) { + if ( $item->{itemlost} || $item->{withdrawn} || $item->{damaged} ) { my $pre_mod_item = GetItem( $item->{'itemnumber'} ); for my $field (@fields) { if ( defined( $item->{$field} ) @@ -605,7 +596,8 @@ sub ModItem { # item status is possible ModZebra( $biblionumber, "specialUpdate", "biblioserver" ); - logaction("CATALOGUING", "MODIFY", $itemnumber, "item ".Dumper($item)) if C4::Context->preference("CataloguingLog"); + logaction( "CATALOGUING", "MODIFY", $itemnumber, "item " . Dumper($item) ) + if $log_action && C4::Context->preference("CataloguingLog"); } =head2 ModItemTransfer @@ -625,6 +617,8 @@ sub ModItemTransfer { # Remove the 'shelving cart' location status if it is being used. CartToShelf( $itemnumber ) if ( C4::Context->preference("ReturnToShelvingCart") ); + $dbh->do("UPDATE branchtransfers SET datearrived = NOW(), comments = ? WHERE itemnumber = ? AND datearrived IS NULL", undef, "Canceled, new transfer from $frombranch to $tobranch created", $itemnumber); + #new entry in branchtransfers.... my $sth = $dbh->prepare( "INSERT INTO branchtransfers (itemnumber, frombranch, datesent, tobranch) @@ -638,18 +632,24 @@ sub ModItemTransfer { =head2 ModDateLastSeen - ModDateLastSeen($itemnum); +ModDateLastSeen( $itemnumber, $leave_item_lost ); Mark item as seen. Is called when an item is issued, returned or manually marked during inventory/stocktaking. -C<$itemnum> is the item number +C<$itemnumber> is the item number +C<$leave_item_lost> determines if a lost item will be found or remain lost =cut sub ModDateLastSeen { - my ($itemnumber) = @_; - - my $today = C4::Dates->new(); - ModItem({ itemlost => 0, datelastseen => $today->output("iso") }, undef, $itemnumber); + my ( $itemnumber, $leave_item_lost ) = @_; + + my $today = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 }); + + my $params; + $params->{datelastseen} = $today; + $params->{itemlost} = 0 unless $leave_item_lost; + + ModItem( $params, undef, $itemnumber, { log_action => 0 } ); } =head2 DelItem @@ -667,7 +667,8 @@ sub DelItem { my $biblionumber = $params->{biblionumber}; unless ($biblionumber) { - $biblionumber = C4::Biblio::GetBiblionumberFromItemnumber($itemnumber); + my $item = Koha::Items->find( $itemnumber ); + $biblionumber = $item ? $item->biblio->biblionumber : undef; } # If there is no biblionumber for the given itemnumber, there is nothing to delete @@ -676,8 +677,6 @@ sub DelItem { # FIXME check the item has no current issues my $deleted = _koha_delete_item( $itemnumber ); - # get the MARC record - my $record = GetMarcBiblio($biblionumber); ModZebra( $biblionumber, "specialUpdate", "biblioserver" ); #search item field code @@ -729,16 +728,15 @@ item that has a given branch code. sub CheckItemPreSave { my $item_ref = shift; - require C4::Branch; my %errors = (); # check for duplicate barcode if (exists $item_ref->{'barcode'} and defined $item_ref->{'barcode'}) { - my $existing_itemnumber = GetItemnumberFromBarcode($item_ref->{'barcode'}); - if ($existing_itemnumber) { + my $existing_item= Koha::Items->find({barcode => $item_ref->{'barcode'}}); + if ($existing_item) { if (!exists $item_ref->{'itemnumber'} # new item - or $item_ref->{'itemnumber'} != $existing_itemnumber) { # existing item + or $item_ref->{'itemnumber'} != $existing_item->itemnumber) { # existing item $errors{'duplicate_barcode'} = $item_ref->{'barcode'}; } } @@ -746,20 +744,16 @@ sub CheckItemPreSave { # check for valid home branch if (exists $item_ref->{'homebranch'} and defined $item_ref->{'homebranch'}) { - my $branch_name = C4::Branch::GetBranchName($item_ref->{'homebranch'}); - unless (defined $branch_name) { - # relies on fact that branches.branchname is a non-NULL column, - # so GetBranchName returns undef only if branch does not exist + my $home_library = Koha::Libraries->find( $item_ref->{homebranch} ); + unless (defined $home_library) { $errors{'invalid_homebranch'} = $item_ref->{'homebranch'}; } } # check for valid holding branch if (exists $item_ref->{'holdingbranch'} and defined $item_ref->{'holdingbranch'}) { - my $branch_name = C4::Branch::GetBranchName($item_ref->{'holdingbranch'}); - unless (defined $branch_name) { - # relies on fact that branches.branchname is a non-NULL column, - # so GetBranchName returns undef only if branch does not exist + my $holding_library = Koha::Libraries->find( $item_ref->{holdingbranch} ); + unless (defined $holding_library) { $errors{'invalid_holdingbranch'} = $item_ref->{'holdingbranch'}; } } @@ -774,254 +768,8 @@ The following functions provide various ways of getting an item record, a set of item records, or lists of authorized values for certain item fields. -Some of the functions in this group are candidates -for refactoring -- for example, some of the code -in C and C -has copy-and-paste work. - =cut -=head2 GetItemStatus - - $itemstatushash = GetItemStatus($fwkcode); - -Returns a list of valid values for the -C field. - -NOTE: does B return an individual item's -status. - -Can be MARC dependent. -fwkcode is optional. -But basically could be can be loan or not -Create a status selector with the following code - -=head3 in PERL SCRIPT - - my $itemstatushash = getitemstatus; - my @itemstatusloop; - foreach my $thisstatus (keys %$itemstatushash) { - my %row =(value => $thisstatus, - statusname => $itemstatushash->{$thisstatus}->{'statusname'}, - ); - push @itemstatusloop, \%row; - } - $template->param(statusloop=>\@itemstatusloop); - -=head3 in TEMPLATE - - - -=cut - -sub GetItemStatus { - - # returns a reference to a hash of references to status... - my ($fwk) = @_; - my %itemstatus; - my $dbh = C4::Context->dbh; - my $sth; - $fwk = '' unless ($fwk); - my ( $tag, $subfield ) = - GetMarcFromKohaField( "items.notforloan", $fwk ); - if ( $tag and $subfield ) { - my $sth = - $dbh->prepare( - "SELECT authorised_value - FROM marc_subfield_structure - WHERE tagfield=? - AND tagsubfield=? - AND frameworkcode=? - " - ); - $sth->execute( $tag, $subfield, $fwk ); - if ( my ($authorisedvaluecat) = $sth->fetchrow ) { - my $authvalsth = - $dbh->prepare( - "SELECT authorised_value,lib - FROM authorised_values - WHERE category=? - ORDER BY lib - " - ); - $authvalsth->execute($authorisedvaluecat); - while ( my ( $authorisedvalue, $lib ) = $authvalsth->fetchrow ) { - $itemstatus{$authorisedvalue} = $lib; - } - return \%itemstatus; - exit 1; - } - else { - - #No authvalue list - # build default - } - } - - #No authvalue list - #build default - $itemstatus{"1"} = "Not For Loan"; - return \%itemstatus; -} - -=head2 GetItemLocation - - $itemlochash = GetItemLocation($fwk); - -Returns a list of valid values for the -C field. - -NOTE: does B return an individual item's -location. - -where fwk stands for an optional framework code. -Create a location selector with the following code - -=head3 in PERL SCRIPT - - my $itemlochash = getitemlocation; - my @itemlocloop; - foreach my $thisloc (keys %$itemlochash) { - my $selected = 1 if $thisbranch eq $branch; - my %row =(locval => $thisloc, - selected => $selected, - locname => $itemlochash->{$thisloc}, - ); - push @itemlocloop, \%row; - } - $template->param(itemlocationloop => \@itemlocloop); - -=head3 in TEMPLATE - - - -=cut - -sub GetItemLocation { - - # returns a reference to a hash of references to location... - my ($fwk) = @_; - my %itemlocation; - my $dbh = C4::Context->dbh; - my $sth; - $fwk = '' unless ($fwk); - my ( $tag, $subfield ) = - GetMarcFromKohaField( "items.location", $fwk ); - if ( $tag and $subfield ) { - my $sth = - $dbh->prepare( - "SELECT authorised_value - FROM marc_subfield_structure - WHERE tagfield=? - AND tagsubfield=? - AND frameworkcode=?" - ); - $sth->execute( $tag, $subfield, $fwk ); - if ( my ($authorisedvaluecat) = $sth->fetchrow ) { - my $authvalsth = - $dbh->prepare( - "SELECT authorised_value,lib - FROM authorised_values - WHERE category=? - ORDER BY lib" - ); - $authvalsth->execute($authorisedvaluecat); - while ( my ( $authorisedvalue, $lib ) = $authvalsth->fetchrow ) { - $itemlocation{$authorisedvalue} = $lib; - } - return \%itemlocation; - exit 1; - } - else { - - #No authvalue list - # build default - } - } - - #No authvalue list - #build default - $itemlocation{"1"} = "Not For Loan"; - return \%itemlocation; -} - -=head2 GetLostItems - - $items = GetLostItems( $where ); - -This function gets a list of lost items. - -=over 2 - -=item input: - -C<$where> is a hashref. it containts a field of the items table as key -and the value to match as value. For example: - -{ barcode => 'abc123', - homebranch => 'CPL', } - -=item return: - -C<$items> is a reference to an array full of hashrefs with columns -from the "items" table as keys. - -=item usage in the perl script: - - my $where = { barcode => '0001548' }; - my $items = GetLostItems( $where ); - $template->param( itemsloop => $items ); - -=back - -=cut - -sub GetLostItems { - # Getting input args. - my $where = shift; - my $dbh = C4::Context->dbh; - - my $query = " - SELECT title, author, lib, itemlost, authorised_value, barcode, datelastseen, price, replacementprice, homebranch, - itype, itemtype, holdingbranch, location, itemnotes, items.biblionumber as biblionumber, itemcallnumber - FROM items - LEFT JOIN biblio ON (items.biblionumber = biblio.biblionumber) - LEFT JOIN biblioitems ON (items.biblionumber = biblioitems.biblionumber) - LEFT JOIN authorised_values ON (items.itemlost = authorised_values.authorised_value) - WHERE - authorised_values.category = 'LOST' - AND itemlost IS NOT NULL - AND itemlost <> 0 - "; - my @query_parameters; - foreach my $key (keys %$where) { - $query .= " AND $key LIKE ?"; - push @query_parameters, "%$where->{$key}%"; - } - - my $sth = $dbh->prepare($query); - $sth->execute( @query_parameters ); - my $items = []; - while ( my $row = $sth->fetchrow_hashref ){ - push @$items, $row; - } - return $items; -} - =head2 GetItemsForInventory ($itemlist, $iTotalRecords) = GetItemsForInventory( { @@ -1035,8 +783,7 @@ sub GetLostItems { branch => $branch, offset => $offset, size => $size, - stautshash => $statushash - interface => $interface, + statushash => $statushash, } ); Retrieve a list of title/authors/barcode/callnumber, for biblio inventory. @@ -1067,7 +814,6 @@ sub GetItemsForInventory { my $offset = $parameters->{'offset'} // ''; my $size = $parameters->{'size'} // ''; my $statushash = $parameters->{'statushash'} // ''; - my $interface = $parameters->{'interface'} // ''; my $dbh = C4::Context->dbh; my ( @bind_params, @where_strings ); @@ -1101,7 +847,7 @@ sub GetItemsForInventory { } if ($datelastseen) { - $datelastseen = format_date_in_iso($datelastseen); + $datelastseen = output_pref({ str => $datelastseen, dateformat => 'iso', dateonly => 1 }); push @where_strings, '(datelastseen < ? OR datelastseen IS NULL)'; push @bind_params, $datelastseen; } @@ -1134,8 +880,8 @@ sub GetItemsForInventory { $query .= 'WHERE '; $query .= join ' AND ', @where_strings; } - $query .= ' ORDER BY items.cn_sort, itemcallnumber, title'; my $count_query = $select_count . $query; + $query .= ' ORDER BY items.cn_sort, itemcallnumber, title'; $query .= " LIMIT $offset, $size" if ($offset and $size); $query = $select_columns . $query; my $sth = $dbh->prepare($query); @@ -1147,9 +893,19 @@ sub GetItemsForInventory { $sth->execute( @bind_params ); my ($iTotalRecords) = $sth->fetchrow_array(); - my $avmapping = C4::Koha::GetKohaAuthorisedValuesMapping( { - interface => $interface - } ); + my @avs = Koha::AuthorisedValues->search( + { 'marc_subfield_structures.kohafield' => { '>' => '' }, + 'me.authorised_value' => { '>' => '' }, + }, + { join => { category => 'marc_subfield_structures' }, + distinct => ['marc_subfield_structures.kohafield, me.category, frameworkcode, me.authorised_value'], + '+select' => [ 'marc_subfield_structures.kohafield', 'marc_subfield_structures.frameworkcode', 'me.authorised_value', 'me.lib' ], + '+as' => [ 'kohafield', 'frameworkcode', 'authorised_value', 'lib' ], + } + ); + + my $avmapping = { map { $_->get_column('kohafield') . ',' . $_->get_column('frameworkcode') . ',' . $_->get_column('authorised_value') => $_->get_column('lib') } @avs }; + foreach my $row (@$tmpresults) { # Auth values @@ -1164,97 +920,6 @@ sub GetItemsForInventory { return (\@results, $iTotalRecords); } -=head2 GetItemsCount - - $count = &GetItemsCount( $biblionumber); - -This function return count of item with $biblionumber - -=cut - -sub GetItemsCount { - my ( $biblionumber ) = @_; - my $dbh = C4::Context->dbh; - my $query = "SELECT count(*) - FROM items - WHERE biblionumber=?"; - my $sth = $dbh->prepare($query); - $sth->execute($biblionumber); - my $count = $sth->fetchrow; - return ($count); -} - -=head2 GetItemInfosOf - - GetItemInfosOf(@itemnumbers); - -=cut - -sub GetItemInfosOf { - my @itemnumbers = @_; - - my $itemnumber_values = @itemnumbers ? join( ',', @itemnumbers ) : "''"; - - my $query = " - SELECT * - FROM items - WHERE itemnumber IN ($itemnumber_values) - "; - return get_infos_of( $query, 'itemnumber' ); -} - -=head2 GetItemsByBiblioitemnumber - - GetItemsByBiblioitemnumber($biblioitemnumber); - -Returns an arrayref of hashrefs suitable for use in a TMPL_LOOP -Called by C - -=cut - -sub GetItemsByBiblioitemnumber { - my ( $bibitem ) = @_; - my $dbh = C4::Context->dbh; - my $sth = $dbh->prepare("SELECT * FROM items WHERE items.biblioitemnumber = ?") || die $dbh->errstr; - # Get all items attached to a biblioitem - my $i = 0; - my @results; - $sth->execute($bibitem) || die $sth->errstr; - while ( my $data = $sth->fetchrow_hashref ) { - # Foreach item, get circulation information - my $sth2 = $dbh->prepare( "SELECT * FROM issues,borrowers - WHERE itemnumber = ? - AND issues.borrowernumber = borrowers.borrowernumber" - ); - $sth2->execute( $data->{'itemnumber'} ); - if ( my $data2 = $sth2->fetchrow_hashref ) { - # if item is out, set the due date and who it is out too - $data->{'date_due'} = $data2->{'date_due'}; - $data->{'cardnumber'} = $data2->{'cardnumber'}; - $data->{'borrowernumber'} = $data2->{'borrowernumber'}; - } - else { - # set date_due to blank, so in the template we check itemlost, and withdrawn - $data->{'date_due'} = ''; - } # else - # Find the last 3 people who borrowed this item. - my $query2 = "SELECT * FROM old_issues, borrowers WHERE itemnumber = ? - AND old_issues.borrowernumber = borrowers.borrowernumber - ORDER BY returndate desc,timestamp desc LIMIT 3"; - $sth2 = $dbh->prepare($query2) || die $dbh->errstr; - $sth2->execute( $data->{'itemnumber'} ) || die $sth2->errstr; - my $i2 = 0; - while ( my $data2 = $sth2->fetchrow_hashref ) { - $data->{"timestamp$i2"} = $data2->{'timestamp'}; - $data->{"card$i2"} = $data2->{'cardnumber'}; - $data->{"borrower$i2"} = $data2->{'borrowernumber'}; - $i2++; - } - push(@results,$data); - } - return (\@results); -} - =head2 GetItemsInfo @results = GetItemsInfo($biblionumber); @@ -1300,7 +965,8 @@ If this is set, it is set to C. sub GetItemsInfo { my ( $biblionumber ) = @_; my $dbh = C4::Context->dbh; - # note biblioitems.* must be avoided to prevent large marc and marcxml fields from killing performance. + require C4::Languages; + my $language = C4::Languages::getlanguage(); my $query = " SELECT items.*, biblio.*, @@ -1326,8 +992,10 @@ sub GetItemsInfo { serial.serialseq, serial.publisheddate, itemtypes.description, + COALESCE( localization.translation, itemtypes.description ) AS translated_description, itemtypes.notforloan as notforloan_per_itemtype, holding.branchurl, + holding.branchcode, holding.branchname, holding.opac_info as holding_branch_opac_info, home.opac_info as home_branch_opac_info @@ -1344,9 +1012,15 @@ sub GetItemsInfo { LEFT JOIN serial USING (serialid) LEFT JOIN itemtypes ON itemtypes.itemtype = " . (C4::Context->preference('item-level_itypes') ? 'items.itype' : 'biblioitems.itemtype'); + $query .= q| + LEFT JOIN localization ON itemtypes.itemtype = localization.code + AND localization.entity = 'itemtypes' + AND localization.lang = ? + |; + $query .= " WHERE items.biblionumber = ? ORDER BY home.branchname, items.enumchron, LPAD( items.copynumber, 8, '0' ), items.dateaccessioned DESC" ; my $sth = $dbh->prepare($query); - $sth->execute($biblionumber); + $sth->execute($language, $biblionumber); my $i = 0; my @results; my $serial; @@ -1360,22 +1034,20 @@ sub GetItemsInfo { $serial ||= $data->{'serial'}; + my $descriptions; # get notforloan complete status if applicable - if ( my $code = C4::Koha::GetAuthValCode( 'items.notforloan', $data->{frameworkcode} ) ) { - $data->{notforloanvalue} = C4::Koha::GetKohaAuthorisedValueLib( $code, $data->{itemnotforloan} ); - $data->{notforloanvalueopac} = C4::Koha::GetKohaAuthorisedValueLib( $code, $data->{itemnotforloan}, 1 ); - } + $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.notforloan', authorised_value => $data->{itemnotforloan} }); + $data->{notforloanvalue} = $descriptions->{lib} // ''; + $data->{notforloanvalueopac} = $descriptions->{opac_description} // ''; # get restricted status and description if applicable - if ( my $code = C4::Koha::GetAuthValCode( 'items.restricted', $data->{frameworkcode} ) ) { - $data->{restrictedopac} = C4::Koha::GetKohaAuthorisedValueLib( $code, $data->{restricted}, 1 ); - $data->{restricted} = C4::Koha::GetKohaAuthorisedValueLib( $code, $data->{restricted} ); - } + $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.restricted', authorised_value => $data->{restricted} }); + $data->{restricted} = $descriptions->{lib} // ''; + $data->{restrictedopac} = $descriptions->{opac_description} // ''; # my stack procedures - if ( my $code = C4::Koha::GetAuthValCode( 'items.stack', $data->{frameworkcode} ) ) { - $data->{stack} = C4::Koha::GetKohaAuthorisedValueLib( $code, $data->{stack} ); - } + $descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.stack', authorised_value => $data->{stack} }); + $data->{stack} = $descriptions->{lib} // ''; # Find the last 3 people who borrowed this item. my $sth2 = $dbh->prepare("SELECT * FROM old_issues,borrowers @@ -1459,8 +1131,10 @@ sub GetItemsLocationInfo { $sth->execute($biblionumber); while ( my $data = $sth->fetchrow_hashref ) { - $data->{location_intranet} = GetKohaAuthorisedValueLib('LOC', $data->{location}); - $data->{location_opac}= GetKohaAuthorisedValueLib('LOC', $data->{location}, 1); + my $av = Koha::AuthorisedValues->search({ category => 'LOC', authorised_value => $data->{location} }); + $av = $av->count ? $av->next : undef; + $data->{location_intranet} = $av ? $av->lib : ''; + $data->{location_opac} = $av ? $av->opac_description : ''; push @results, $data; } return @results; @@ -1468,44 +1142,40 @@ sub GetItemsLocationInfo { =head2 GetHostItemsInfo - $hostiteminfo = GetHostItemsInfo($hostfield); - Returns the iteminfo for items linked to records via a host field + $hostiteminfo = GetHostItemsInfo($hostfield); + Returns the iteminfo for items linked to records via a host field =cut sub GetHostItemsInfo { - my ($record) = @_; - my @returnitemsInfo; - - if (C4::Context->preference('marcflavour') eq 'MARC21' || - C4::Context->preference('marcflavour') eq 'NORMARC'){ - foreach my $hostfield ( $record->field('773') ) { - my $hostbiblionumber = $hostfield->subfield("0"); - my $linkeditemnumber = $hostfield->subfield("9"); - my @hostitemInfos = GetItemsInfo($hostbiblionumber); - foreach my $hostitemInfo (@hostitemInfos){ - if ($hostitemInfo->{itemnumber} eq $linkeditemnumber){ - push (@returnitemsInfo,$hostitemInfo); - last; - } - } - } - } elsif ( C4::Context->preference('marcflavour') eq 'UNIMARC'){ - foreach my $hostfield ( $record->field('461') ) { - my $hostbiblionumber = $hostfield->subfield("0"); - my $linkeditemnumber = $hostfield->subfield("9"); - my @hostitemInfos = GetItemsInfo($hostbiblionumber); - foreach my $hostitemInfo (@hostitemInfos){ - if ($hostitemInfo->{itemnumber} eq $linkeditemnumber){ - push (@returnitemsInfo,$hostitemInfo); - last; - } - } - } - } - return @returnitemsInfo; -} + my ($record) = @_; + my @returnitemsInfo; + + if( !C4::Context->preference('EasyAnalyticalRecords') ) { + return @returnitemsInfo; + } + my @fields; + if( C4::Context->preference('marcflavour') eq 'MARC21' || + C4::Context->preference('marcflavour') eq 'NORMARC') { + @fields = $record->field('773'); + } elsif( C4::Context->preference('marcflavour') eq 'UNIMARC') { + @fields = $record->field('461'); + } + + foreach my $hostfield ( @fields ) { + my $hostbiblionumber = $hostfield->subfield("0"); + my $linkeditemnumber = $hostfield->subfield("9"); + my @hostitemInfos = GetItemsInfo($hostbiblionumber); + foreach my $hostitemInfo (@hostitemInfos) { + if( $hostitemInfo->{itemnumber} eq $linkeditemnumber ) { + push @returnitemsInfo, $hostitemInfo; + last; + } + } + } + return @returnitemsInfo; +} =head2 GetLastAcquisitions @@ -1558,61 +1228,6 @@ sub GetLastAcquisitions { return @results; } -=head2 GetItemnumbersForBiblio - - my $itemnumbers = GetItemnumbersForBiblio($biblionumber); - -Given a single biblionumber, return an arrayref of all the corresponding itemnumbers - -=cut - -sub GetItemnumbersForBiblio { - my $biblionumber = shift; - my @items; - my $dbh = C4::Context->dbh; - my $sth = $dbh->prepare("SELECT itemnumber FROM items WHERE biblionumber = ?"); - $sth->execute($biblionumber); - while (my $result = $sth->fetchrow_hashref) { - push @items, $result->{'itemnumber'}; - } - return \@items; -} - -=head2 get_itemnumbers_of - - my @itemnumbers_of = get_itemnumbers_of(@biblionumbers); - -Given a list of biblionumbers, return the list of corresponding itemnumbers -for each biblionumber. - -Return a reference on a hash where keys are biblionumbers and values are -references on array of itemnumbers. - -=cut - -sub get_itemnumbers_of { - my @biblionumbers = @_; - - my $dbh = C4::Context->dbh; - - my $query = ' - SELECT itemnumber, - biblionumber - FROM items - WHERE biblionumber IN (?' . ( ',?' x scalar @biblionumbers - 1 ) . ') - '; - my $sth = $dbh->prepare($query); - $sth->execute(@biblionumbers); - - my %itemnumbers_of; - - while ( my ( $itemnumber, $biblionumber ) = $sth->fetchrow_array ) { - push @{ $itemnumbers_of{$biblionumber} }, $itemnumber; - } - - return \%itemnumbers_of; -} - =head2 get_hostitemnumbers_of my @itemnumbers_of = get_hostitemnumbers_of($biblionumber); @@ -1626,86 +1241,62 @@ references on array of itemnumbers. sub get_hostitemnumbers_of { - my ($biblionumber) = @_; - my $marcrecord = GetMarcBiblio($biblionumber); - my (@returnhostitemnumbers,$tag, $biblio_s, $item_s); - - my $marcflavor = C4::Context->preference('marcflavour'); - if ($marcflavor eq 'MARC21' || $marcflavor eq 'NORMARC') { - $tag='773'; - $biblio_s='0'; - $item_s='9'; - } elsif ($marcflavor eq 'UNIMARC') { - $tag='461'; - $biblio_s='0'; - $item_s='9'; + my ($biblionumber) = @_; + my $marcrecord = C4::Biblio::GetMarcBiblio({ biblionumber => $biblionumber }); + + return unless $marcrecord; + + my ( @returnhostitemnumbers, $tag, $biblio_s, $item_s ); + + my $marcflavor = C4::Context->preference('marcflavour'); + if ( $marcflavor eq 'MARC21' || $marcflavor eq 'NORMARC' ) { + $tag = '773'; + $biblio_s = '0'; + $item_s = '9'; + } + elsif ( $marcflavor eq 'UNIMARC' ) { + $tag = '461'; + $biblio_s = '0'; + $item_s = '9'; } foreach my $hostfield ( $marcrecord->field($tag) ) { my $hostbiblionumber = $hostfield->subfield($biblio_s); + next unless $hostbiblionumber; # have tag, don't have $biblio_s subfield my $linkeditemnumber = $hostfield->subfield($item_s); - my @itemnumbers; - if (my $itemnumbers = get_itemnumbers_of($hostbiblionumber)->{$hostbiblionumber}) - { - @itemnumbers = @$itemnumbers; - } - foreach my $itemnumber (@itemnumbers){ - if ($itemnumber eq $linkeditemnumber){ - push (@returnhostitemnumbers,$itemnumber); - last; - } + if ( ! $linkeditemnumber ) { + warn "ERROR biblionumber $biblionumber has 773^0, but doesn't have 9"; + next; } + my $is_from_biblio = Koha::Items->search({ itemnumber => $linkeditemnumber, biblionumber => $hostbiblionumber }); + push @returnhostitemnumbers, $linkeditemnumber + if $is_from_biblio; } - return @returnhostitemnumbers; -} - - -=head2 GetItemnumberFromBarcode - - $result = GetItemnumberFromBarcode($barcode); - -=cut - -sub GetItemnumberFromBarcode { - my ($barcode) = @_; - my $dbh = C4::Context->dbh; - - my $rq = - $dbh->prepare("SELECT itemnumber FROM items WHERE items.barcode=?"); - $rq->execute($barcode); - my ($result) = $rq->fetchrow; - return ($result); -} - -=head2 GetBarcodeFromItemnumber - $result = GetBarcodeFromItemnumber($itemnumber); - -=cut - -sub GetBarcodeFromItemnumber { - my ($itemnumber) = @_; - my $dbh = C4::Context->dbh; - - my $rq = - $dbh->prepare("SELECT barcode FROM items WHERE items.itemnumber=?"); - $rq->execute($itemnumber); - my ($result) = $rq->fetchrow; - return ($result); + return @returnhostitemnumbers; } =head2 GetHiddenItemnumbers - my @itemnumbers_to_hide = GetHiddenItemnumbers(@items); + my @itemnumbers_to_hide = GetHiddenItemnumbers({ items => \@items, borcat => $category }); Given a list of items it checks which should be hidden from the OPAC given the current configuration. Returns a list of itemnumbers corresponding to -those that should be hidden. +those that should be hidden. Optionally takes a borcat parameter for certain borrower types +to be excluded =cut sub GetHiddenItemnumbers { - my (@items) = @_; + my $params = shift; + my $items = $params->{items}; + if (my $exceptions = C4::Context->preference('OpacHiddenItemsExceptions') and $params->{'borcat'}){ + foreach my $except (split(/\|/, $exceptions)){ + if ($params->{'borcat'} eq $except){ + return; # we don't hide anything for this borrower category + } + } + } my @resultitems; my $yaml = C4::Context->preference('OpacHiddenItems'); @@ -1722,7 +1313,7 @@ sub GetHiddenItemnumbers { my $dbh = C4::Context->dbh; # For each item - foreach my $item (@items) { + foreach my $item (@$items) { # We check each rule foreach my $field (keys %$hidingrules) { @@ -1750,104 +1341,6 @@ sub GetHiddenItemnumbers { return @resultitems; } -=head3 get_item_authorised_values - -find the types and values for all authorised values assigned to this item. - -parameters: itemnumber - -returns: a hashref malling the authorised value to the value set for this itemnumber - - $authorised_values = { - 'CCODE' => undef, - 'DAMAGED' => '0', - 'LOC' => '3', - 'LOST' => '0' - 'NOT_LOAN' => '0', - 'RESTRICTED' => undef, - 'STACK' => undef, - 'WITHDRAWN' => '0', - 'branches' => 'CPL', - 'cn_source' => undef, - 'itemtypes' => 'SER', - }; - -Notes: see C4::Biblio::get_biblio_authorised_values for a similar method at the biblio level. - -=cut - -sub get_item_authorised_values { - my $itemnumber = shift; - - # assume that these entries in the authorised_value table are item level. - my $query = q(SELECT distinct authorised_value, kohafield - FROM marc_subfield_structure - WHERE kohafield like 'item%' - AND authorised_value != '' ); - - my $itemlevel_authorised_values = C4::Context->dbh->selectall_hashref( $query, 'authorised_value' ); - my $iteminfo = GetItem( $itemnumber ); - # warn( Data::Dumper->Dump( [ $itemlevel_authorised_values ], [ 'itemlevel_authorised_values' ] ) ); - my $return; - foreach my $this_authorised_value ( keys %$itemlevel_authorised_values ) { - my $field = $itemlevel_authorised_values->{ $this_authorised_value }->{'kohafield'}; - $field =~ s/^items\.//; - if ( exists $iteminfo->{ $field } ) { - $return->{ $this_authorised_value } = $iteminfo->{ $field }; - } - } - # warn( Data::Dumper->Dump( [ $return ], [ 'return' ] ) ); - return $return; -} - -=head3 get_authorised_value_images - -find a list of icons that are appropriate for display based on the -authorised values for a biblio. - -parameters: listref of authorised values, such as comes from -get_item_authorised_values or -from C4::Biblio::get_biblio_authorised_values - -returns: listref of hashrefs for each image. Each hashref looks like this: - - { imageurl => '/intranet-tmpl/prog/img/itemtypeimg/npl/WEB.gif', - label => '', - category => '', - value => '', } - -Notes: Currently, I put on the full path to the images on the staff -side. This should either be configurable or not done at all. Since I -have to deal with 'intranet' or 'opac' in -get_biblio_authorised_values, perhaps I should be passing it in. - -=cut - -sub get_authorised_value_images { - my $authorised_values = shift; - - my @imagelist; - - my $authorised_value_list = GetAuthorisedValues(); - # warn ( Data::Dumper->Dump( [ $authorised_value_list ], [ 'authorised_value_list' ] ) ); - foreach my $this_authorised_value ( @$authorised_value_list ) { - if ( exists $authorised_values->{ $this_authorised_value->{'category'} } - && $authorised_values->{ $this_authorised_value->{'category'} } eq $this_authorised_value->{'authorised_value'} ) { - # warn ( Data::Dumper->Dump( [ $this_authorised_value ], [ 'this_authorised_value' ] ) ); - if ( defined $this_authorised_value->{'imageurl'} ) { - push @imagelist, { imageurl => C4::Koha::getitemtypeimagelocation( 'intranet', $this_authorised_value->{'imageurl'} ), - label => $this_authorised_value->{'lib'}, - category => $this_authorised_value->{'category'}, - value => $this_authorised_value->{'authorised_value'}, }; - } - } - } - - # warn ( Data::Dumper->Dump( [ \@imagelist ], [ 'imagelist' ] ) ); - return \@imagelist; - -} - =head1 LIMITED USE FUNCTIONS The following functions, while part of the public API, @@ -1886,7 +1379,7 @@ sub GetMarcItem { my $itemrecord = GetItem($itemnumber); - # Tack on 'items.' prefix to column names so that TransformKohaToMarc will work. + # Tack on 'items.' prefix to column names so that C4::Biblio::TransformKohaToMarc will work. # Also, don't emit a subfield if the underlying field is blank. @@ -1900,8 +1393,13 @@ sub Item2Marc { defined($itemrecord->{$_}) && $itemrecord->{$_} ne '' ? ("items.$_" => $itemrecord->{$_}) : () } keys %{ $itemrecord } }; - my $itemmarc = TransformKohaToMarc($mungeditem); - my ( $itemtag, $itemsubfield ) = GetMarcFromKohaField("items.itemnumber",GetFrameworkCode($biblionumber)||''); + my $framework = C4::Biblio::GetFrameworkCode( $biblionumber ); + my $itemmarc = C4::Biblio::TransformKohaToMarc( + $mungeditem, { no_split => 1}, + ); + my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField( + "items.itemnumber", $framework, + ); my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($mungeditem->{'items.more_subfields_xml'}); if (defined $unlinked_item_subfields and $#$unlinked_item_subfields > -1) { @@ -2048,7 +1546,11 @@ sub _do_column_fixes_for_mod { (not defined $item->{'withdrawn'} or $item->{'withdrawn'} eq '')) { $item->{'withdrawn'} = 0; } - if (exists $item->{'location'} && !$item->{'permanent_location'}) { + if (exists $item->{location} + and $item->{location} ne 'CART' + and $item->{location} ne 'PROC' + and not $item->{permanent_location} + ) { $item->{'permanent_location'} = $item->{'location'}; } if (exists $item->{'timestamp'}) { @@ -2128,7 +1630,7 @@ C sub _set_defaults_for_add { my $item = shift; - $item->{dateaccessioned} ||= C4::Dates->new->output('iso'); + $item->{dateaccessioned} ||= output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 }); $item->{$_} ||= 0 for (qw( notforloan damaged itemlost withdrawn)); } @@ -2144,6 +1646,8 @@ sub _koha_new_item { my ( $item, $barcode ) = @_; my $dbh=C4::Context->dbh; my $error; + $item->{permanent_location} //= $item->{location}; + _mod_item_dates( $item ); my $query = "INSERT INTO items SET biblionumber = ?, @@ -2161,7 +1665,7 @@ sub _koha_new_item { notforloan = ?, damaged = ?, itemlost = ?, - withdrawn = ?, + withdrawn = ?, itemcallnumber = ?, coded_location_qualifier = ?, restricted = ?, @@ -2170,7 +1674,7 @@ sub _koha_new_item { holdingbranch = ?, paidfor = ?, location = ?, - permanent_location = ?, + permanent_location = ?, onloan = ?, issues = ?, renewals = ?, @@ -2180,14 +1684,15 @@ sub _koha_new_item { ccode = ?, itype = ?, materials = ?, - uri = ?, + uri = ?, enumchron = ?, more_subfields_xml = ?, copynumber = ?, - stocknumber = ? + stocknumber = ?, + new_status = ? "; my $sth = $dbh->prepare($query); - my $today = C4::Dates->today('iso'); + my $today = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 }); $sth->execute( $item->{'biblionumber'}, $item->{'biblioitemnumber'}, @@ -2228,6 +1733,7 @@ sub _koha_new_item { $item->{'more_subfields_xml'}, $item->{'copynumber'}, $item->{'stocknumber'}, + $item->{'new_status'}, ); my $itemnumber; @@ -2291,64 +1797,102 @@ sub MoveItemFromBiblio { return; } -=head2 DelItemCheck +=head2 ItemSafeToDelete - DelItemCheck($dbh, $biblionumber, $itemnumber); + ItemSafeToDelete( $biblionumber, $itemnumber); -Exported function (core API) for deleting an item record in Koha if there no current issue. +Exported function (core API) for checking whether an item record is safe to delete. + +returns 1 if the item is safe to delete, + +"book_on_loan" if the item is checked out, + +"not_same_branch" if the item is blocked by independent branches, + +"book_reserved" if the there are holds aganst the item, or + +"linked_analytics" if the item has linked analytic records. =cut -sub DelItemCheck { - my ( $dbh, $biblionumber, $itemnumber ) = @_; - my $error; +sub ItemSafeToDelete { + my ( $biblionumber, $itemnumber ) = @_; + my $status; + my $dbh = C4::Context->dbh; - my $countanalytics=GetAnalyticsCount($itemnumber); + my $error; + my $countanalytics = GetAnalyticsCount($itemnumber); # check that there is no issue on this item before deletion. - my $sth = $dbh->prepare(q{ + my $sth = $dbh->prepare( + q{ SELECT COUNT(*) FROM issues WHERE itemnumber = ? - }); + } + ); $sth->execute($itemnumber); my ($onloan) = $sth->fetchrow; my $item = GetItem($itemnumber); - if ($onloan){ - $error = "book_on_loan" + if ($onloan) { + $status = "book_on_loan"; } - elsif ( !C4::Context->IsSuperLibrarian() + elsif ( defined C4::Context->userenv + and !C4::Context->IsSuperLibrarian() and C4::Context->preference("IndependentBranches") and ( C4::Context->userenv->{branch} ne $item->{'homebranch'} ) ) { - $error = "not_same_branch"; + $status = "not_same_branch"; } - else{ + else { # check it doesn't have a waiting reserve - $sth = $dbh->prepare(q{ + $sth = $dbh->prepare( + q{ SELECT COUNT(*) FROM reserves WHERE (found = 'W' OR found = 'T') AND itemnumber = ? - }); + } + ); $sth->execute($itemnumber); my ($reserve) = $sth->fetchrow; - if ($reserve){ - $error = "book_reserved"; - } elsif ($countanalytics > 0){ - $error = "linked_analytics"; - } else { - DelItem( - { - biblionumber => $biblionumber, - itemnumber => $itemnumber - } - ); - return 1; + if ($reserve) { + $status = "book_reserved"; + } + elsif ( $countanalytics > 0 ) { + $status = "linked_analytics"; + } + else { + $status = 1; } } - return $error; + return $status; +} + +=head2 DelItemCheck + + DelItemCheck( $biblionumber, $itemnumber); + +Exported function (core API) for deleting an item record in Koha if there no current issue. + +DelItemCheck wraps ItemSafeToDelete around DelItem. + +=cut + +sub DelItemCheck { + my ( $biblionumber, $itemnumber ) = @_; + my $status = ItemSafeToDelete( $biblionumber, $itemnumber ); + + if ( $status == 1 ) { + DelItem( + { + biblionumber => $biblionumber, + itemnumber => $itemnumber + } + ); + } + return $status; } =head2 _koha_modify_item @@ -2367,6 +1911,7 @@ sub _koha_modify_item { my $query = "UPDATE items SET "; my @bind; + _mod_item_dates( $item ); for my $key ( keys %$item ) { next if ( $key eq 'itemnumber' ); $query.="$key=?,"; @@ -2384,6 +1929,34 @@ sub _koha_modify_item { return ($item->{'itemnumber'},$error); } +sub _mod_item_dates { # date formatting for date fields in item hash + my ( $item ) = @_; + return if !$item || ref($item) ne 'HASH'; + + my @keys = grep + { $_ =~ /^onloan$|^date|date$|datetime$/ } + keys %$item; + # Incl. dateaccessioned,replacementpricedate,datelastborrowed,datelastseen + # NOTE: We do not (yet) have items fields ending with datetime + # Fields with _on$ have been handled already + + foreach my $key ( @keys ) { + next if !defined $item->{$key}; # skip undefs + my $dt = eval { dt_from_string( $item->{$key} ) }; + # eval: dt_from_string will die on us if we pass illegal dates + + my $newstr; + if( defined $dt && ref($dt) eq 'DateTime' ) { + if( $key =~ /datetime/ ) { + $newstr = DateTime::Format::MySQL->format_datetime($dt); + } else { + $newstr = DateTime::Format::MySQL->format_date($dt); + } + } + $item->{$key} = $newstr; # might be undef to clear garbage + } +} + =head2 _koha_delete_item _koha_delete_item( $itemnum ); @@ -2452,7 +2025,7 @@ sub _marc_from_item_hash { my $item_marc = MARC::Record->new(); foreach my $item_field ( keys %{$mungeditem} ) { - my ( $tag, $subfield ) = GetMarcFromKohaField( $item_field, $frameworkcode ); + my ( $tag, $subfield ) = C4::Biblio::GetMarcFromKohaField( $item_field, $frameworkcode ); next unless defined $tag and defined $subfield; # skip if not mapped to MARC field my @values = split(/\s?\|\s?/, $mungeditem->{$item_field}, -1); foreach my $value (@values){ @@ -2507,7 +2080,7 @@ sub _get_unlinked_item_subfields { my $original_item_marc = shift; my $frameworkcode = shift; - my $marcstructure = GetMarcStructure(1, $frameworkcode); + my $marcstructure = GetMarcStructure(1, $frameworkcode, { unsafe => 1 }); # assume that this record has only one field, and that that # field contains only the item information @@ -2580,36 +2153,15 @@ counts Usage of itemnumber in Analytical bibliorecords. sub GetAnalyticsCount { my ($itemnumber) = @_; - require C4::Search; ### ZOOM search here my $query; $query= "hi=".$itemnumber; - my ($err,$res,$result) = C4::Search::SimpleSearch($query,0,10); + my $searcher = Koha::SearchEngine::Search->new({index => $Koha::SearchEngine::BIBLIOS_INDEX}); + my ($err,$res,$result) = $searcher->simple_search_compat($query,0,10); return ($result); } -=head2 GetItemHolds - - $holds = &GetItemHolds($biblionumber, $itemnumber); - -This function return the count of holds with $biblionumber and $itemnumber - -=cut - -sub GetItemHolds { - my ($biblionumber, $itemnumber) = @_; - my $holds; - my $dbh = C4::Context->dbh; - my $query = "SELECT count(*) - FROM reserves - WHERE biblionumber=? AND itemnumber=?"; - my $sth = $dbh->prepare($query); - $sth->execute($biblionumber, $itemnumber); - $holds = $sth->fetchrow; - return $holds; -} - =head2 SearchItemsByField my $items = SearchItemsByField($field, $value); @@ -2683,16 +2235,16 @@ sub _SearchItems_build_where_fragment { $column = $kohafield; } else { # MARC field is not linked to a DB field so we need to use - # ExtractValue on biblioitems.marcxml or + # ExtractValue on marcxml from biblio_metadata or # items.more_subfields_xml, depending on the MARC field. my $xpath; my $sqlfield; - my ($itemfield) = GetMarcFromKohaField('items.itemnumber'); + my ($itemfield) = C4::Biblio::GetMarcFromKohaField('items.itemnumber'); if ($marcfield eq $itemfield) { $sqlfield = 'more_subfields_xml'; $xpath = '//record/datafield/subfield[@code="' . $marcsubfield . '"]'; } else { - $sqlfield = 'marcxml'; + $sqlfield = 'metadata'; # From biblio_metadata if ($marcfield < 10) { $xpath = "//record/controlfield[\@tag=\"$marcfield\"]"; } else { @@ -2801,11 +2353,16 @@ sub SearchItems { FROM items LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber + LEFT JOIN biblio_metadata ON biblio_metadata.biblionumber = biblio.biblionumber + WHERE 1 }; if (defined $where_str and $where_str ne '') { - $query .= qq{ WHERE $where_str }; + $query .= qq{ AND $where_str }; } + $query .= q{ AND biblio_metadata.format = 'marcxml' AND biblio_metadata.marcflavour = ? }; + push @where_args, C4::Context->preference('marcflavour'); + my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns; push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns; push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns; @@ -2889,9 +2446,13 @@ sub PrepareItemrecordDisplay { my ( $bibnum, $itemnum, $defaultvalues, $frameworkcode ) = @_; my $dbh = C4::Context->dbh; - $frameworkcode = &GetFrameworkCode($bibnum) if $bibnum; - my ( $itemtagfield, $itemtagsubfield ) = &GetMarcFromKohaField( "items.itemnumber", $frameworkcode ); - my $tagslib = &GetMarcStructure( 1, $frameworkcode ); + $frameworkcode = C4::Biblio::GetFrameworkCode($bibnum) if $bibnum; + my ( $itemtagfield, $itemtagsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber", $frameworkcode ); + + # Note: $tagslib obtained from GetMarcStructure() in 'unsafe' mode is + # a shared data structure. No plugin (including custom ones) should change + # its contents. See also GetMarcStructure. + my $tagslib = GetMarcStructure( 1, $frameworkcode, { unsafe => 1 } ); # return nothing if we don't have found an existing framework. return q{} unless $tagslib; @@ -2915,13 +2476,13 @@ sub PrepareItemrecordDisplay { $query .= qq{ ORDER BY lib}; my $authorised_values_sth = $dbh->prepare( $query ); foreach my $tag ( sort keys %{$tagslib} ) { - my $previous_tag = ''; if ( $tag ne '' ) { # loop through each subfield my $cntsubf; foreach my $subfield ( sort keys %{ $tagslib->{$tag} } ) { - next if ( subfield_is_koha_internal_p($subfield) ); + next if IsMarcStructureInternal($tagslib->{$tag}{$subfield}); + next unless ( $tagslib->{$tag}->{$subfield}->{'tab'} ); next if ( $tagslib->{$tag}->{$subfield}->{'tab'} ne "10" ); my %subfield_data; $subfield_data{tag} = $tag; @@ -2960,7 +2521,7 @@ sub PrepareItemrecordDisplay { if ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber' && $defaultvalues && $defaultvalues->{'callnumber'} ) { - if( $itemrecord and $defaultvalues and not $itemrecord->field($subfield) ){ + if( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ){ # if the item record exists, only use default value if the item has no callnumber $defaultvalue = $defaultvalues->{callnumber}; } elsif ( !$itemrecord and $defaultvalues ) { @@ -2971,7 +2532,7 @@ sub PrepareItemrecordDisplay { if ( ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.holdingbranch' || $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.homebranch' ) && $defaultvalues && $defaultvalues->{'branchcode'} ) { - if ( $itemrecord and $defaultvalues and not $itemrecord->field($subfield) ) { + if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) { $defaultvalue = $defaultvalues->{branchcode}; } } @@ -2979,7 +2540,7 @@ sub PrepareItemrecordDisplay { && $defaultvalues && $defaultvalues->{'location'} ) { - if ( $itemrecord and $defaultvalues and not $itemrecord->field($subfield) ) { + if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) { # if the item record exists, only use default value if the item has no locationr $defaultvalue = $defaultvalues->{location}; } elsif ( !$itemrecord and $defaultvalues ) { @@ -3022,14 +2583,17 @@ sub PrepareItemrecordDisplay { #----- itemtypes } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "itemtypes" ) { - my $sth = $dbh->prepare( "SELECT itemtype,description FROM itemtypes ORDER BY description" ); - $sth->execute; + my $itemtypes = Koha::ItemTypes->search_with_localization; push @authorised_values, "" unless ( $tagslib->{$tag}->{$subfield}->{mandatory} ); - while ( my ( $itemtype, $description ) = $sth->fetchrow_array ) { - push @authorised_values, $itemtype; - $authorised_lib{$itemtype} = $description; + while ( my $itemtype = $itemtypes->next ) { + push @authorised_values, $itemtype->itemtype; + $authorised_lib{$itemtype->itemtype} = $itemtype->translated_description; + } + if ($defaultvalues && $defaultvalues->{'itemtype'}) { + $defaultvalue = $defaultvalues->{'itemtype'}; } + #---- class_sources } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "cn_source" ) { push @authorised_values, "" unless ( $tagslib->{$tag}->{$subfield}->{mandatory} ); @@ -3074,15 +2638,18 @@ sub PrepareItemrecordDisplay { }); my $pars = { dbh => $dbh, record => undef, tagslib =>$tagslib, id => $subfield_data{id}, tabloop => undef }; $plugin->build( $pars ); + if ( $itemrecord and my $field = $itemrecord->field($tag) ) { + $defaultvalue = $field->subfield($subfield); + } if( !$plugin->errstr ) { #TODO Move html to template; see report 12176/13397 my $tab= $plugin->noclick? '-1': ''; my $class= $plugin->noclick? ' disabled': ''; my $title= $plugin->noclick? 'No popup': 'Tag editor'; - $subfield_data{marc_value} = qq[...\n].$plugin->javascript; + $subfield_data{marc_value} = qq[...\n].$plugin->javascript; } else { warn $plugin->errstr; - $subfield_data{marc_value} = qq(); # supply default input form + $subfield_data{marc_value} = qq(); # supply default input form } } elsif ( $tag eq '' ) { # it's an hidden field @@ -3118,4 +2685,66 @@ sub PrepareItemrecordDisplay { }; } +sub ToggleNewStatus { + my ( $params ) = @_; + my @rules = @{ $params->{rules} }; + my $report_only = $params->{report_only}; + + my $dbh = C4::Context->dbh; + my @errors; + my @item_columns = map { "items.$_" } Koha::Items->columns; + my @biblioitem_columns = map { "biblioitems.$_" } Koha::Biblioitems->columns; + my $report; + for my $rule ( @rules ) { + my $age = $rule->{age}; + my $conditions = $rule->{conditions}; + my $substitutions = $rule->{substitutions}; + my @params; + + my $query = q| + SELECT items.biblionumber, items.itemnumber + FROM items + LEFT JOIN biblioitems ON biblioitems.biblionumber = items.biblionumber + WHERE 1 + |; + for my $condition ( @$conditions ) { + if ( + grep {/^$condition->{field}$/} @item_columns + or grep {/^$condition->{field}$/} @biblioitem_columns + ) { + if ( $condition->{value} =~ /\|/ ) { + my @values = split /\|/, $condition->{value}; + $query .= qq| AND $condition->{field} IN (| + . join( ',', ('?') x scalar @values ) + . q|)|; + push @params, @values; + } else { + $query .= qq| AND $condition->{field} = ?|; + push @params, $condition->{value}; + } + } + } + if ( defined $age ) { + $query .= q| AND TO_DAYS(NOW()) - TO_DAYS(dateaccessioned) >= ? |; + push @params, $age; + } + my $sth = $dbh->prepare($query); + $sth->execute( @params ); + while ( my $values = $sth->fetchrow_hashref ) { + my $biblionumber = $values->{biblionumber}; + my $itemnumber = $values->{itemnumber}; + my $item = C4::Items::GetItem( $itemnumber ); + for my $substitution ( @$substitutions ) { + next unless $substitution->{field}; + C4::Items::ModItem( {$substitution->{field} => $substitution->{value}}, $biblionumber, $itemnumber ) + unless $report_only; + push @{ $report->{$itemnumber} }, $substitution; + } + } + } + + return $report; +} + + 1;