use strict;
+use Carp;
use C4::Context;
use C4::Koha;
use C4::Biblio;
use C4::Branch;
require C4::Reserves;
use C4::Charset;
+use C4::Acquisition;
use vars qw($VERSION @ISA @EXPORT);
AddItem
AddItemBatchFromMarc
ModItemFromMarc
+ Item2Marc
ModItem
ModDateLastSeen
ModItemTransfer
GetItemsInfo
get_itemnumbers_of
GetItemnumberFromBarcode
+
+ DelItemCheck
+ MoveItemFromBiblio
+ GetLatestAcquisitions
+ CartToShelf
);
}
return $data;
} # sub GetItem
+=head2 CartToShelf
+
+=over 4
+
+CartToShelf($itemnumber);
+
+=back
+
+Set the current shelving location of the item record
+to its stored permanent shelving location. This is
+primarily used to indicate when an item whose current
+location is a special processing ('PROC') or shelving cart
+('CART') location is back in the stacks.
+
+=cut
+
+sub CartToShelf {
+ my ( $itemnumber ) = @_;
+
+ unless ( $itemnumber ) {
+ croak "FAILED CartToShelf() - no itemnumber supplied";
+ }
+
+ my $item = GetItem($itemnumber);
+ $item->{location} = $item->{permanent_location};
+ ModItem($item, undef, $itemnumber);
+}
+
=head2 AddItemFromMarc
=over 4
_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} );
$item->{'itemnumber'} = $itemnumber;
my $dbh = C4::Context->dbh;
my $frameworkcode = GetFrameworkCode( $biblionumber );
- my $item = &TransformMarcToKoha( $dbh, $item_marc, $frameworkcode );
+ my ($itemtag,$itemsubfield)=GetMarcFromKohaField("items.itemnumber",$frameworkcode);
+
+ my $localitemmarc=MARC::Record->new;
+ $localitemmarc->append_fields($item_marc->field($itemtag));
+ my $item = &TransformMarcToKoha( $dbh, $item_marc, $frameworkcode ,'items');
foreach my $item_field (keys %default_values_for_mod_from_marc) {
$item->{$item_field} = $default_values_for_mod_from_marc{$item_field} unless exists $item->{$item_field};
}
while ( my ( $authorisedvalue, $lib ) = $authvalsth->fetchrow ) {
$itemstatus{$authorisedvalue} = $lib;
}
- $authvalsth->finish;
return \%itemstatus;
exit 1;
}
#No authvalue list
# build default
}
- $sth->finish;
}
#No authvalue list
while ( my ( $authorisedvalue, $lib ) = $authvalsth->fetchrow ) {
$itemlocation{$authorisedvalue} = $lib;
}
- $authvalsth->finish;
return \%itemlocation;
exit 1;
}
#No authvalue list
# build default
}
- $sth->finish;
}
#No authvalue list
my $query = "
SELECT *
- FROM items, biblio, authorised_values
+ FROM items
+ LEFT JOIN biblio ON (items.biblionumber = biblio.biblionumber)
+ LEFT JOIN biblioitems ON (items.biblionumber = biblioitems.biblionumber)
+ LEFT JOIN authorised_values ON (items.itemlost = authorised_values.authorised_value)
WHERE
- items.biblionumber = biblio.biblionumber
- AND items.itemlost = authorised_values.authorised_value
- AND authorised_values.category = 'LOST'
+ authorised_values.category = 'LOST'
AND itemlost IS NOT NULL
AND itemlost <> 0
-
";
my @query_parameters;
foreach my $key (keys %$where) {
$query .= " AND $key LIKE ?";
push @query_parameters, "%$where->{$key}%";
}
- if ( defined $orderby ) {
- $query .= ' ORDER BY ?';
- push @query_parameters, $orderby;
+ my @ordervalues = qw/title author homebranch itype barcode price replacementprice lib datelastseen location/;
+
+ if ( defined $orderby && grep($orderby, @ordervalues)) {
+ $query .= ' ORDER BY '.$orderby;
}
my $sth = $dbh->prepare($query);
=over 4
-$itemlist = GetItemsForInventory($minlocation, $maxlocation, $location, $itemtype $datelastseen, $branch, $offset, $size);
+$itemlist = GetItemsForInventory($minlocation, $maxlocation, $location, $itemtype $datelastseen, $branch, $offset, $size, $statushash);
=back
The required minlocation & maxlocation parameters are used to specify a range of item callnumbers
the datelastseen can be used to specify that you want to see items not seen since a past date only.
offset & size can be used to retrieve only a part of the whole listing (defaut behaviour)
+$statushash requires a hashref that has the authorized values fieldname (intems.notforloan, etc...) as keys, and an arrayref of statuscodes we are searching for as values.
=cut
sub GetItemsForInventory {
- my ( $minlocation, $maxlocation,$location, $itemtype, $datelastseen, $branch, $offset, $size ) = @_;
+ my ( $minlocation, $maxlocation,$location, $itemtype, $ignoreissued, $datelastseen, $branch, $offset, $size, $statushash ) = @_;
my $dbh = C4::Context->dbh;
+ my ( @bind_params, @where_strings );
my $query = <<'END_SQL';
-SELECT itemnumber, barcode, itemcallnumber, title, author, biblio.biblionumber, datelastseen
+SELECT items.itemnumber, barcode, itemcallnumber, title, author, biblio.biblionumber, datelastseen
FROM items
LEFT JOIN biblio ON items.biblionumber = biblio.biblionumber
LEFT JOIN biblioitems on items.biblionumber = biblioitems.biblionumber
-WHERE itemcallnumber >= ?
- AND itemcallnumber <= ?
END_SQL
- my @bind_params = ( $minlocation, $maxlocation );
+ if ($statushash){
+ for my $authvfield (keys %$statushash){
+ if ( scalar @{$statushash->{$authvfield}} > 0 ){
+ my $joinedvals = join ',', @{$statushash->{$authvfield}};
+ push @where_strings, "$authvfield in (" . $joinedvals . ")";
+ }
+ }
+ }
+
+ if ($minlocation) {
+ push @where_strings, 'itemcallnumber >= ?';
+ push @bind_params, $minlocation;
+ }
+
+ if ($maxlocation) {
+ push @where_strings, 'itemcallnumber <= ?';
+ push @bind_params, $maxlocation;
+ }
if ($datelastseen) {
$datelastseen = format_date_in_iso($datelastseen);
- $query .= ' AND (datelastseen < ? OR datelastseen IS NULL) ';
+ push @where_strings, '(datelastseen < ? OR datelastseen IS NULL)';
push @bind_params, $datelastseen;
}
if ( $location ) {
- $query.= ' AND items.location = ? ';
+ push @where_strings, 'items.location = ?';
push @bind_params, $location;
}
if ( $branch ) {
- $query.= ' AND items.homebranch = ? ';
+ push @where_strings, 'items.homebranch = ?';
push @bind_params, $branch;
}
if ( $itemtype ) {
- $query.= ' AND biblioitems.itemtype = ? ';
+ 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';
+ }
+
+ if ( @where_strings ) {
+ $query .= 'WHERE ';
+ $query .= join ' AND ', @where_strings;
+ }
$query .= ' ORDER BY itemcallnumber, title';
my $sth = $dbh->prepare($query);
$sth->execute( @bind_params );
my $sth = $dbh->prepare($query);
$sth->execute($biblionumber);
my $count = $sth->fetchrow;
- $sth->finish;
return ($count);
}
# set date_due to blank, so in the template we check itemlost, and wthdrawn
$data->{'date_due'} = '';
} # else
- $sth2->finish;
# Find the last 3 people who borrowed this item.
my $query2 = "SELECT * FROM old_issues, borrowers WHERE itemnumber = ?
AND old_issues.borrowernumber = borrowers.borrowernumber
$data->{"borrower$i2"} = $data2->{'borrowernumber'};
$i2++;
}
- $sth2->finish;
push(@results,$data);
}
- $sth->finish;
return (\@results);
}
sub GetItemsInfo {
my ( $biblionumber, $type ) = @_;
my $dbh = C4::Context->dbh;
- my $query = "SELECT items.*,biblio.*,biblioitems.volume,biblioitems.number,biblioitems.itemtype,biblioitems.isbn,biblioitems.issn,biblioitems.publicationyear,biblioitems.publishercode,biblioitems.volumedate,biblioitems.volumedesc,biblioitems.lccn,biblioitems.url,items.notforloan as itemnotforloan
- FROM items
- LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
- LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber";
- $query .= (C4::Context->preference('item-level_itypes')) ?
- " LEFT JOIN itemtypes on items.itype = itemtypes.itemtype "
- : " LEFT JOIN itemtypes on biblioitems.itemtype = itemtypes.itemtype ";
- $query .= "WHERE items.biblionumber = ? ORDER BY items.dateaccessioned desc" ;
+ # note biblioitems.* must be avoided to prevent large marc and marcxml fields from killing performance.
+ my $query = "
+ SELECT items.*,
+ biblio.*,
+ biblioitems.volume,
+ biblioitems.number,
+ biblioitems.itemtype,
+ biblioitems.isbn,
+ biblioitems.issn,
+ biblioitems.publicationyear,
+ biblioitems.publishercode,
+ biblioitems.volumedate,
+ biblioitems.volumedesc,
+ biblioitems.lccn,
+ biblioitems.url,
+ items.notforloan as itemnotforloan,
+ itemtypes.description
+ FROM items
+ LEFT JOIN branches ON items.homebranch = branches.branchcode
+ LEFT JOIN biblio ON biblio.biblionumber = items.biblionumber
+ LEFT JOIN biblioitems ON biblioitems.biblioitemnumber = items.biblioitemnumber
+ LEFT JOIN itemtypes ON itemtypes.itemtype = "
+ . (C4::Context->preference('item-level_itypes') ? 'items.itype' : 'biblioitems.itemtype');
+ $query .= " WHERE items.biblionumber = ? ORDER BY branches.branchname,items.dateaccessioned desc" ;
my $sth = $dbh->prepare($query);
$sth->execute($biblionumber);
my $i = 0;
my @results;
- my ( $date_due, $count_reserves, $serial );
+ my $serial;
my $isth = $dbh->prepare(
"SELECT issues.*,borrowers.cardnumber,borrowers.surname,borrowers.firstname,borrowers.branchcode as bcode
my $ssth = $dbh->prepare("SELECT serialseq,publisheddate from serialitems left join serial on serialitems.serialid=serial.serialid where serialitems.itemnumber=? ");
while ( my $data = $sth->fetchrow_hashref ) {
my $datedue = '';
+ my $count_reserves;
$isth->execute( $data->{'itemnumber'} );
if ( my $idata = $isth->fetchrow_hashref ) {
$data->{borrowernumber} = $idata->{borrowernumber};
$datedue = $idata->{'date_due'};
if (C4::Context->preference("IndependantBranches")){
my $userenv = C4::Context->userenv;
- if ( ($userenv) && ( $userenv->{flags} != 1 ) ) {
+ if ( ($userenv) && ( $userenv->{flags} % 2 != 1 ) ) {
$data->{'NOTSAMEBRANCH'} = 1 if ($idata->{'bcode'} ne $userenv->{branch});
}
}
if ( $datedue eq '' ) {
my ( $restype, $reserves ) =
C4::Reserves::CheckReserves( $data->{'itemnumber'} );
- if ($restype) {
- $count_reserves = $restype;
- }
+# Previous conditional check with if ($restype) is not needed because a true
+# result for one item will result in subsequent items defaulting to this true
+# value.
+ $count_reserves = $restype;
}
- $isth->finish;
- $ssth->finish;
#get branch information.....
my $bsth = $dbh->prepare(
"SELECT * FROM branches WHERE branchcode = ?
my $sth2 = $dbh->prepare("SELECT * FROM old_issues,borrowers
WHERE itemnumber = ?
AND old_issues.borrowernumber = borrowers.borrowernumber
+ ORDER BY returndate DESC
LIMIT 3");
$sth2->execute($data->{'itemnumber'});
my $ii = 0;
$results[$i] = $data;
$i++;
}
- $sth->finish;
if($serial) {
return( sort { ($b->{'publisheddate'} || $b->{'enumchron'}) cmp ($a->{'publisheddate'} || $a->{'enumchron'}) } @results );
} else {
}
}
+=head2 GetLastAcquisitions
+
+=over 4
+
+my $lastacq = GetLastAcquisitions({'branches' => ('branch1','branch2'), 'itemtypes' => ('BK','BD')}, 10);
+
+=back
+
+=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 get_itemnumbers_of
=over 4
authorised values for a biblio.
parameters: listref of authorised values, such as comes from
- get_item_ahtorised_values or
+ get_item_authorised_values or
from C4::Biblio::get_biblio_authorised_values
returns: listref of hashrefs for each image. Each hashref looks like
# Tack on 'items.' prefix to column names so that TransformKohaToMarc will work.
# Also, don't emit a subfield if the underlying field is blank.
- my $mungeditem = { map { $itemrecord->{$_} ne '' ? ("items.$_" => $itemrecord->{$_}) : () } keys %{ $itemrecord } };
+
+
+ return Item2Marc($itemrecord,$biblionumber);
+
+}
+sub Item2Marc {
+ my ($itemrecord,$biblionumber)=@_;
+ my $mungeditem = {
+ map {
+ defined($itemrecord->{$_}) && $itemrecord->{$_} ne '' ? ("items.$_" => $itemrecord->{$_}) : ()
+ } keys %{ $itemrecord }
+ };
my $itemmarc = TransformKohaToMarc($mungeditem);
+ my ( $itemtag, $itemsubfield ) = GetMarcFromKohaField("items.itemnumber",GetFrameworkCode($biblionumber)||'');
my $unlinked_item_subfields = _parse_unlinked_item_subfields_from_xml($mungeditem->{'items.more_subfields_xml'});
if (defined $unlinked_item_subfields and $#$unlinked_item_subfields > -1) {
- my @fields = $itemmarc->fields();
- if ($#fields > -1) {
- $fields[0]->add_subfields(@$unlinked_item_subfields);
+ foreach my $field ($itemmarc->field($itemtag)){
+ $field->add_subfields(@$unlinked_item_subfields);
}
}
-
- return $itemmarc;
-
+ return $itemmarc;
}
=head1 PRIVATE FUNCTIONS AND VARIABLES
(not defined $item->{'wthdrawn'} or $item->{'wthdrawn'} eq '')) {
$item->{'wthdrawn'} = 0;
}
+ if (exists $item->{'location'} && !exists $item->{'permanent_location'}) {
+ $item->{'permanent_location'} = $item->{'location'};
+ }
}
=head2 _get_single_item_column
sub _set_defaults_for_add {
my $item = shift;
-
- # if dateaccessioned is provided, use it. Otherwise, set to NOW()
- if (!(exists $item->{'dateaccessioned'}) ||
- ($item->{'dateaccessioned'} eq '')) {
- # FIXME add check for invalid date
- my $today = C4::Dates->new();
- $item->{'dateaccessioned'} = $today->output("iso"); #TODO: check time issues
- }
-
- # various item status fields cannot be null
- $item->{'notforloan'} = 0 unless exists $item->{'notforloan'} and defined $item->{'notforloan'} and $item->{'notforloan'} ne '';
- $item->{'damaged'} = 0 unless exists $item->{'damaged'} and defined $item->{'damaged'} and $item->{'damaged'} ne '';
- $item->{'itemlost'} = 0 unless exists $item->{'itemlost'} and defined $item->{'itemlost'} and $item->{'itemlost'} ne '';
- $item->{'wthdrawn'} = 0 unless exists $item->{'wthdrawn'} and defined $item->{'wthdrawn'} and $item->{'wthdrawn'} ne '';
+ $item->{dateaccessioned} ||= C4::Dates->new->output('iso');
+ $item->{$_} ||= 0 for (qw( notforloan damaged itemlost wthdrawn));
}
=head2 _koha_new_item
if ( defined $sth->errstr ) {
$error.="ERROR in _koha_new_item $query".$sth->errstr;
}
- $sth->finish();
return ( $itemnumber, $error );
}
+=head2 MoveItemFromBiblio
+
+=over 4
+
+MoveItemFromBiblio($itenumber, $frombiblio, $tobiblio);
+
+=back
+
+Moves an item from a biblio to another
+
+Returns undef if the move failed or the biblionumber of the destination record otherwise
+=cut
+sub MoveItemFromBiblio {
+ my ($itemnumber, $frombiblio, $tobiblio) = @_;
+ my $dbh = C4::Context->dbh;
+ my $sth = $dbh->prepare("SELECT biblioitemnumber FROM biblioitems WHERE biblionumber = ?");
+ $sth->execute( $tobiblio );
+ my ( $tobiblioitem ) = $sth->fetchrow();
+ $sth = $dbh->prepare("UPDATE items SET biblioitemnumber = ?, biblionumber = ? WHERE itemnumber = ? AND biblionumber = ?");
+ my $return = $sth->execute($tobiblioitem, $tobiblio, $itemnumber, $frombiblio);
+ if ($return == 1) {
+
+ # Getting framework
+ my $frameworkcode = GetFrameworkCode($frombiblio);
+
+ # Getting marc field for itemnumber
+ my ($itemtag, $itemsubfield) = GetMarcFromKohaField('items.itemnumber', $frameworkcode);
+
+ # Getting the record we want to move the item from
+ my $record = GetMarcBiblio($frombiblio);
+
+ # The item we want to move
+ my $item;
+
+ # For each item
+ foreach my $fielditem ($record->field($itemtag)){
+ # If it is the item we want to move
+ if ($fielditem->subfield($itemsubfield) == $itemnumber) {
+ # We save it
+ $item = $fielditem;
+ # Then delete it from the record
+ $record->delete_field($fielditem)
+ }
+ }
+
+ # If we found an item (should always true, except in case of database-marcxml inconsistency)
+ if ($item) {
+
+ # Checking if the item we want to move is in an order
+ my $order = GetOrderFromItemnumber($itemnumber);
+ if ($order) {
+ # Replacing the biblionumber within the order if necessary
+ $order->{'biblionumber'} = $tobiblio;
+ ModOrder($order);
+ }
+
+ # Saving the modification
+ ModBiblioMarc($record, $frombiblio, $frameworkcode);
+
+ # Getting the record we want to move the item to
+ $record = GetMarcBiblio($tobiblio);
+
+ # Inserting the previously saved item
+ $record->insert_fields_ordered($item);
+
+ # Saving the modification
+ ModBiblioMarc($record, $tobiblio, $frameworkcode);
+
+ } else {
+ return undef;
+ }
+ } else {
+ return undef;
+ }
+}
+
+=head2 DelItemCheck
+
+=over 4
+
+DelItemCheck($dbh, $biblionumber, $itemnumber);
+
+=back
+
+Exported function (core API) for deleting an item record in Koha if there no current issue.
+
+=cut
+
+sub DelItemCheck {
+ my ( $dbh, $biblionumber, $itemnumber ) = @_;
+ my $error;
+
+ # check that there is no issue on this item before deletion.
+ my $sth=$dbh->prepare("select * from issues i where i.itemnumber=?");
+ $sth->execute($itemnumber);
+
+ my $onloan=$sth->fetchrow;
+
+ if ($onloan){
+ $error = "book_on_loan"
+ }else{
+ # check it doesnt have a waiting reserve
+ $sth=$dbh->prepare("SELECT * FROM reserves WHERE found = 'W' AND itemnumber = ?");
+ $sth->execute($itemnumber);
+ my $reserve=$sth->fetchrow;
+ if ($reserve){
+ $error = "book_reserved";
+ }else{
+ DelItem($dbh, $biblionumber, $itemnumber);
+ return 1;
+ }
+ }
+ return $error;
+}
+
=head2 _koha_modify_item
=over 4
$error.="ERROR in _koha_modify_item $query".$dbh->errstr;
warn $error;
}
- $sth->finish();
return ($item->{'itemnumber'},$error);
}
my $sth = $dbh->prepare("SELECT * FROM items WHERE itemnumber=?");
$sth->execute($itemnum);
my $data = $sth->fetchrow_hashref();
- $sth->finish();
my $query = "INSERT INTO deleteditems SET ";
my @bind = ();
foreach my $key ( keys %$data ) {
$query =~ s/\,$//;
$sth = $dbh->prepare($query);
$sth->execute(@bind);
- $sth->finish();
# delete from items table
$sth = $dbh->prepare("DELETE FROM items WHERE itemnumber=?");
$sth->execute($itemnum);
- $sth->finish();
return undef;
}