use strict;
-require Exporter;
-
use C4::Context;
use C4::Koha;
use C4::Biblio;
use MARC::Record;
use C4::ClassSource;
use C4::Log;
-use C4::Reserves;
+use C4::Branch;
+require C4::Reserves;
use vars qw($VERSION @ISA @EXPORT);
-my $VERSION = 3.00;
-
-@ISA = qw( Exporter );
-
-# function exports
-@EXPORT = qw(
- GetItem
- AddItemFromMarc
- AddItem
- ModItemFromMarc
- ModItem
- ModDateLastSeen
- ModItemTransfer
-
- GetItemStatus
- GetItemLocation
- GetLostItems
- GetItemsForInventory
- GetItemsCount
- GetItemInfosOf
- GetItemsByBiblioitemnumber
- GetItemsInfo
- get_itemnumbers_of
-);
+BEGIN {
+ $VERSION = 3.01;
+
+ require Exporter;
+ @ISA = qw( Exporter );
+
+ # function exports
+ @EXPORT = qw(
+ GetItem
+ AddItemFromMarc
+ AddItem
+ AddItemBatchFromMarc
+ ModItemFromMarc
+ ModItem
+ ModDateLastSeen
+ ModItemTransfer
+ DelItem
+
+ CheckItemPreSave
+
+ GetItemStatus
+ GetItemLocation
+ GetLostItems
+ GetItemsForInventory
+ GetItemsCount
+ GetItemInfosOf
+ GetItemsByBiblioitemnumber
+ GetItemsInfo
+ get_itemnumbers_of
+ GetItemnumberFromBarcode
+ );
+}
=head1 NAME
=over 4
-$item = GetItem($itemnumber,$barcode);
+$item = GetItem($itemnumber,$barcode,$serial);
=back
Return item information, for a given itemnumber or barcode.
The return value is a hashref mapping item column
-names to values.
+names to values. If C<$serial> is true, include serial publication data.
=cut
sub GetItem {
- my ($itemnumber,$barcode) = @_;
+ my ($itemnumber,$barcode, $serial) = @_;
my $dbh = C4::Context->dbh;
+ my $data;
if ($itemnumber) {
my $sth = $dbh->prepare("
SELECT * FROM items
WHERE itemnumber = ?");
$sth->execute($itemnumber);
- my $data = $sth->fetchrow_hashref;
- return $data;
+ $data = $sth->fetchrow_hashref;
} else {
my $sth = $dbh->prepare("
SELECT * FROM items
WHERE barcode = ?"
);
- $sth->execute($barcode);
- my $data = $sth->fetchrow_hashref;
- return $data;
+ $sth->execute($barcode);
+ $data = $sth->fetchrow_hashref;
}
+ if ( $serial) {
+ my $ssth = $dbh->prepare("SELECT serialseq,publisheddate from serialitems left join serial on serialitems.serialid=serial.serialid where serialitems.itemnumber=?");
+ $ssth->execute($data->{'itemnumber'}) ;
+ ($data->{'serialseq'} , $data->{'publisheddate'}) = $ssth->fetchrow_array();
+ warn $data->{'serialseq'} , $data->{'publisheddate'};
+ }
+ return $data;
} # sub GetItem
=head2 AddItemFromMarc
# parse item hash from MARC
my $frameworkcode = GetFrameworkCode( $biblionumber );
my $item = &TransformMarcToKoha( $dbh, $source_item_marc, $frameworkcode );
-
return AddItem($item, $biblionumber, $dbh, $frameworkcode);
}
_set_defaults_for_add($item);
_set_derived_columns_for_add($item);
# FIXME - checks here
- my ( $itemnumber, $error ) = _koha_new_item( $dbh, $item, $item->{barcode} );
+ my ( $itemnumber, $error ) = _koha_new_item( $dbh, $item, $item->{barcode} );
$item->{'itemnumber'} = $itemnumber;
# create MARC tag representing item and add to bib
return ($item->{biblionumber}, $item->{biblioitemnumber}, $itemnumber);
}
+=head2 AddItemBatchFromMarc
+
+=over 4
+
+($itemnumber_ref, $error_ref) = AddItemBatchFromMarc($record, $biblionumber, $biblioitemnumber, $frameworkcode);
+
+=back
+
+Efficiently create item records from a MARC biblio record with
+embedded item fields. This routine is suitable for batch jobs.
+
+This API assumes that the bib record has already been
+saved to the C<biblio> and C<biblioitems> tables. It does
+not expect that C<biblioitems.marc> and C<biblioitems.marcxml>
+are populated, but it will do so via a call to ModBibiloMarc.
+
+The goal of this API is to have a similar effect to using AddBiblio
+and AddItems in succession, but without inefficient repeated
+parsing of the MARC XML bib record.
+
+This function returns an arrayref of new itemsnumbers and an arrayref of item
+errors encountered during the processing. Each entry in the errors
+list is a hashref containing the following keys:
+
+=over 2
+
+=item item_sequence
+
+Sequence number of original item tag in the MARC record.
+
+=item item_barcode
+
+Item barcode, provide to assist in the construction of
+useful error messages.
+
+=item error_condition
+
+Code representing the error condition. Can be 'duplicate_barcode',
+'invalid_homebranch', or 'invalid_holdingbranch'.
+
+=item error_information
+
+Additional information appropriate to the error condition.
+
+=back
+
+=cut
+
+sub AddItemBatchFromMarc {
+ my ($record, $biblionumber, $biblioitemnumber, $frameworkcode) = @_;
+ my $error;
+ my @itemnumbers = ();
+ my @errors = ();
+ my $dbh = C4::Context->dbh;
+
+ # loop through the item tags and start creating items
+ my @bad_item_fields = ();
+ my ($itemtag, $itemsubfield) = &GetMarcFromKohaField("items.itemnumber",'');
+ my $item_sequence_num = 0;
+ ITEMFIELD: foreach my $item_field ($record->field($itemtag)) {
+ $item_sequence_num++;
+ # we take the item field and stick it into a new
+ # MARC record -- this is required so far because (FIXME)
+ # TransformMarcToKoha requires a MARC::Record, not a MARC::Field
+ # and there is no TransformMarcFieldToKoha
+ my $temp_item_marc = MARC::Record->new();
+ $temp_item_marc->append_fields($item_field);
+
+ # add biblionumber and biblioitemnumber
+ my $item = TransformMarcToKoha( $dbh, $temp_item_marc, $frameworkcode, 'items' );
+ $item->{'biblionumber'} = $biblionumber;
+ $item->{'biblioitemnumber'} = $biblioitemnumber;
+
+ # check for duplicate barcode
+ my %item_errors = CheckItemPreSave($item);
+ if (%item_errors) {
+ push @errors, _repack_item_errors($item_sequence_num, $item, \%item_errors);
+ push @bad_item_fields, $item_field;
+ next ITEMFIELD;
+ }
+
+ _set_defaults_for_add($item);
+ _set_derived_columns_for_add($item);
+ my ( $itemnumber, $error ) = _koha_new_item( $dbh, $item, $item->{barcode} );
+ warn $error if $error;
+ push @itemnumbers, $itemnumber; # FIXME not checking error
+ $item->{'itemnumber'} = $itemnumber;
+
+ &logaction(C4::Context->userenv->{'number'},"CATALOGUING","ADD",$itemnumber,"item")
+ if C4::Context->preference("CataloguingLog");
+
+ my $new_item_marc = _marc_from_item_hash($item, $frameworkcode);
+ $item_field->replace_with($new_item_marc->field($itemtag));
+ }
+
+ # remove any MARC item fields for rejected items
+ foreach my $item_field (@bad_item_fields) {
+ $record->delete_field($item_field);
+ }
+
+ # update the MARC biblio
+ $biblionumber = ModBiblioMarc( $record, $biblionumber, $frameworkcode );
+
+ return (\@itemnumbers, \@errors);
+}
+
=head2 ModItemFromMarc
=over 4
my $dbh = @_ ? shift : C4::Context->dbh;
my $frameworkcode = @_ ? shift : GetFrameworkCode( $biblionumber );
- $item->{'itemnumber'} = $itemnumber;
+ $item->{'itemnumber'} = $itemnumber or return undef;
_set_derived_columns_for_mod($item);
_do_column_fixes_for_mod($item);
# FIXME add checks
_koha_modify_item($dbh, $item);
# update biblio MARC XML
- my $whole_item = GetItem($itemnumber);
- my $new_item_marc = _marc_from_item_hash($whole_item, $frameworkcode);
+ my $whole_item = GetItem($itemnumber) or die "FAILED GetItem($itemnumber)";
+ my $new_item_marc = _marc_from_item_hash($whole_item, $frameworkcode) or die "FAILED _marc_from_item_hash($whole_item, $frameworkcode)";
_replace_item_field_in_biblio($new_item_marc, $biblionumber, $itemnumber, $frameworkcode);
-
+ (C4::Context->userenv eq '0') and die "userenv is '0', not hashref"; # logaction line would crash anyway
+ ($new_item_marc eq '0') and die "$new_item_marc is '0', not hashref"; # logaction line would crash anyway
logaction(C4::Context->userenv->{'number'},"CATALOGUING","MODIFY",$itemnumber,$new_item_marc->as_formatted)
if C4::Context->preference("CataloguingLog");
}
ModItem({ itemlost => 0, datelastseen => $today->output("iso") }, undef, $itemnumber);
}
+=head2 DelItem
+
+=over 4
+
+DelItem($biblionumber, $itemnumber);
+
+=back
+
+Exported function (core API) for deleting an item record in Koha.
+
+=cut
+
+sub DelItem {
+ my ( $dbh, $biblionumber, $itemnumber ) = @_;
+
+ # FIXME check the item has no current issues
+
+ _koha_delete_item( $dbh, $itemnumber );
+
+ # get the MARC record
+ my $record = GetMarcBiblio($biblionumber);
+ my $frameworkcode = GetFrameworkCode($biblionumber);
+
+ # backup the record
+ my $copy2deleted = $dbh->prepare("UPDATE deleteditems SET marc=? WHERE itemnumber=?");
+ $copy2deleted->execute( $record->as_usmarc(), $itemnumber );
+
+ #search item field code
+ my ( $itemtag, $itemsubfield ) = GetMarcFromKohaField("items.itemnumber",$frameworkcode);
+ my @fields = $record->field($itemtag);
+
+ # delete the item specified
+ foreach my $field (@fields) {
+ if ( $field->subfield($itemsubfield) eq $itemnumber ) {
+ $record->delete_field($field);
+ }
+ }
+ &ModBiblioMarc( $record, $biblionumber, $frameworkcode );
+ &logaction(C4::Context->userenv->{'number'},"CATALOGUING","DELETE",$itemnumber,"item")
+ if C4::Context->preference("CataloguingLog");
+}
+
+=head2 CheckItemPreSave
+
+=over 4
+
+ my $item_ref = TransformMarcToKoha($marc, 'items');
+ # do stuff
+ my %errors = CheckItemPreSave($item_ref);
+ if (exists $errors{'duplicate_barcode'}) {
+ print "item has duplicate barcode: ", $errors{'duplicate_barcode'}, "\n";
+ } elsif (exists $errors{'invalid_homebranch'}) {
+ print "item has invalid home branch: ", $errors{'invalid_homebranch'}, "\n";
+ } elsif (exists $errors{'invalid_holdingbranch'}) {
+ print "item has invalid holding branch: ", $errors{'invalid_holdingbranch'}, "\n";
+ } else {
+ print "item is OK";
+ }
+
+=back
+
+Given a hashref containing item fields, determine if it can be
+inserted or updated in the database. Specifically, checks for
+database integrity issues, and returns a hash containing any
+of the following keys, if applicable.
+
+=over 2
+
+=item duplicate_barcode
+
+Barcode, if it duplicates one already found in the database.
+
+=item invalid_homebranch
+
+Home branch, if not defined in branches table.
+
+=item invalid_holdingbranch
+
+Holding branch, if not defined in branches table.
+
+=back
+
+This function does NOT implement any policy-related checks,
+e.g., whether current operator is allowed to save an
+item that has a given branch code.
+
+=cut
+
+sub CheckItemPreSave {
+ my $item_ref = shift;
+
+ my %errors = ();
+
+ # check for duplicate barcode
+ if (exists $item_ref->{'barcode'} and defined $item_ref->{'barcode'}) {
+ my $existing_itemnumber = GetItemnumberFromBarcode($item_ref->{'barcode'});
+ if ($existing_itemnumber) {
+ if (!exists $item_ref->{'itemnumber'} # new item
+ or $item_ref->{'itemnumber'} != $existing_itemnumber) { # existing item
+ $errors{'duplicate_barcode'} = $item_ref->{'barcode'};
+ }
+ }
+ }
+
+ # check for valid home branch
+ if (exists $item_ref->{'homebranch'} and defined $item_ref->{'homebranch'}) {
+ my $branch_name = GetBranchName($item_ref->{'homebranch'});
+ unless (defined $branch_name) {
+ # relies on fact that branches.branchname is a non-NULL column,
+ # so GetBranchName returns undef only if branch does not exist
+ $errors{'invalid_homebranch'} = $item_ref->{'homebranch'};
+ }
+ }
+
+ # check for valid holding branch
+ if (exists $item_ref->{'holdingbranch'} and defined $item_ref->{'holdingbranch'}) {
+ my $branch_name = GetBranchName($item_ref->{'holdingbranch'});
+ unless (defined $branch_name) {
+ # relies on fact that branches.branchname is a non-NULL column,
+ # so GetBranchName returns undef only if branch does not exist
+ $errors{'invalid_holdingbranch'} = $item_ref->{'holdingbranch'};
+ }
+ }
+
+ return %errors;
+
+}
+
=head1 EXPORTED SPECIAL ACCESSOR FUNCTIONS
The following functions provide various ways of
$sth->execute($biblionumber);
my $i = 0;
my @results;
- my ( $date_due, $count_reserves );
+ my ( $date_due, $count_reserves, $serial );
my $isth = $dbh->prepare(
"SELECT issues.*,borrowers.cardnumber,borrowers.surname,borrowers.firstname,borrowers.branchcode as bcode
WHERE itemnumber = ?
AND returndate IS NULL"
);
- while ( my $data = $sth->fetchrow_hashref ) {
+ 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 ) {
+ warn $data->{itemnumber};
my $datedue = '';
$isth->execute( $data->{'itemnumber'} );
if ( my $idata = $isth->fetchrow_hashref ) {
}
}
}
- if ( $datedue eq '' ) {
+ if ( $data->{'serial'}) {
+ $ssth->execute($data->{'itemnumber'}) ;
+ ($data->{'serialseq'} , $data->{'publisheddate'}) = $ssth->fetchrow_array();
+ warn $data->{'serialseq'} , $data->{'publisheddate'};
+ $serial = 1;
+ }
+ if ( $datedue eq '' ) {
my ( $restype, $reserves ) =
C4::Reserves::CheckReserves( $data->{'itemnumber'} );
if ($restype) {
}
}
$isth->finish;
-
+ $ssth->finish;
#get branch information.....
my $bsth = $dbh->prepare(
"SELECT * FROM branches WHERE branchcode = ?
$i++;
}
$sth->finish;
-
- return (@results);
+ if($serial) {
+ return( sort { $b->{'publisheddate'} cmp $a->{'publisheddate'} } @results );
+ } else {
+ return (@results);
+ }
}
=head2 get_itemnumbers_of
return \%itemnumbers_of;
}
+=head2 GetItemnumberFromBarcode
+
+=over 4
+
+$result = GetItemnumberFromBarcode($barcode);
+
+=back
+
+=cut
+
+sub GetItemnumberFromBarcode {
+ my ($barcode) = @_;
+ my $dbh = C4::Context->dbh;
+
+ my $rq =
+ $dbh->prepare("SELECT itemnumber FROM items WHERE items.barcode=?");
+ $rq->execute($barcode);
+ my ($result) = $rq->fetchrow;
+ return ($result);
+}
+
=head1 LIMITED USE FUNCTIONS
The following functions, while part of the public API,
}
# various item status fields cannot be null
- $item->{'notforloan'} = 0 unless exists $item->{'notforloan'} and defined $item->{'notforloan'};
- $item->{'damaged'} = 0 unless exists $item->{'damaged'} and defined $item->{'damaged'};
- $item->{'itemlost'} = 0 unless exists $item->{'itemlost'} and defined $item->{'itemlost'};
- $item->{'wthdrawn'} = 0 unless exists $item->{'wthdrawn'} and defined $item->{'wthdrawn'};
+ $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 '';
}
=head2 _koha_new_item
sub _koha_new_item {
my ( $dbh, $item, $barcode ) = @_;
my $error;
-
- my $query =
+ my $query =
"INSERT INTO items SET
biblionumber = ?,
biblioitemnumber = ?,
ccode = ?,
itype = ?,
materials = ?,
- uri = ?
+ uri = ?
";
my $sth = $dbh->prepare($query);
- $sth->execute(
+ $sth->execute(
$item->{'biblionumber'},
$item->{'biblioitemnumber'},
$barcode,
return ($item->{'itemnumber'},$error);
}
+=head2 _koha_delete_item
+
+=over 4
+
+_koha_delete_item( $dbh, $itemnum );
+
+=back
+
+Internal function to delete an item record from the koha tables
+
+=cut
+
+sub _koha_delete_item {
+ my ( $dbh, $itemnum ) = @_;
+
+ # save the deleted item to deleteditems table
+ 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 .= "$key = ?,";
+ push( @bind, $data->{$key} );
+ }
+ $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;
+}
+
=head2 _marc_from_item_hash
=over 4
ModBiblioMarc($completeRecord, $biblionumber, $frameworkcode);
}
+=head2 _repack_item_errors
+
+Add an error message hash generated by C<CheckItemPreSave>
+to a list of errors.
+
+=cut
+
+sub _repack_item_errors {
+ my $item_sequence_num = shift;
+ my $item_ref = shift;
+ my $error_ref = shift;
+
+ my @repacked_errors = ();
+
+ foreach my $error_code (sort keys %{ $error_ref }) {
+ my $repacked_error = {};
+ $repacked_error->{'item_sequence'} = $item_sequence_num;
+ $repacked_error->{'item_barcode'} = exists($item_ref->{'barcode'}) ? $item_ref->{'barcode'} : '';
+ $repacked_error->{'error_code'} = $error_code;
+ $repacked_error->{'error_information'} = $error_ref->{$error_code};
+ push @repacked_errors, $repacked_error;
+ }
+
+ return @repacked_errors;
+}
+
1;