use Modern::Perl;
-use vars qw(@ISA @EXPORT);
+our (@ISA, @EXPORT_OK);
BEGIN {
require Exporter;
@ISA = qw(Exporter);
- @EXPORT = qw(
+ @EXPORT_OK = qw(
AddItemFromMarc
AddItemBatchFromMarc
ModItemFromMarc
Item2Marc
- ModItem
ModDateLastSeen
ModItemTransfer
- DelItem
CheckItemPreSave
GetItemsForInventory
GetItemsInfo
GetHostItemsInfo
get_hostitemnumbers_of
GetHiddenItemnumbers
- ItemSafeToDelete
- DelItemCheck
+ GetMarcItem
MoveItemFromBiblio
CartToShelf
GetAnalyticsCount
SearchItems
PrepareItemrecordDisplay
+ ToggleNewStatus
);
}
-use Carp;
-use Try::Tiny;
+use Carp qw( croak );
use C4::Context;
use C4::Koha;
-use C4::Biblio;
-use Koha::DateUtils;
+use C4::Biblio qw( GetMarcStructure TransformMarcToKoha );
+use Koha::DateUtils qw( dt_from_string output_pref );
use MARC::Record;
-use C4::ClassSource;
-use C4::Log;
-use List::MoreUtils qw(any);
-use YAML qw(Load);
+use C4::ClassSource qw( GetClassSort GetClassSources GetClassSource );
+use C4::Log qw( logaction );
+use List::MoreUtils qw( any );
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 output_pref );
use Koha::Database;
use Koha::Biblioitems;
use Koha::Items;
use Koha::ItemTypes;
use Koha::SearchEngine;
+use Koha::SearchEngine::Indexer;
use Koha::SearchEngine::Search;
use Koha::Libraries;
=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.
+The final optional parameter, C<$params>, expected to contain
+'skip_record_index' key, which relayed down to Koha::Item/store,
+there it prevents calling of index_records,
+which takes most of the time in batch adds/deletes: index_records
+to be called later in C<additem.pl> after the whole loop.
+
+$params:
+ skip_record_index => 1|0
+
=cut
sub AddItemFromMarc {
- my ( $source_item_marc, $biblionumber ) = @_;
+ my $source_item_marc = shift;
+ my $biblionumber = shift;
+ my $params = @_ ? shift : {};
+
my $dbh = C4::Context->dbh;
# parse item hash from MARC
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;
- my $item = Koha::Item->new( $item_values )->store;
+ $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_record_index => $params->{skip_record_index} });
return ( $item->biblionumber, $item->biblioitemnumber, $item->itemnumber );
}
sub AddItemBatchFromMarc {
my ($record, $biblionumber, $biblioitemnumber, $frameworkcode) = @_;
- my $error;
my @itemnumbers = ();
my @errors = ();
my $dbh = C4::Context->dbh;
$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_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));
}
$record->delete_field($item_field);
}
- # update the MARC biblio
- # $biblionumber = ModBiblioMarc( $record, $biblionumber, $frameworkcode );
-
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.
+my $item = ModItemFromMarc($item_marc, $biblionumber, $itemnumber[, $params]);
-Note that only columns that can be directly
-changed from the cataloging and serials
-item editors are included in this hash.
+The final optional parameter, C<$params>, expected to contain
+'skip_record_index' key, which relayed down to Koha::Item/store,
+there it prevents calling of index_records,
+which takes most of the time in batch adds/deletes: index_records better
+to be called later in C<additem.pl> after the whole loop.
-Returns item record
+$params:
+ skip_record_index => 1|0
=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,
- 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 ( $item_marc, $biblionumber, $itemnumber, $params ) = @_;
my $frameworkcode = C4::Biblio::GetFrameworkCode($biblionumber);
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 $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
-
my $item_object = Koha::Items->find($itemnumber);
- $item_object->more_subfields_xml(_get_unlinked_subfields_xml($unlinked_item_subfields))->store;
-
- return $item_object->get_from_storage->unblessed;
-}
+ my $item = TransformMarcToKoha( $localitemmarc, $frameworkcode, 'items' );
-=head2 ModItem
+ my ( $perm_loc_tag, $perm_loc_subfield ) = C4::Biblio::GetMarcFromKohaField( "items.permanent_location" );
+ my $has_permanent_location = defined $perm_loc_tag && defined $item_marc->subfield( $perm_loc_tag, $perm_loc_subfield );
-ModItem(
- { column => $newvalue },
- $biblionumber,
- $itemnumber,
- {
- [ unlinked_item_subfields => $unlinked_item_subfields, ]
- [ log_action => 1, ]
+ # Retrieving the values for the fields that are not linked
+ my @mapped_fields = Koha::MarcSubfieldStructures->search(
+ {
+ frameworkcode => $frameworkcode,
+ kohafield => { -like => "items.%" }
+ }
+ )->get_column('kohafield');
+ for my $c ( $item_object->_result->result_source->columns ) {
+ next if grep { "items.$c" eq $_ } @mapped_fields;
+ $item->{$c} = $item_object->$c;
}
-);
-
-Change one or more columns in an item record.
-
-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.
+ $item->{cn_source} = delete $item->{'items.cn_source'}; # Because of C4::Biblio::_disambiguate
+ delete $item->{'items.cn_sort'}; # Because of C4::Biblio::_disambiguate
+ $item->{itemnumber} = $itemnumber;
+ $item->{biblionumber} = $biblionumber;
-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
+ my $existing_cn_sort = $item_object->cn_sort; # set_or_blank will reset cn_sort to undef as we are not passing it
+ # We rely on Koha::Item->store to modify it if itemcallnumber or cn_source is modified
+ $item_object = $item_object->set_or_blank($item);
+ $item_object->cn_sort($existing_cn_sort); # Resetting to the existing value
-sub ModItem {
- my ( $item, $biblionumber, $itemnumber, $additional_params ) = @_;
- my $log_action = $additional_params->{log_action} // 1;
+ $item_object->make_column_dirty('permanent_location') if $has_permanent_location;
- _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" );
-
- _after_item_action_hooks({ action => 'modify', item_id => $itemnumber });
+ my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
+ $item_object->more_subfields_xml(_get_unlinked_subfields_xml($unlinked_item_subfields));
+ $item_object->store({ skip_record_index => $params->{skip_record_index} });
- logaction( "CATALOGUING", "MODIFY", $itemnumber, "item " . Dumper($item) )
- if $log_action && C4::Context->preference("CataloguingLog");
+ return $item_object->unblessed;
}
=head2 ModItemTransfer
- ModItemTransfer($itemnumber, $frombranch, $tobranch, $trigger);
+ ModItemTransfer($itemnumber, $frombranch, $tobranch, $trigger, [$params]);
Marks an item as being transferred from one branch to another and records the trigger.
+The last optional parameter allows for passing skip_record_index through to the items store call.
+
=cut
sub ModItemTransfer {
- my ( $itemnumber, $frombranch, $tobranch, $trigger ) = @_;
+ my ( $itemnumber, $frombranch, $tobranch, $trigger, $params ) = @_;
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 $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);
+ # NOTE: This retains the existing hard coded behaviour by ignoring transfer limits
+ # and always replacing any existing transfers. (In theory, calls to ModItemTransfer
+ # will have been preceded by a check of branch transfer limits)
+ my $to_library = Koha::Libraries->find($tobranch);
+ my $transfer = $item->request_transfer(
+ {
+ to => $to_library,
+ reason => $trigger,
+ ignore_limits => 1,
+ replace => 1
+ }
+ );
- #new entry in branchtransfers....
- my $sth = $dbh->prepare(
- "INSERT INTO branchtransfers (itemnumber, frombranch, datesent, tobranch, reason)
- VALUES (?, ?, NOW(), ?, ?)");
- $sth->execute($itemnumber, $frombranch, $tobranch, $trigger);
+ # Immediately set the item to in transit if it is checked in
+ if ( !$item->checkout ) {
+ $item->holdingbranch($frombranch)->store(
+ {
+ log_action => 0,
+ skip_record_index => $params->{skip_record_index}
+ }
+ );
+ $transfer->transit;
+ }
- # 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;
}
=head2 ModDateLastSeen
-ModDateLastSeen( $itemnumber, $leave_item_lost );
+ModDateLastSeen( $itemnumber, $leave_item_lost, $params );
Mark item as seen. Is called when an item is issued, returned or manually marked during inventory/stocktaking.
C<$itemnumber> is the item number
C<$leave_item_lost> determines if a lost item will be found or remain lost
+The last optional parameter allows for passing skip_record_index through to the items store call.
+
=cut
sub ModDateLastSeen {
- my ( $itemnumber, $leave_item_lost ) = @_;
+ my ( $itemnumber, $leave_item_lost, $params ) = @_;
my $today = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
my $item = Koha::Items->find($itemnumber);
$item->datelastseen($today);
$item->itemlost(0) unless $leave_item_lost;
- $item->store({ 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" );
-
- _after_item_action_hooks({ action => 'delete', item_id => $itemnumber });
-
- #search item field code
- logaction("CATALOGUING", "DELETE", $itemnumber, "item") if C4::Context->preference("CataloguingLog");
- return $deleted;
+ $item->store({ log_action => 0, skip_record_index => $params->{skip_record_index} });
}
=head2 CheckItemPreSave
minlocation => $minlocation,
maxlocation => $maxlocation,
location => $location,
- itemtype => $itemtype,
ignoreissued => $ignoreissued,
datelastseen => $datelastseen,
branchcode => $branchcode,
offset => $offset,
size => $size,
statushash => $statushash,
+ itemtypes => \@itemsarray,
} );
Retrieve a list of title/authors/barcode/callnumber, for biblio inventory.
my $size = $parameters->{'size'} // '';
my $statushash = $parameters->{'statushash'} // '';
my $ignore_waiting_holds = $parameters->{'ignore_waiting_holds'} // '';
+ my $itemtypes = $parameters->{'itemtypes'} || [];
my $dbh = C4::Context->dbh;
my ( @bind_params, @where_strings );
push @where_strings, 'biblioitems.itemtype = ?';
push @bind_params, $itemtype;
}
-
if ( $ignoreissued) {
$query .= "LEFT JOIN issues ON items.itemnumber = issues.itemnumber ";
push @where_strings, 'issues.date_due IS NULL';
push( @where_strings, q{(reserves.found != 'W' OR reserves.found IS NULL)} );
}
+ if ( @$itemtypes ) {
+ my $itemtypes_str = join ', ', @$itemtypes;
+ push @where_strings, "( biblioitems.itemtype IN (" . $itemtypes_str . ") OR items.itype IN (" . $itemtypes_str . ") )";
+ }
+
if ( @where_strings ) {
$query .= 'WHERE ';
$query .= join ' AND ', @where_strings;
Item's call number normalized for sorting
=back
-
+
=cut
sub GetItemsLocationInfo {
}
my @resultitems;
- my $yaml = C4::Context->preference('OpacHiddenItems');
- return () if (! $yaml =~ /\S/ );
- $yaml = "$yaml\n\n"; # YAML is anal on ending \n. Surplus does not hurt
- my $hidingrules;
- eval {
- $hidingrules = YAML::Load($yaml);
- };
- if ($@) {
- warn "Unable to parse OpacHiddenItems syspref : $@";
- return ();
- }
+ my $hidingrules = C4::Context->yaml_preference('OpacHiddenItems');
+
+ return
+ unless $hidingrules;
+
my $dbh = C4::Context->dbh;
# For each item
} keys %{ $itemrecord }
};
my $framework = C4::Biblio::GetFrameworkCode( $biblionumber );
- my $itemmarc = C4::Biblio::TransformKohaToMarc( $mungeditem ); # Bug 21774: no_split parameter removed to allow cloned subfields
+ 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 _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 _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 = ?,
- 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->{'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);
AND biblionumber = ?
|, undef, $tobiblioitem, $tobiblio, $itemnumber, $frombiblio );
if ($return == 1) {
- ModZebra( $tobiblio, "specialUpdate", "biblioserver" );
- ModZebra( $frombiblio, "specialUpdate", "biblioserver" );
+ my $indexer = Koha::SearchEngine::Indexer->new({ index => $Koha::SearchEngine::BIBLIOS_INDEX });
+ $indexer->index_records( $tobiblio, "specialUpdate", "biblioserver" );
+ $indexer->index_records( $frombiblio, "specialUpdate", "biblioserver" );
# Checking if the item we want to move is in an order
require C4::Acquisition;
my $order = C4::Acquisition::GetOrderFromItemnumber($itemnumber);
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);
-
- my $item = Koha::Items->find($itemnumber) or return;
-
- if ($item->checkout) {
- $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
- my $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]);
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);
+ push @operators, 'not like';
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};
+ my $ifnull = $filter->{ifnull};
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;
}
+ if ( defined $ifnull ) {
+ $column = "COALESCE($column, ?)";
+ }
+
if (ref $query eq 'ARRAY') {
if ($op eq '=') {
$op = 'IN';
args => [ $query ],
};
}
+
+ if ( defined $ifnull ) {
+ unshift @{ $where_fragment->{args} }, $ifnull;
+ }
}
}
=item * query: the value to search in this column
-=item * operator: comparison operator. Can be one of = != > < >= <= like
+=item * operator: comparison operator. Can be one of = != > < >= <= like 'not like'
=back
# its contents. See also GetMarcStructure.
my $tagslib = GetMarcStructure( 1, $frameworkcode, { unsafe => 1 } );
+ # Pick the default location from NewItemsDefaultLocation
+ if ( C4::Context->preference('NewItemsDefaultLocation') ) {
+ $defaultvalues //= {};
+ $defaultvalues->{location} //= C4::Context->preference('NewItemsDefaultLocation');
+ }
+
# return nothing if we don't have found an existing framework.
return q{} unless $tagslib;
my $itemrecord;
# loop through each subfield
my $cntsubf;
- foreach my $subfield ( sort keys %{ $tagslib->{$tag} } ) {
- next if IsMarcStructureInternal($tagslib->{$tag}{$subfield});
- next unless ( $tagslib->{$tag}->{$subfield}->{'tab'} );
- next if ( $tagslib->{$tag}->{$subfield}->{'tab'} ne "10" );
+ foreach my $subfield (
+ sort { $a->{display_order} <=> $b->{display_order} || $a->{subfield} cmp $b->{subfield} }
+ grep { ref($_) && %$_ } # Not a subfield (values for "important", "lib", "mandatory", etc.) or empty
+ values %{ $tagslib->{$tag} } )
+ {
+ next unless ( $subfield->{'tab'} );
+ next if ( $subfield->{'tab'} ne "10" );
my %subfield_data;
$subfield_data{tag} = $tag;
- $subfield_data{subfield} = $subfield;
+ $subfield_data{subfield} = $subfield->{subfield};
$subfield_data{countsubfield} = $cntsubf++;
- $subfield_data{kohafield} = $tagslib->{$tag}->{$subfield}->{'kohafield'};
- $subfield_data{id} = "tag_".$tag."_subfield_".$subfield."_".int(rand(1000000));
+ $subfield_data{kohafield} = $subfield->{kohafield};
+ $subfield_data{id} = "tag_".$tag."_subfield_".$subfield->{subfield}."_".int(rand(1000000));
# $subfield_data{marc_lib}=$tagslib->{$tag}->{$subfield}->{lib};
- $subfield_data{marc_lib} = $tagslib->{$tag}->{$subfield}->{lib};
- $subfield_data{mandatory} = $tagslib->{$tag}->{$subfield}->{mandatory};
- $subfield_data{repeatable} = $tagslib->{$tag}->{$subfield}->{repeatable};
+ $subfield_data{marc_lib} = $subfield->{lib};
+ $subfield_data{mandatory} = $subfield->{mandatory};
+ $subfield_data{repeatable} = $subfield->{repeatable};
$subfield_data{hidden} = "display:none"
- if ( ( $tagslib->{$tag}->{$subfield}->{hidden} > 4 )
- || ( $tagslib->{$tag}->{$subfield}->{hidden} < -4 ) );
+ if ( ( $subfield->{hidden} > 4 )
+ || ( $subfield->{hidden} < -4 ) );
my ( $x, $defaultvalue );
if ($itemrecord) {
- ( $x, $defaultvalue ) = _find_value( $tag, $subfield, $itemrecord );
+ ( $x, $defaultvalue ) = _find_value( $tag, $subfield->{subfield}, $itemrecord );
}
- $defaultvalue = $tagslib->{$tag}->{$subfield}->{defaultvalue} unless $defaultvalue;
+ $defaultvalue = $subfield->{defaultvalue} unless $defaultvalue;
if ( !defined $defaultvalue ) {
$defaultvalue = q||;
} else {
$defaultvalue =~ s/"/"/g;
+ # get today date & replace <<YYYY>>, <<MM>>, <<DD>> if provided in the default value
+ my $today_dt = dt_from_string;
+ my $year = $today_dt->strftime('%Y');
+ my $shortyear = $today_dt->strftime('%y');
+ my $month = $today_dt->strftime('%m');
+ my $day = $today_dt->strftime('%d');
+ $defaultvalue =~ s/<<YYYY>>/$year/g;
+ $defaultvalue =~ s/<<YY>>/$shortyear/g;
+ $defaultvalue =~ s/<<MM>>/$month/g;
+ $defaultvalue =~ s/<<DD>>/$day/g;
+
+ # And <<USER>> with surname (?)
+ my $username =
+ ( C4::Context->userenv
+ ? C4::Context->userenv->{'surname'}
+ : "superlibrarian" );
+ $defaultvalue =~ s/<<USER>>/$username/g;
}
- my $maxlength = $tagslib->{$tag}->{$subfield}->{maxlength};
+ my $maxlength = $subfield->{maxlength};
# search for itemcallnumber if applicable
- if ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
+ if ( $subfield->{kohafield} eq 'items.itemcallnumber'
&& C4::Context->preference('itemcallnumber') && $itemrecord) {
foreach my $itemcn_pref (split(/,/,C4::Context->preference('itemcallnumber'))){
my $CNtag = substr( $itemcn_pref, 0, 3 );
last if $defaultvalue;
}
}
- if ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.itemcallnumber'
+ if ( $subfield->{kohafield} eq 'items.itemcallnumber'
&& $defaultvalues
&& $defaultvalues->{'callnumber'} ) {
- if( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ){
+ if( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield->{subfield}) ){
# if the item record exists, only use default value if the item has no callnumber
$defaultvalue = $defaultvalues->{callnumber};
} elsif ( !$itemrecord and $defaultvalues ) {
$defaultvalue = $defaultvalues->{callnumber};
}
}
- if ( ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.holdingbranch' || $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.homebranch' )
+ if ( ( $subfield->{kohafield} eq 'items.holdingbranch' || $subfield->{kohafield} eq 'items.homebranch' )
&& $defaultvalues
&& $defaultvalues->{'branchcode'} ) {
if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) {
$defaultvalue = $defaultvalues->{branchcode};
}
}
- if ( ( $tagslib->{$tag}->{$subfield}->{kohafield} eq 'items.location' )
+ if ( ( $subfield->{kohafield} eq 'items.location' )
&& $defaultvalues
&& $defaultvalues->{'location'} ) {
- if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield) ) {
+ if ( $itemrecord and $defaultvalues and not $itemrecord->subfield($tag,$subfield->{subfield}) ) {
# if the item record exists, only use default value if the item has no locationr
$defaultvalue = $defaultvalues->{location};
} elsif ( !$itemrecord and $defaultvalues ) {
$defaultvalue = $defaultvalues->{location};
}
}
- if ( $tagslib->{$tag}->{$subfield}->{authorised_value} ) {
+ if ( $subfield->{authorised_value} ) {
my @authorised_values;
my %authorised_lib;
# builds list, depending on authorised value...
#---- branch
- if ( $tagslib->{$tag}->{$subfield}->{'authorised_value'} eq "branches" ) {
+ if ( $subfield->{'authorised_value'} eq "branches" ) {
if ( ( C4::Context->preference("IndependentBranches") )
&& !C4::Context->IsSuperLibrarian() ) {
my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches WHERE branchcode = ? ORDER BY branchname" );
$sth->execute( C4::Context->userenv->{branch} );
push @authorised_values, ""
- unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
+ unless ( $subfield->{mandatory} );
while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
push @authorised_values, $branchcode;
$authorised_lib{$branchcode} = $branchname;
my $sth = $dbh->prepare( "SELECT branchcode,branchname FROM branches ORDER BY branchname" );
$sth->execute;
push @authorised_values, ""
- unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
+ unless ( $subfield->{mandatory} );
while ( my ( $branchcode, $branchname ) = $sth->fetchrow_array ) {
push @authorised_values, $branchcode;
$authorised_lib{$branchcode} = $branchname;
}
#----- itemtypes
- } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "itemtypes" ) {
+ } elsif ( $subfield->{authorised_value} eq "itemtypes" ) {
my $itemtypes = Koha::ItemTypes->search_with_localization;
- push @authorised_values, ""
- unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
+ push @authorised_values, "";
while ( my $itemtype = $itemtypes->next ) {
push @authorised_values, $itemtype->itemtype;
$authorised_lib{$itemtype->itemtype} = $itemtype->translated_description;
}
#---- class_sources
- } elsif ( $tagslib->{$tag}->{$subfield}->{authorised_value} eq "cn_source" ) {
- push @authorised_values, "" unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
+ } elsif ( $subfield->{authorised_value} eq "cn_source" ) {
+ push @authorised_values, "";
my $class_sources = GetClassSources();
my $default_source = $defaultvalue || C4::Context->preference("DefaultClassificationSource");
#---- "true" authorised value
} else {
$authorised_values_sth->execute(
- $tagslib->{$tag}->{$subfield}->{authorised_value},
+ $subfield->{authorised_value},
$branch_limit ? $branch_limit : ()
);
- push @authorised_values, ""
- unless ( $tagslib->{$tag}->{$subfield}->{mandatory} );
+ push @authorised_values, "";
while ( my ( $value, $lib ) = $authorised_values_sth->fetchrow_array ) {
push @authorised_values, $value;
$authorised_lib{$value} = $lib;
default => $defaultvalue // q{},
labels => \%authorised_lib,
};
- } elsif ( $tagslib->{$tag}->{$subfield}->{value_builder} ) {
+ } elsif ( $subfield->{value_builder} ) {
# it is a plugin
require Koha::FrameworkPlugin;
my $plugin = Koha::FrameworkPlugin->new({
- name => $tagslib->{$tag}->{$subfield}->{value_builder},
+ name => $subfield->{value_builder},
item_style => 1,
});
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) || q{};
+ $defaultvalue = $field->subfield($subfield->{subfield}) || q{};
}
if( !$plugin->errstr ) {
#TODO Move html to template; see report 12176/13397
elsif ( $tag eq '' ) { # it's an hidden field
$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" ?
+ elsif ( $subfield->{'hidden'} ) { # FIXME: shouldn't input type be "hidden" ?
$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
- 300 <= $tag && $tag < 400 && $subfield eq 'a' )
+ 300 <= $tag && $tag < 400 && $subfield->{subfield} eq 'a' )
or (C4::Context->preference("marcflavour") eq "MARC21" and
500 <= $tag && $tag < 600 )
) {