X-Git-Url: http://koha-dev.rot13.org:8081/gitweb/?a=blobdiff_plain;f=C4%2FItems.pm;h=80edaeb948fc3635fdce284db0e1e3dcf2f15c3a;hb=2088b6857e9dd0d41acb53582897703586b7116d;hp=0b8089640059afc42cf80e44a90b65d852f3c79b;hpb=c855b1ca287758b535fe5ba885949accac4e474a;p=koha-ffzg.git diff --git a/C4/Items.pm b/C4/Items.pm index 0b80896400..80edaeb948 100644 --- a/C4/Items.pm +++ b/C4/Items.pm @@ -18,8 +18,7 @@ package C4::Items; # You should have received a copy of the GNU General Public License # along with Koha; if not, see . -use strict; -#use warnings; FIXME - Bug 2505 +use Modern::Perl; use vars qw(@ISA @EXPORT); BEGIN { @@ -28,14 +27,11 @@ BEGIN { @EXPORT = qw( AddItemFromMarc - AddItem AddItemBatchFromMarc ModItemFromMarc Item2Marc - ModItem ModDateLastSeen ModItemTransfer - DelItem CheckItemPreSave GetItemsForInventory GetItemsInfo @@ -43,12 +39,9 @@ BEGIN { GetHostItemsInfo get_hostitemnumbers_of GetHiddenItemnumbers - ItemSafeToDelete - DelItemCheck MoveItemFromBiblio CartToShelf GetAnalyticsCount - SearchItemsByField SearchItems PrepareItemrecordDisplay ); @@ -137,7 +130,7 @@ sub CartToShelf { my $item = Koha::Items->find($itemnumber); if ( $item->location eq 'CART' ) { - ModItem({ location => $item->permanent_location}, undef, $itemnumber); + $item->location($item->permanent_location)->store; } } @@ -161,73 +154,13 @@ sub AddItemFromMarc { my $localitemmarc = MARC::Record->new; $localitemmarc->append_fields( $source_item_marc->field($itemtag) ); - my $item = C4::Biblio::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 columns directly but should instead -be stored in C and included in -the biblio items tag for display and indexing. - -=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; - - $item->{'itemnumber'} = $itemnumber; - - C4::Biblio::ModZebra( $item->{biblionumber}, "specialUpdate", "biblioserver" ); - - logaction( "CATALOGUING", "ADD", $itemnumber, "item" ) - if C4::Context->preference("CataloguingLog"); - - _after_item_action_hooks({ action => 'create', item_id => $itemnumber }); - - 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; + my $item = Koha::Item->new( $item_values )->store; + return ( $item->biblionumber, $item->biblioitemnumber, $item->itemnumber ); } =head2 AddItemBatchFromMarc @@ -313,16 +246,12 @@ sub AddItemBatchFromMarc { 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->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->unblessed, $frameworkcode, $unlinked_item_subfields); $item_field->replace_with($new_item_marc->field($itemtag)); } @@ -337,83 +266,6 @@ sub AddItemBatchFromMarc { return (\@itemnumbers, \@errors); } -=head2 ModItemFromMarc - - ModItemFromMarc($item_marc, $biblionumber, $itemnumber); - -This function updates an item record based on a supplied -C object containing an embedded item field. -This API is meant for the use of C; for -other purposes, C 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, - 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; @@ -424,146 +276,43 @@ sub ModItemFromMarc { my $localitemmarc = MARC::Record->new; $localitemmarc->append_fields( $item_marc->field($itemtag) ); + my $item_object = Koha::Items->find($itemnumber); 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}; - } + $item_object->set($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. - -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 columns directly but should instead -be stored in C 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, -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}; - - 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); - } - - if ($unlinked_item_subfields) { - $item->{'more_subfields_xml'} = _get_unlinked_subfields_xml($unlinked_item_subfields); - }; - - my @fields = qw( itemlost withdrawn damaged ); - - # Only retrieve the item if we need to set an "on" date field - if ( $item->{itemlost} || $item->{withdrawn} || $item->{damaged} ) { - my $pre_mod_item = Koha::Items->find( $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" ); - - _after_item_action_hooks({ action => 'modify', item_id => $itemnumber }); - - 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 ( $item->location eq 'CART' && $item->permanent_location ne 'CART' ); + 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, { log_action => 0 }); + # 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; } @@ -583,45 +332,10 @@ sub ModDateLastSeen { 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" ); - - _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; + my $item = Koha::Items->find($itemnumber); + $item->datelastseen($today); + $item->itemlost(0) unless $leave_item_lost; + $item->store({ log_action => 0 }); } =head2 CheckItemPreSave @@ -764,7 +478,7 @@ sub GetItemsForInventory { my $max_cnsort = GetClassSort($class_source,undef,$maxlocation); my $select_columns = q{ - SELECT DISTINCT(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(DISTINCT(items.itemnumber))}; my $query = q{ @@ -860,7 +574,11 @@ sub GetItemsForInventory { # 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->{$_}}; } } @@ -948,10 +666,8 @@ sub GetItemsInfo { holding.branchcode, holding.branchname, holding.opac_info as holding_branch_opac_info, - home.opac_info as home_branch_opac_info - "; - $query .= ",IF(tmp_holdsqueue.itemnumber,1,0) AS has_pending_hold" if !C4::Context->preference('AllowItemsOnHoldCheckout'); - $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 @@ -963,9 +679,8 @@ sub GetItemsInfo { LEFT JOIN serial USING (serialid) LEFT JOIN itemtypes ON itemtypes.itemtype = " . (C4::Context->preference('item-level_itypes') ? 'items.itype' : 'biblioitems.itemtype'); - $query .= " - LEFT JOIN tmp_holdsqueue USING (itemnumber)" if !C4::Context->preference('AllowItemsOnHoldCheckout'); $query .= q| + LEFT JOIN tmp_holdsqueue USING (itemnumber) LEFT JOIN localization ON itemtypes.itemtype = localization.code AND localization.entity = 'itemtypes' AND localization.lang = ? @@ -1321,333 +1036,6 @@ the inner workings of C. =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. - -=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. - -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 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, C, C, or -C 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 -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. - -=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 - -=item * - -C - -=item * - -C - -=item * - -C - -=item * - -C - -=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 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); @@ -1698,193 +1086,6 @@ sub MoveItemFromBiblio { 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 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]); @@ -2053,29 +1254,6 @@ sub GetAnalyticsCount { 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) = @_; @@ -2104,12 +1282,12 @@ sub _SearchItems_build_where_fragment { 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 } @@ -2144,6 +1322,9 @@ sub _SearchItems_build_where_fragment { } $column = "ExtractValue($sqlfield, '$xpath')"; } + } elsif ($field eq 'issues') { + # Consider NULL as 0 for issues count + $column = 'COALESCE(issues,0)'; } else { $column = $field; } @@ -2404,11 +1585,13 @@ sub PrepareItemrecordDisplay { # 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' @@ -2492,7 +1675,7 @@ sub PrepareItemrecordDisplay { 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 @@ -2519,7 +1702,7 @@ sub PrepareItemrecordDisplay { $subfield_data{marc_value} = { type => 'select', values => \@authorised_values, - default => "$defaultvalue", + default => $defaultvalue // q{}, labels => \%authorised_lib, }; } elsif ( $tagslib->{$tag}->{$subfield}->{value_builder} ) { @@ -2532,24 +1715,24 @@ 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); + $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[...\n].$plugin->javascript; + $subfield_data{marc_value} = qq[...\n].$plugin->javascript; } else { warn $plugin->errstr; $subfield_data{marc_value} = qq(); # supply default input form } } elsif ( $tag eq '' ) { # it's an hidden field - $subfield_data{marc_value} = qq(); + $subfield_data{marc_value} = qq(); } elsif ( $tagslib->{$tag}->{$subfield}->{'hidden'} ) { # FIXME: shouldn't input type be "hidden" ? - $subfield_data{marc_value} = qq(); + $subfield_data{marc_value} = qq(); } elsif ( length($defaultvalue) > 100 or (C4::Context->preference("marcflavour") eq "UNIMARC" and @@ -2558,9 +1741,9 @@ sub PrepareItemrecordDisplay { 500 <= $tag && $tag < 600 ) ) { # oversize field (textarea) - $subfield_data{marc_value} = qq(\n"); + $subfield_data{marc_value} = qq(\n"); } else { - $subfield_data{marc_value} = qq(); + $subfield_data{marc_value} = qq(); } push( @loop_data, \%subfield_data ); } @@ -2605,8 +1788,8 @@ sub ToggleNewStatus { |; 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}; @@ -2629,51 +1812,20 @@ sub ToggleNewStatus { while ( my $values = $sth->fetchrow_hashref ) { my $biblionumber = $values->{biblionumber}; my $itemnumber = $values->{itemnumber}; + my $item = Koha::Items->find($itemnumber); for my $substitution ( @$substitutions ) { + my $field = $substitution->{item_field}; + my $value = $substitution->{value}; next unless $substitution->{field}; next if ( defined $values->{ $substitution->{item_field} } and $values->{ $substitution->{item_field} } eq $substitution->{value} ); - C4::Items::ModItem( { $substitution->{item_field} => $substitution->{value} }, $biblionumber, $itemnumber ) - unless $report_only; + $item->$field($value); push @{ $report->{$itemnumber} }, $substitution; } + $item->store unless $report_only; } } return $report; } -=head2 _after_item_action_hooks - -Helper method that takes care of calling all plugin hooks - -=cut - -sub _after_item_action_hooks { - my ( $args ) = @_; - - my $item_id = $args->{item_id}; - my $action = $args->{action}; - - if ( C4::Context->preference('UseKohaPlugins') && C4::Context->config("enable_plugins") ) { - - my @plugins = Koha::Plugins->new->GetPlugins({ - method => 'after_item_action', - }); - - if (@plugins) { - - my $item = Koha::Items->find( $item_id ); - - foreach my $plugin ( @plugins ) { - try { - $plugin->after_item_action({ action => $action, item => $item, item_id => $item_id }); - } - catch { - warn "$_"; - }; - } - } - } -} - 1;