# You should have received a copy of the GNU General Public License
# along with Koha; if not, see <http://www.gnu.org/licenses>.
-use strict;
-#use warnings; FIXME - Bug 2505
+use Modern::Perl;
+
+use vars qw(@ISA @EXPORT);
+BEGIN {
+ require Exporter;
+ @ISA = qw(Exporter);
+
+ @EXPORT = qw(
+ AddItemFromMarc
+ AddItemBatchFromMarc
+ ModItemFromMarc
+ Item2Marc
+ ModDateLastSeen
+ ModItemTransfer
+ CheckItemPreSave
+ GetItemsForInventory
+ GetItemsInfo
+ GetItemsLocationInfo
+ GetHostItemsInfo
+ get_hostitemnumbers_of
+ GetHiddenItemnumbers
+ MoveItemFromBiblio
+ CartToShelf
+ GetAnalyticsCount
+ SearchItems
+ PrepareItemrecordDisplay
+ );
+}
use Carp;
+use Try::Tiny;
use C4::Context;
use C4::Koha;
use C4::Biblio;
use MARC::Record;
use C4::ClassSource;
use C4::Log;
-use List::MoreUtils qw/any/;
-use YAML qw/Load/;
+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::DateUtils qw(dt_from_string);
use Koha::Database;
use Koha::Biblioitems;
use Koha::SearchEngine::Search;
use Koha::Libraries;
-use vars qw(@ISA @EXPORT);
-
-BEGIN {
-
- require Exporter;
- @ISA = qw( Exporter );
-
- # function exports
- @EXPORT = qw(
- GetItem
- AddItemFromMarc
- AddItem
- AddItemBatchFromMarc
- ModItemFromMarc
- Item2Marc
- ModItem
- ModDateLastSeen
- ModItemTransfer
- DelItem
-
- CheckItemPreSave
-
- GetItemsForInventory
- GetItemsInfo
- GetItemsLocationInfo
- GetHostItemsInfo
- GetItemnumbersForBiblio
- get_hostitemnumbers_of
- GetBarcodeFromItemnumber
- GetHiddenItemnumbers
- ItemSafeToDelete
- DelItemCheck
- MoveItemFromBiblio
- GetLatestAcquisitions
-
- CartToShelf
- ShelfToCart
-
- GetAnalyticsCount
-
- SearchItemsByField
- SearchItems
-
- PrepareItemrecordDisplay
-
- );
-}
-
=head1 NAME
C4::Items - item management functions
modification transaction must keep the items table
and the MARC XML in sync at all times.
-Consequently, all code that creates, modifies, or deletes
-item records B<must> use an appropriate function from
-C<C4::Items>. If no existing function is suitable, it is
-better to add one to C<C4::Items> than to use add
-one-off SQL statements to add or modify items.
-
The items table will be considered authoritative. In other
words, if there is ever a discrepancy between the items
table and the MARC XML, the items table should be considered
=cut
-=head2 GetItem
-
- $item = GetItem($itemnumber,$barcode,$serial);
-
-Return item information, for a given itemnumber or barcode.
-The return value is a hashref mapping item column
-names to values. If C<$serial> is true, include serial publication data.
-
-=cut
-
-sub GetItem {
- my ($itemnumber,$barcode, $serial) = @_;
- my $dbh = C4::Context->dbh;
-
- my $item;
- if ($itemnumber) {
- $item = Koha::Items->find( $itemnumber );
- } else {
- $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
-
=head2 CartToShelf
CartToShelf($itemnumber);
croak "FAILED CartToShelf() - no itemnumber supplied";
}
- my $item = GetItem($itemnumber);
- if ( $item->{location} eq 'CART' ) {
- $item->{location} = $item->{permanent_location};
- ModItem($item, undef, $itemnumber);
+ my $item = Koha::Items->find($itemnumber);
+ if ( $item->location eq 'CART' ) {
+ $item->location($item->permanent_location)->store;
}
}
-=head2 ShelfToCart
-
- ShelfToCart($itemnumber);
-
-Set the current shelving location of the item
-to shelving cart ('CART').
-
-=cut
-
-sub ShelfToCart {
- my ( $itemnumber ) = @_;
-
- unless ( $itemnumber ) {
- croak "FAILED ShelfToCart() - no itemnumber supplied";
- }
-
- my $item = GetItem($itemnumber);
- $item->{'location'} = 'CART';
- ModItem($item, undef, $itemnumber);
-}
-
=head2 AddItemFromMarc
my ($biblionumber, $biblioitemnumber, $itemnumber)
- = AddItemFromMarc($source_item_marc, $biblionumber);
+ = AddItemFromMarc($source_item_marc, $biblionumber[, $params]);
Given a MARC::Record object containing an embedded item
record and a biblionumber, create a new item record.
-=cut
-
-sub AddItemFromMarc {
- my ( $source_item_marc, $biblionumber ) = @_;
- my $dbh = C4::Context->dbh;
+The final optional parameter, C<$params>, expected to contain
+'skip_modzebra_update' key, which relayed down to Koha::Item/store,
+there it prevents calling of ModZebra (and Elasticsearch update),
+which takes most of the time in batch adds/deletes: ModZebra better
+to be called later in C<additem.pl> after the whole loop.
- # parse item hash from MARC
- 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);
-}
-
-=head2 AddItem
-
- my ($biblionumber, $biblioitemnumber, $itemnumber)
- = AddItem($item, $biblionumber[, $dbh, $frameworkcode, $unlinked_item_subfields]);
-
-Given a hash containing item column names as keys,
-create a new Koha item record.
-
-The first two optional parameters (C<$dbh> and C<$frameworkcode>)
-do not need to be supplied for general use; they exist
-simply to allow them to be picked up from AddItemFromMarc.
-
-The final optional parameter, 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<items> columns directly but should instead
-be stored in C<items.more_subfields_xml> and included in
-the biblio items tag for display and indexing.
+$params:
+ skip_modzebra_update => 1|0
=cut
-sub AddItem {
- my $item = shift;
- my $biblionumber = shift;
-
- my $dbh = @_ ? shift : C4::Context->dbh;
- my $frameworkcode = @_ ? shift : C4::Biblio::GetFrameworkCode($biblionumber);
- my $unlinked_item_subfields;
- if (@_) {
- $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;
-
- _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
- 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} );
- return if $error;
+sub AddItemFromMarc {
+ my $source_item_marc = shift;
+ my $biblionumber = shift;
+ my $params = @_ ? shift : {};
- $item->{'itemnumber'} = $itemnumber;
+ my $dbh = C4::Context->dbh;
- ModZebra( $item->{biblionumber}, "specialUpdate", "biblioserver" );
+ # parse item hash from MARC
+ my $frameworkcode = C4::Biblio::GetFrameworkCode($biblionumber);
+ my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
- logaction( "CATALOGUING", "ADD", $itemnumber, "item" )
- if C4::Context->preference("CataloguingLog");
+ my $localitemmarc = MARC::Record->new;
+ $localitemmarc->append_fields( $source_item_marc->field($itemtag) );
- return ( $item->{biblionumber}, $item->{biblioitemnumber}, $itemnumber );
+ my $item_values = C4::Biblio::TransformMarcToKoha( $localitemmarc, $frameworkcode, 'items' );
+ my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
+ $item_values->{more_subfields_xml} = _get_unlinked_subfields_xml($unlinked_item_subfields);
+ $item_values->{biblionumber} = $biblionumber;
+ $item_values->{cn_source} = delete $item_values->{'items.cn_source'}; # Because of C4::Biblio::_disambiguate
+ $item_values->{cn_sort} = delete $item_values->{'items.cn_sort'}; # Because of C4::Biblio::_disambiguate
+ my $item = Koha::Item->new( $item_values )->store({ skip_modzebra_update => $params->{skip_modzebra_update} });
+ return ( $item->biblionumber, $item->biblioitemnumber, $item->itemnumber );
}
=head2 AddItemBatchFromMarc
$record = $record->clone();
# loop through the item tags and start creating items
my @bad_item_fields = ();
- my ($itemtag, $itemsubfield) = C4::Biblio::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++;
$item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields);
$item->{'biblionumber'} = $biblionumber;
$item->{'biblioitemnumber'} = $biblioitemnumber;
+ $item->{cn_source} = delete $item->{'items.cn_source'}; # Because of C4::Biblio::_disambiguate
+ $item->{cn_sort} = delete $item->{'items.cn_sort'}; # Because of C4::Biblio::_disambiguate
# check for duplicate barcode
my %item_errors = CheckItemPreSave($item);
next ITEMFIELD;
}
- _set_defaults_for_add($item);
- _set_derived_columns_for_add($item);
- my ( $itemnumber, $error ) = _koha_new_item( $item, $item->{barcode} );
- warn $error if $error;
- push @itemnumbers, $itemnumber; # FIXME not checking error
- $item->{'itemnumber'} = $itemnumber;
+ my $item_object = Koha::Item->new($item)->store;
+ push @itemnumbers, $item_object->itemnumber; # FIXME not checking error
- logaction("CATALOGUING", "ADD", $itemnumber, "item") if C4::Context->preference("CataloguingLog");
+ logaction("CATALOGUING", "ADD", $item_object->itemnumber, "item") if C4::Context->preference("CataloguingLog");
- my $new_item_marc = _marc_from_item_hash($item, $frameworkcode, $unlinked_item_subfields);
+ my $new_item_marc = _marc_from_item_hash($item_object->unblessed, $frameworkcode, $unlinked_item_subfields);
$item_field->replace_with($new_item_marc->field($itemtag));
}
return (\@itemnumbers, \@errors);
}
-=head2 ModItemFromMarc
-
- ModItemFromMarc($item_marc, $biblionumber, $itemnumber);
-
-This function updates an item record based on a supplied
-C<MARC::Record> object containing an embedded item field.
-This API is meant for the use of C<additem.pl>; for
-other purposes, C<ModItem> should be used.
-
-This function uses the hash %default_values_for_mod_from_marc,
-which contains default values for item fields to
-apply when modifying an item. This is needed because
-if an item field's value is cleared, TransformMarcToKoha
-does not include the column in the
-hash that's passed to ModItem, which without
-use of this hash makes it impossible to clear
-an item field's value. See bug 2466.
-
-Note that only columns that can be directly
-changed from the cataloging and serials
-item editors are included in this hash.
-
-Returns item record
-
-=cut
-
-sub _build_default_values_for_mod_marc {
- # 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,
- ccode => undef,
- 'items.cn_source' => undef,
- coded_location_qualifier => undef,
- copynumber => undef,
- damaged => 0,
- enumchron => undef,
- holdingbranch => undef,
- homebranch => undef,
- itemcallnumber => undef,
- itemlost => 0,
- itemnotes => undef,
- itemnotes_nonpublic => undef,
- itype => undef,
- location => undef,
- permanent_location => undef,
- materials => undef,
- new_status => undef,
- notforloan => 0,
- # paidfor => undef, # commented, see bug 12817
- price => undef,
- replacementprice => undef,
- replacementpricedate => undef,
- restricted => undef,
- stack => undef,
- stocknumber => undef,
- 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{$field} = $default_value
- if C4::Biblio::GetMarcFromKohaField( $kohafield );
- }
-
- $cache->set_in_cache($cache_key, \%default_values_for_mod_from_marc);
- return \%default_values_for_mod_from_marc;
-}
-
sub ModItemFromMarc {
my $item_marc = shift;
my $biblionumber = shift;
my $itemnumber = shift;
my $frameworkcode = C4::Biblio::GetFrameworkCode($biblionumber);
- my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber", $frameworkcode );
+ my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
my $localitemmarc = MARC::Record->new;
$localitemmarc->append_fields( $item_marc->field($itemtag) );
- 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}
- unless exists $item->{$item_field};
- }
+ my $item_object = Koha::Items->find($itemnumber);
+ my $item = TransformMarcToKoha( $localitemmarc, $frameworkcode, 'items' );
+ $item->{cn_source} = delete $item->{'items.cn_source'}; # Because of C4::Biblio::_disambiguate
+ $item->{cn_sort} = delete $item->{'items.cn_sort'}; # Because of C4::Biblio::_disambiguate
+ $item->{itemnumber} = $itemnumber;
+ $item->{biblionumber} = $biblionumber;
+ $item_object = $item_object->set_or_blank($item);
my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
+ $item_object->more_subfields_xml(_get_unlinked_subfields_xml($unlinked_item_subfields))->store;
+ $item_object->store;
- ModItem( $item, $biblionumber, $itemnumber, { unlinked_item_subfields => $unlinked_item_subfields } );
- return $item;
-}
-
-=head2 ModItem
-
-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.
-
-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.
-
-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<items> columns directly but should instead
-be stored in C<items.more_subfields_xml> and included in
-the biblio items tag for display and indexing.
-
-If one of the changed columns is used to calculate
-the derived value of a column such as C<items.cn_sort>,
-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, $biblionumber, $itemnumber, $additional_params ) = @_;
- my $log_action = $additional_params->{log_action} // 1;
- my $unlinked_item_subfields = $additional_params->{unlinked_item_subfields};
-
- # if $biblionumber is undefined, get it from the current item
- unless (defined $biblionumber) {
- $biblionumber = _get_single_item_column('biblionumber', $itemnumber);
- }
-
- 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 damaged );
-
- # Only call GetItem if we need to set an "on" date field
- if ( $item->{itemlost} || $item->{withdrawn} || $item->{damaged} ) {
- my $pre_mod_item = GetItem( $item->{'itemnumber'} );
- for my $field (@fields) {
- if ( defined( $item->{$field} )
- and not $pre_mod_item->{$field}
- and $item->{$field} )
- {
- $item->{ $field . '_on' } =
- DateTime::Format::MySQL->format_datetime( dt_from_string() );
- }
- }
- }
-
- # If the field is defined but empty, we are removing and,
- # and thus need to clear out the 'on' field as well
- for my $field (@fields) {
- if ( defined( $item->{$field} ) && !$item->{$field} ) {
- $item->{ $field . '_on' } = undef;
- }
- }
-
-
- _set_derived_columns_for_mod($item);
- _do_column_fixes_for_mod($item);
- # FIXME add checks
- # duplicate barcode
- # attempt to change itemnumber
- # attempt to change biblionumber (if we want
- # an API to relink an item to a different bib,
- # it should be a separate function)
-
- # update items table
- _koha_modify_item($item);
-
- # request that bib be reindexed so that searching on current
- # item status is possible
- ModZebra( $biblionumber, "specialUpdate", "biblioserver" );
-
- logaction( "CATALOGUING", "MODIFY", $itemnumber, "item " . Dumper($item) )
- if $log_action && C4::Context->preference("CataloguingLog");
+ return $item_object->unblessed;
}
=head2 ModItemTransfer
- ModItemTransfer($itenumber, $frombranch, $tobranch);
+ ModItemTransfer($itemnumber, $frombranch, $tobranch, $trigger);
-Marks an item as being transferred from one branch
-to another.
+Marks an item as being transferred from one branch to another and records the trigger.
=cut
sub ModItemTransfer {
- my ( $itemnumber, $frombranch, $tobranch ) = @_;
+ my ( $itemnumber, $frombranch, $tobranch, $trigger ) = @_;
my $dbh = C4::Context->dbh;
+ my $item = Koha::Items->find( $itemnumber );
# Remove the 'shelving cart' location status if it is being used.
- CartToShelf( $itemnumber ) if ( C4::Context->preference("ReturnToShelvingCart") );
+ CartToShelf( $itemnumber ) if $item->location && $item->location eq 'CART' && ( !$item->permanent_location || $item->permanent_location ne 'CART' );
$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)
- VALUES (?, ?, NOW(), ?)");
- $sth->execute($itemnumber, $frombranch, $tobranch);
+ "INSERT INTO branchtransfers (itemnumber, frombranch, datesent, tobranch, reason)
+ VALUES (?, ?, NOW(), ?, ?)");
+ $sth->execute($itemnumber, $frombranch, $tobranch, $trigger);
- ModItem({ holdingbranch => $tobranch }, undef, $itemnumber);
+ # FIXME we are fetching the item twice in the 2 next statements!
+ Koha::Items->find($itemnumber)->holdingbranch($frombranch)->store({ log_action => 0 });
ModDateLastSeen($itemnumber);
return;
}
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
-
- DelItem({ itemnumber => $itemnumber, [ biblionumber => $biblionumber ] } );
-
-Exported function (core API) for deleting an item record in Koha.
-
-=cut
-
-sub DelItem {
- my ( $params ) = @_;
-
- my $itemnumber = $params->{itemnumber};
- my $biblionumber = $params->{biblionumber};
-
- unless ($biblionumber) {
- 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
- return 0 unless $biblionumber;
-
- # FIXME check the item has no current issues
- my $deleted = _koha_delete_item( $itemnumber );
-
- ModZebra( $biblionumber, "specialUpdate", "biblioserver" );
-
- #search item field code
- logaction("CATALOGUING", "DELETE", $itemnumber, "item") if C4::Context->preference("CataloguingLog");
- return $deleted;
+ my $item = Koha::Items->find($itemnumber);
+ $item->datelastseen($today);
+ $item->itemlost(0) unless $leave_item_lost;
+ $item->store({ log_action => 0 });
}
=head2 CheckItemPreSave
my ( $parameters ) = @_;
my $minlocation = $parameters->{'minlocation'} // '';
my $maxlocation = $parameters->{'maxlocation'} // '';
+ my $class_source = $parameters->{'class_source'} // C4::Context->preference('DefaultClassificationSource');
my $location = $parameters->{'location'} // '';
my $itemtype = $parameters->{'itemtype'} // '';
my $ignoreissued = $parameters->{'ignoreissued'} // '';
my $offset = $parameters->{'offset'} // '';
my $size = $parameters->{'size'} // '';
my $statushash = $parameters->{'statushash'} // '';
+ my $ignore_waiting_holds = $parameters->{'ignore_waiting_holds'} // '';
my $dbh = C4::Context->dbh;
my ( @bind_params, @where_strings );
+ my $min_cnsort = GetClassSort($class_source,undef,$minlocation);
+ my $max_cnsort = GetClassSort($class_source,undef,$maxlocation);
+
my $select_columns = q{
- SELECT items.itemnumber, barcode, itemcallnumber, title, author, biblio.biblionumber, biblio.frameworkcode, datelastseen, homebranch, location, notforloan, damaged, itemlost, withdrawn, stocknumber
+ SELECT DISTINCT(items.itemnumber), barcode, itemcallnumber, title, author, biblio.biblionumber, biblio.frameworkcode, datelastseen, homebranch, location, notforloan, damaged, itemlost, withdrawn, stocknumber, items.cn_sort
};
- my $select_count = q{SELECT COUNT(*)};
+ my $select_count = q{SELECT COUNT(DISTINCT(items.itemnumber))};
my $query = q{
FROM items
LEFT JOIN biblio ON items.biblionumber = biblio.biblionumber
}
if ($minlocation) {
- push @where_strings, 'itemcallnumber >= ?';
- push @bind_params, $minlocation;
+ push @where_strings, 'items.cn_sort >= ?';
+ push @bind_params, $min_cnsort;
}
if ($maxlocation) {
- push @where_strings, 'itemcallnumber <= ?';
- push @bind_params, $maxlocation;
+ push @where_strings, 'items.cn_sort <= ?';
+ push @bind_params, $max_cnsort;
}
if ($datelastseen) {
push @where_strings, 'issues.date_due IS NULL';
}
+ if ( $ignore_waiting_holds ) {
+ $query .= "LEFT JOIN reserves ON items.itemnumber = reserves.itemnumber ";
+ push( @where_strings, q{(reserves.found != 'W' OR reserves.found IS NULL)} );
+ }
+
if ( @where_strings ) {
$query .= 'WHERE ';
$query .= join ' AND ', @where_strings;
# Auth values
foreach (keys %$row) {
- if (defined($avmapping->{"items.$_,".$row->{'frameworkcode'}.",".$row->{$_}})) {
+ if (
+ defined(
+ $avmapping->{ "items.$_," . $row->{'frameworkcode'} . "," . ( $row->{$_} // q{} ) }
+ )
+ ) {
$row->{$_} = $avmapping->{"items.$_,".$row->{'frameworkcode'}.",".$row->{$_}};
}
}
holding.branchcode,
holding.branchname,
holding.opac_info as holding_branch_opac_info,
- home.opac_info as home_branch_opac_info
- ";
- $query .= "
+ home.opac_info as home_branch_opac_info,
+ IF(tmp_holdsqueue.itemnumber,1,0) AS has_pending_hold
FROM items
LEFT JOIN branches AS holding ON items.holdingbranch = holding.branchcode
LEFT JOIN branches AS home ON items.homebranch=home.branchcode
LEFT JOIN itemtypes ON itemtypes.itemtype = "
. (C4::Context->preference('item-level_itypes') ? 'items.itype' : 'biblioitems.itemtype');
$query .= q|
+ LEFT JOIN tmp_holdsqueue USING (itemnumber)
LEFT JOIN localization ON itemtypes.itemtype = localization.code
AND localization.entity = 'itemtypes'
AND localization.lang = ?
# get restricted status and description if applicable
$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} // '';
+ $data->{restrictedvalue} = $descriptions->{lib} // '';
+ $data->{restrictedvalueopac} = $descriptions->{opac_description} // '';
# my stack procedures
$descriptions = Koha::AuthorisedValues->get_description_by_koha_field({frameworkcode => $data->{frameworkcode}, kohafield => 'items.stack', authorised_value => $data->{stack} });
return @returnitemsInfo;
}
-=head2 GetLastAcquisitions
-
- my $lastacq = GetLastAcquisitions({'branches' => ('branch1','branch2'),
- 'itemtypes' => ('BK','BD')}, 10);
-
-=cut
-
-sub GetLastAcquisitions {
- my ($data,$max) = @_;
-
- my $itemtype = C4::Context->preference('item-level_itypes') ? 'itype' : 'itemtype';
-
- my $number_of_branches = @{$data->{branches}};
- my $number_of_itemtypes = @{$data->{itemtypes}};
-
-
- my @where = ('WHERE 1 ');
- $number_of_branches and push @where
- , 'AND holdingbranch IN ('
- , join(',', ('?') x $number_of_branches )
- , ')'
- ;
-
- $number_of_itemtypes and push @where
- , "AND $itemtype IN ("
- , join(',', ('?') x $number_of_itemtypes )
- , ')'
- ;
-
- my $query = "SELECT biblio.biblionumber as biblionumber, title, dateaccessioned
- FROM items RIGHT JOIN biblio ON (items.biblionumber=biblio.biblionumber)
- RIGHT JOIN biblioitems ON (items.biblioitemnumber=biblioitems.biblioitemnumber)
- @where
- GROUP BY biblio.biblionumber
- ORDER BY dateaccessioned DESC LIMIT $max";
-
- my $dbh = C4::Context->dbh;
- my $sth = $dbh->prepare($query);
-
- $sth->execute((@{$data->{branches}}, @{$data->{itemtypes}}));
-
- my @results;
- while( my $row = $sth->fetchrow_hashref){
- push @results, {date => $row->{dateaccessioned}
- , biblionumber => $row->{biblionumber}
- , title => $row->{title}};
- }
-
- 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_hostitemnumbers_of
my @itemnumbers_of = get_hostitemnumbers_of($biblionumber);
sub get_hostitemnumbers_of {
my ($biblionumber) = @_;
- my $marcrecord = C4::Biblio::GetMarcBiblio({ biblionumber => $biblionumber });
+ if( !C4::Context->preference('EasyAnalyticalRecords') ) {
+ return ();
+ }
+
+ my $marcrecord = C4::Biblio::GetMarcBiblio({ biblionumber => $biblionumber });
return unless $marcrecord;
my ( @returnhostitemnumbers, $tag, $biblio_s, $item_s );
return @returnhostitemnumbers;
}
-
-=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);
-}
-
=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');
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) {
# while the other treats the MARC representation as authoritative
# under certain circumstances.
- my $itemrecord = GetItem($itemnumber);
+ my $item = Koha::Items->find($itemnumber) or return;
# 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.
-
- return Item2Marc($itemrecord,$biblionumber);
+ return Item2Marc($item->unblessed, $biblionumber);
}
sub Item2Marc {
} keys %{ $itemrecord }
};
my $framework = C4::Biblio::GetFrameworkCode( $biblionumber );
- my $itemmarc = C4::Biblio::TransformKohaToMarc(
- $mungeditem, { no_split => 1},
- );
+ my $itemmarc = C4::Biblio::TransformKohaToMarc( $mungeditem, { framework => $framework } );
my ( $itemtag, $itemsubfield ) = C4::Biblio::GetMarcFromKohaField(
"items.itemnumber", $framework,
);
=cut
-=head2 %derived_columns
-
-This hash keeps track of item columns that
-are strictly derived from other columns in
-the item record and are not meant to be set
-independently.
-
-Each key in the hash should be the name of a
-column (as named by TransformMarcToKoha). Each
-value should be hashref whose keys are the
-columns on which the derived column depends. The
-hashref should also contain a 'BUILDER' key
-that is a reference to a sub that calculates
-the derived value.
-
-=cut
-
-my %derived_columns = (
- 'items.cn_sort' => {
- 'itemcallnumber' => 1,
- 'items.cn_source' => 1,
- 'BUILDER' => \&_calc_items_cn_sort,
- }
-);
-
-=head2 _set_derived_columns_for_add
-
- _set_derived_column_for_add($item);
-
-Given an item hash representing a new item to be added,
-calculate any derived columns. Currently the only
-such column is C<items.cn_sort>.
-
-=cut
-
-sub _set_derived_columns_for_add {
- my $item = shift;
-
- foreach my $column (keys %derived_columns) {
- my $builder = $derived_columns{$column}->{'BUILDER'};
- my $source_values = {};
- foreach my $source_column (keys %{ $derived_columns{$column} }) {
- next if $source_column eq 'BUILDER';
- $source_values->{$source_column} = $item->{$source_column};
- }
- $builder->($item, $source_values);
- }
-}
-
-=head2 _set_derived_columns_for_mod
-
- _set_derived_column_for_mod($item);
-
-Given an item hash representing a new item to be modified.
-calculate any derived columns. Currently the only
-such column is C<items.cn_sort>.
-
-This routine differs from C<_set_derived_columns_for_add>
-in that it needs to handle partial item records. In other
-words, the caller of C<ModItem> may have supplied only one
-or two columns to be changed, so this function needs to
-determine whether any of the columns to be changed affect
-any of the derived columns. Also, if a derived column
-depends on more than one column, but the caller is not
-changing all of then, this routine retrieves the unchanged
-values from the database in order to ensure a correct
-calculation.
-
-=cut
-
-sub _set_derived_columns_for_mod {
- my $item = shift;
-
- foreach my $column (keys %derived_columns) {
- my $builder = $derived_columns{$column}->{'BUILDER'};
- my $source_values = {};
- my %missing_sources = ();
- my $must_recalc = 0;
- foreach my $source_column (keys %{ $derived_columns{$column} }) {
- next if $source_column eq 'BUILDER';
- if (exists $item->{$source_column}) {
- $must_recalc = 1;
- $source_values->{$source_column} = $item->{$source_column};
- } else {
- $missing_sources{$source_column} = 1;
- }
- }
- if ($must_recalc) {
- foreach my $source_column (keys %missing_sources) {
- $source_values->{$source_column} = _get_single_item_column($source_column, $item->{'itemnumber'});
- }
- $builder->($item, $source_values);
- }
- }
-}
-
-=head2 _do_column_fixes_for_mod
-
- _do_column_fixes_for_mod($item);
-
-Given an item hashref containing one or more
-columns to modify, fix up certain values.
-Specifically, set to 0 any passed value
-of C<notforloan>, C<damaged>, C<itemlost>, or
-C<withdrawn> that is either undefined or
-contains the empty string.
-
-=cut
-
-sub _do_column_fixes_for_mod {
- my $item = shift;
-
- if (exists $item->{'notforloan'} and
- (not defined $item->{'notforloan'} or $item->{'notforloan'} eq '')) {
- $item->{'notforloan'} = 0;
- }
- if (exists $item->{'damaged'} and
- (not defined $item->{'damaged'} or $item->{'damaged'} eq '')) {
- $item->{'damaged'} = 0;
- }
- if (exists $item->{'itemlost'} and
- (not defined $item->{'itemlost'} or $item->{'itemlost'} eq '')) {
- $item->{'itemlost'} = 0;
- }
- if (exists $item->{'withdrawn'} and
- (not defined $item->{'withdrawn'} or $item->{'withdrawn'} eq '')) {
- $item->{'withdrawn'} = 0;
- }
- 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'}) {
- delete $item->{'timestamp'};
- }
-}
-
-=head2 _get_single_item_column
-
- _get_single_item_column($column, $itemnumber);
-
-Retrieves the value of a single column from an C<items>
-row specified by C<$itemnumber>.
-
-=cut
-
-sub _get_single_item_column {
- my $column = shift;
- my $itemnumber = shift;
-
- my $dbh = C4::Context->dbh;
- my $sth = $dbh->prepare("SELECT $column FROM items WHERE itemnumber = ?");
- $sth->execute($itemnumber);
- my ($value) = $sth->fetchrow();
- return $value;
-}
-
-=head2 _calc_items_cn_sort
-
- _calc_items_cn_sort($item, $source_values);
-
-Helper routine to calculate C<items.cn_sort>.
-
-=cut
-
-sub _calc_items_cn_sort {
- my $item = shift;
- my $source_values = shift;
-
- $item->{'items.cn_sort'} = GetClassSort($source_values->{'items.cn_source'}, $source_values->{'itemcallnumber'}, "");
-}
-
-=head2 _set_defaults_for_add
-
- _set_defaults_for_add($item_hash);
-
-Given an item hash representing an item to be added, set
-correct default values for columns whose default value
-is not handled by the DBMS. This includes the following
-columns:
-
-=over 2
-
-=item *
-
-C<items.dateaccessioned>
-
-=item *
-
-C<items.notforloan>
-
-=item *
-
-C<items.damaged>
-
-=item *
-
-C<items.itemlost>
-
-=item *
-
-C<items.withdrawn>
-
-=back
-
-=cut
-
-sub _set_defaults_for_add {
- my $item = shift;
- $item->{dateaccessioned} ||= output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
- $item->{$_} ||= 0 for (qw( notforloan damaged itemlost withdrawn));
-}
-
-=head2 _koha_new_item
-
- my ($itemnumber,$error) = _koha_new_item( $item, $barcode );
-
-Perform the actual insert into the C<items> table.
-
-=cut
-
-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 = ?,
- biblioitemnumber = ?,
- barcode = ?,
- dateaccessioned = ?,
- booksellerid = ?,
- homebranch = ?,
- price = ?,
- replacementprice = ?,
- replacementpricedate = ?,
- datelastborrowed = ?,
- datelastseen = ?,
- stack = ?,
- notforloan = ?,
- damaged = ?,
- itemlost = ?,
- withdrawn = ?,
- itemcallnumber = ?,
- coded_location_qualifier = ?,
- restricted = ?,
- itemnotes = ?,
- itemnotes_nonpublic = ?,
- holdingbranch = ?,
- paidfor = ?,
- location = ?,
- permanent_location = ?,
- onloan = ?,
- issues = ?,
- renewals = ?,
- reserves = ?,
- cn_source = ?,
- cn_sort = ?,
- ccode = ?,
- itype = ?,
- materials = ?,
- uri = ?,
- enumchron = ?,
- more_subfields_xml = ?,
- copynumber = ?,
- stocknumber = ?,
- new_status = ?
- ";
- my $sth = $dbh->prepare($query);
- my $today = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
- $sth->execute(
- $item->{'biblionumber'},
- $item->{'biblioitemnumber'},
- $barcode,
- $item->{'dateaccessioned'},
- $item->{'booksellerid'},
- $item->{'homebranch'},
- $item->{'price'},
- $item->{'replacementprice'},
- $item->{'replacementpricedate'} || $today,
- $item->{datelastborrowed},
- $item->{datelastseen} || $today,
- $item->{stack},
- $item->{'notforloan'},
- $item->{'damaged'},
- $item->{'itemlost'},
- $item->{'withdrawn'},
- $item->{'itemcallnumber'},
- $item->{'coded_location_qualifier'},
- $item->{'restricted'},
- $item->{'itemnotes'},
- $item->{'itemnotes_nonpublic'},
- $item->{'holdingbranch'},
- $item->{'paidfor'},
- $item->{'location'},
- $item->{'permanent_location'},
- $item->{'onloan'},
- $item->{'issues'},
- $item->{'renewals'},
- $item->{'reserves'},
- $item->{'items.cn_source'},
- $item->{'items.cn_sort'},
- $item->{'ccode'},
- $item->{'itype'},
- $item->{'materials'},
- $item->{'uri'},
- $item->{'enumchron'},
- $item->{'more_subfields_xml'},
- $item->{'copynumber'},
- $item->{'stocknumber'},
- $item->{'new_status'},
- );
-
- my $itemnumber;
- if ( defined $sth->errstr ) {
- $error.="ERROR in _koha_new_item $query".$sth->errstr;
- }
- else {
- $itemnumber = $dbh->{'mysql_insertid'};
- }
-
- return ( $itemnumber, $error );
-}
-
=head2 MoveItemFromBiblio
MoveItemFromBiblio($itenumber, $frombiblio, $tobiblio);
return;
}
-=head2 ItemSafeToDelete
-
- ItemSafeToDelete( $biblionumber, $itemnumber);
-
-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 ItemSafeToDelete {
- my ( $biblionumber, $itemnumber ) = @_;
- my $status;
- my $dbh = C4::Context->dbh;
-
- my $error;
-
- my $countanalytics = GetAnalyticsCount($itemnumber);
-
- # check that there is no issue on this item before deletion.
- my $sth = $dbh->prepare(
- q{
- SELECT COUNT(*) FROM issues
- WHERE itemnumber = ?
- }
- );
- $sth->execute($itemnumber);
- my ($onloan) = $sth->fetchrow;
-
- my $item = GetItem($itemnumber);
-
- if ($onloan) {
- $status = "book_on_loan";
- }
- elsif ( defined C4::Context->userenv
- and !C4::Context->IsSuperLibrarian()
- and C4::Context->preference("IndependentBranches")
- and ( C4::Context->userenv->{branch} ne $item->{'homebranch'} ) )
- {
- $status = "not_same_branch";
- }
- else {
- # check it doesn't have a waiting reserve
- $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) {
- $status = "book_reserved";
- }
- elsif ( $countanalytics > 0 ) {
- $status = "linked_analytics";
- }
- else {
- $status = 1;
- }
- }
- 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
-
- my ($itemnumber,$error) =_koha_modify_item( $item );
-
-Perform the actual update of the C<items> row. Note that this
-routine accepts a hashref specifying the columns to update.
-
-=cut
-
-sub _koha_modify_item {
- my ( $item ) = @_;
- my $dbh=C4::Context->dbh;
- my $error;
-
- my $query = "UPDATE items SET ";
- my @bind;
- _mod_item_dates( $item );
- for my $key ( keys %$item ) {
- next if ( $key eq 'itemnumber' );
- $query.="$key=?,";
- push @bind, $item->{$key};
- }
- $query =~ s/,$//;
- $query .= " WHERE itemnumber=?";
- push @bind, $item->{'itemnumber'};
- my $sth = $dbh->prepare($query);
- $sth->execute(@bind);
- if ( $sth->err ) {
- $error.="ERROR in _koha_modify_item $query: ".$sth->errstr;
- warn $error;
- }
- 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 );
-
-Internal function to delete an item record from the koha tables
-
-=cut
-
-sub _koha_delete_item {
- my ( $itemnum ) = @_;
-
- my $dbh = C4::Context->dbh;
- # save the deleted item to deleteditems table
- my $sth = $dbh->prepare("SELECT * FROM items WHERE itemnumber=?");
- $sth->execute($itemnum);
- my $data = $sth->fetchrow_hashref();
-
- # There is no item to delete
- return 0 unless $data;
-
- my $query = "INSERT INTO deleteditems SET ";
- my @bind = ();
- foreach my $key ( keys %$data ) {
- next if ( $key eq 'timestamp' ); # timestamp will be set by db
- $query .= "$key = ?,";
- push( @bind, $data->{$key} );
- }
- $query =~ s/\,$//;
- $sth = $dbh->prepare($query);
- $sth->execute(@bind);
-
- # delete from items table
- $sth = $dbh->prepare("DELETE FROM items WHERE itemnumber=?");
- my $deleted = $sth->execute($itemnum);
- return ( $deleted == 1 ) ? 1 : 0;
-}
-
=head2 _marc_from_item_hash
my $item_marc = _marc_from_item_hash($item, $frameworkcode[, $unlinked_item_subfields]);
my $item_marc = MARC::Record->new();
foreach my $item_field ( keys %{$mungeditem} ) {
- my ( $tag, $subfield ) = C4::Biblio::GetMarcFromKohaField( $item_field, $frameworkcode );
+ my ( $tag, $subfield ) = C4::Biblio::GetMarcFromKohaField( $item_field );
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){
my $original_item_marc = shift;
my $frameworkcode = shift;
- my $marcstructure = GetMarcStructure(1, $frameworkcode, { unsafe => 1 });
+ my $marcstructure = C4::Biblio::GetMarcStructure(1, $frameworkcode, { unsafe => 1 });
# assume that this record has only one field, and that that
# field contains only the item information
return ($result);
}
-=head2 SearchItemsByField
-
- my $items = SearchItemsByField($field, $value);
-
-SearchItemsByField will search for items on a specific given field.
-For instance you can search all items with a specific stocknumber like this:
-
- my $items = SearchItemsByField('stocknumber', $stocknumber);
-
-=cut
-
-sub SearchItemsByField {
- my ($field, $value) = @_;
-
- my $filters = {
- field => $field,
- query => $value,
- };
-
- my ($results) = SearchItems($filters);
- return $results;
-}
-
sub _SearchItems_build_where_fragment {
my ($filter) = @_;
push @columns, Koha::Database->new()->schema()->resultset('Biblio')->result_source->columns;
push @columns, Koha::Database->new()->schema()->resultset('Biblioitem')->result_source->columns;
my @operators = qw(= != > < >= <= like);
- my $field = $filter->{field};
- if ( (0 < grep /^$field$/, @columns) or (substr($field, 0, 5) eq 'marc:') ) {
+ my $field = $filter->{field} // q{};
+ if ( (0 < grep { $_ eq $field } @columns) or (substr($field, 0, 5) eq 'marc:') ) {
my $op = $filter->{operator};
my $query = $filter->{query};
- if (!$op or (0 == grep /^$op$/, @operators)) {
+ if (!$op or (0 == grep { $_ eq $op } @operators)) {
$op = '='; # default operator
}
}
$column = "ExtractValue($sqlfield, '$xpath')";
}
+ } elsif ($field eq 'issues') {
+ # Consider NULL as 0 for issues count
+ $column = 'COALESCE(issues,0)';
} else {
$column = $field;
}
$query .= qq{ AND $where_str };
}
- $query .= q{ AND biblio_metadata.format = 'marcxml' AND biblio_metadata.marcflavour = ? };
+ $query .= q{ AND biblio_metadata.format = 'marcxml' AND biblio_metadata.schema = ? };
push @where_args, C4::Context->preference('marcflavour');
my @columns = Koha::Database->new()->schema()->resultset('Item')->result_source->columns;
my $dbh = C4::Context->dbh;
$frameworkcode = C4::Biblio::GetFrameworkCode($bibnum) if $bibnum;
- my ( $itemtagfield, $itemtagsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber", $frameworkcode );
+ my ( $itemtagfield, $itemtagsubfield ) = C4::Biblio::GetMarcFromKohaField( "items.itemnumber" );
# 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 } );
+ my $tagslib = GetMarcStructure( 1, $frameworkcode, { unsafe => 1 } );
# return nothing if we don't have found an existing framework.
return q{} unless $tagslib;
$defaultvalue =~ s/"/"/g;
}
+ my $maxlength = $tagslib->{$tag}->{$subfield}->{maxlength};
+
# search for itemcallnumber if applicable
if ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
- && C4::Context->preference('itemcallnumber') ) {
- my $CNtag = substr( C4::Context->preference('itemcallnumber'), 0, 3 );
- my $CNsubfield = substr( C4::Context->preference('itemcallnumber'), 3, 1 );
- if ( $itemrecord and my $field = $itemrecord->field($CNtag) ) {
- $defaultvalue = $field->subfield($CNsubfield);
+ && C4::Context->preference('itemcallnumber') && $itemrecord) {
+ foreach my $itemcn_pref (split(/,/,C4::Context->preference('itemcallnumber'))){
+ my $CNtag = substr( $itemcn_pref, 0, 3 );
+ next unless my $field = $itemrecord->field($CNtag);
+ my $CNsubfields = substr( $itemcn_pref, 3 );
+ $defaultvalue = $field->as_string( $CNsubfields, ' ');
+ last if $defaultvalue;
}
}
if ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
push @authorised_values, "" unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
my $class_sources = GetClassSources();
- my $default_source = C4::Context->preference("DefaultClassificationSource");
+ my $default_source = $defaultvalue || C4::Context->preference("DefaultClassificationSource");
foreach my $class_source (sort keys %$class_sources) {
next unless $class_sources->{$class_source}->{'used'} or
$subfield_data{marc_value} = {
type => 'select',
values => \@authorised_values,
- default => "$defaultvalue",
+ default => $defaultvalue // q{},
labels => \%authorised_lib,
};
} elsif ( $tagslib->{$tag}->{$subfield}->{value_builder} ) {
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);
+ $defaultvalue = $field->subfield($subfield) || q{};
}
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[<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="255" value="$defaultvalue" /><a href="#" id="buttonDot_$subfield_data{id}" tabindex="$tab" class="buttonDot $class" title="$title">...</a>\n].$plugin->javascript;
+ $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;
} else {
warn $plugin->errstr;
- $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="255" value="$defaultvalue" />); # supply default input form
+ $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
}
}
elsif ( $tag eq '' ) { # it's an hidden field
- $subfield_data{marc_value} = qq(<input type="hidden" tabindex="1" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="255" value="$defaultvalue" />);
+ $subfield_data{marc_value} = qq(<input type="hidden" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
}
elsif ( $tagslib->{$tag}->{$subfield}->{'hidden'} ) { # FIXME: shouldn't input type be "hidden" ?
- $subfield_data{marc_value} = qq(<input type="text" tabindex="1" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="255" value="$defaultvalue" />);
+ $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
}
elsif ( length($defaultvalue) > 100
or (C4::Context->preference("marcflavour") eq "UNIMARC" and
500 <= $tag && $tag < 600 )
) {
# oversize field (textarea)
- $subfield_data{marc_value} = qq(<textarea tabindex="1" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="255">$defaultvalue</textarea>\n");
+ $subfield_data{marc_value} = qq(<textarea id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength">$defaultvalue</textarea>\n");
} else {
- $subfield_data{marc_value} = "<input type=\"text\" name=\"field_value\" value=\"$defaultvalue\" size=\"50\" maxlength=\"255\" />";
+ $subfield_data{marc_value} = qq(<input type="text" id="$subfield_data{id}" name="field_value" class="input_marceditor" size="50" maxlength="$maxlength" value="$defaultvalue" />);
}
push( @loop_data, \%subfield_data );
}
my $age = $rule->{age};
my $conditions = $rule->{conditions};
my $substitutions = $rule->{substitutions};
+ foreach ( @$substitutions ) {
+ ( $_->{item_field} ) = ( $_->{field} =~ /items\.(.*)/ );
+ }
my @params;
my $query = q|
- SELECT items.biblionumber, items.itemnumber
+ SELECT items.*
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
+ grep { $_ eq $condition->{field} } @item_columns
+ or grep { $_ eq $condition->{field} } @biblioitem_columns
) {
if ( $condition->{value} =~ /\|/ ) {
my @values = split /\|/, $condition->{value};
while ( my $values = $sth->fetchrow_hashref ) {
my $biblionumber = $values->{biblionumber};
my $itemnumber = $values->{itemnumber};
- my $item = C4::Items::GetItem( $itemnumber );
+ my $item = Koha::Items->find($itemnumber);
for my $substitution ( @$substitutions ) {
+ my $field = $substitution->{item_field};
+ my $value = $substitution->{value};
next unless $substitution->{field};
- C4::Items::ModItem( {$substitution->{field} => $substitution->{value}}, $biblionumber, $itemnumber )
- unless $report_only;
+ next if ( defined $values->{ $substitution->{item_field} } and $values->{ $substitution->{item_field} } eq $substitution->{value} );
+ $item->$field($value);
push @{ $report->{$itemnumber} }, $substitution;
}
+ $item->store unless $report_only;
}
}
return $report;
}
-
1;