Serials updates to link item record to serial table.
[koha_gimpoz] / C4 / Items.pm
index 29e4da5..f263629 100644 (file)
@@ -19,30 +19,50 @@ package C4::Items;
 
 use strict;
 
-require Exporter;
-
 use C4::Context;
+use C4::Koha;
 use C4::Biblio;
-use C4::Dates;
+use C4::Dates qw/format_date format_date_in_iso/;
 use MARC::Record;
 use C4::ClassSource;
 use C4::Log;
+use C4::Branch;
+require C4::Reserves;
 
 use vars qw($VERSION @ISA @EXPORT);
 
-my $VERSION = 3.00;
-
-@ISA = qw( Exporter );
-
-# function exports
-@EXPORT = qw(
-    AddItemFromMarc
-    AddItem
-    ModItemFromMarc
-    ModItem
-    ModDateLastSeen
-    ModItemTransfer
-);
+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
 
@@ -78,13 +98,53 @@ accurate.
 Most of the functions in C<C4::Items> were originally in
 the C<C4::Biblio> module.
 
-=head1 EXPORTED FUNCTIONS
+=head1 CORE EXPORTED FUNCTIONS
 
 The following functions are meant for use by users
 of C<C4::Items>
 
 =cut
 
+=head2 GetItem
+
+=over 4
+
+$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.  If C<$serial> is true, include serial publication data.
+
+=cut
+
+sub GetItem {
+    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);
+        $data = $sth->fetchrow_hashref;
+    } else {
+        my $sth = $dbh->prepare("
+            SELECT * FROM items 
+            WHERE barcode = ?"
+            );
+        $sth->execute($barcode);               
+        $data = $sth->fetchrow_hashref;
+    }
+    if ( $serial) {      
+    my $ssth = $dbh->prepare("SELECT serialseq,publisheddate from serial where itemnumber=?");
+        $ssth->execute($data->{'itemnumber'}) ;
+        ($data->{'serialseq'} , $data->{'publisheddate'}) = $ssth->fetchrow_array();
+    }          
+    return $data;
+}    # sub GetItem
+
 =head2 AddItemFromMarc
 
 =over 4
@@ -106,7 +166,6 @@ sub AddItemFromMarc {
     # parse item hash from MARC
     my $frameworkcode = GetFrameworkCode( $biblionumber );
     my $item = &TransformMarcToKoha( $dbh, $source_item_marc, $frameworkcode );
-
     return AddItem($item, $biblionumber, $dbh, $frameworkcode);
 }
 
@@ -144,7 +203,7 @@ sub AddItem {
     _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
@@ -157,8 +216,125 @@ sub AddItem {
     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
+
+ModItemFromMarc($item_marc, $biblionumber, $itemnumber);
+
+=back
+
+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.
+
 =cut
 
 sub ModItemFromMarc {
@@ -175,6 +351,24 @@ sub ModItemFromMarc {
 
 =head2 ModItem
 
+=over 4
+
+ModItem({ column => $newvalue }, $biblionumber, $itemnumber);
+
+=back
+
+Change one or more columns in an item record and update
+the MARC representation of the item.
+
+The first argument is a hashref mapping from item column
+names to the new values.  The second and third arguments
+are the biblionumber and itemnumber, respectively.
+
+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.
+
 =cut
 
 sub ModItem {
@@ -190,25 +384,40 @@ sub ModItem {
     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
+    # 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($dbh, $item);
 
     # update biblio MARC XML
-    my $whole_item = GetItem($itemnumber);
-    my $new_item_marc = _marc_from_item_hash($whole_item, $frameworkcode);
-    ModItemInMarc($new_item_marc, $biblionumber, $itemnumber, $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");
 }
 
 =head2 ModItemTransfer
 
+=over 4
+
+ModItemTransfer($itenumber, $frombranch, $tobranch);
+
+=back
+
+Marks an item as being transferred from one branch
+to another.
+
 =cut
 
 sub ModItemTransfer {
@@ -247,6 +456,871 @@ sub ModDateLastSeen {
     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 
+getting an item record, a set of item records, or
+lists of authorized values for certain item fields.
+
+Some of the functions in this group are candidates
+for refactoring -- for example, some of the code
+in C<GetItemsByBiblioitemnumber> and C<GetItemsInfo>
+has copy-and-paste work.
+
+=cut
+
+=head2 GetItemStatus
+
+=over 4
+
+$itemstatushash = GetItemStatus($fwkcode);
+
+=back
+
+Returns a list of valid values for the
+C<items.notforloan> field.
+
+NOTE: does B<not> return an individual item's
+status.
+
+Can be MARC dependant.
+fwkcode is optional.
+But basically could be can be loan or not
+Create a status selector with the following code
+
+=head3 in PERL SCRIPT
+
+=over 4
+
+my $itemstatushash = getitemstatus;
+my @itemstatusloop;
+foreach my $thisstatus (keys %$itemstatushash) {
+    my %row =(value => $thisstatus,
+                statusname => $itemstatushash->{$thisstatus}->{'statusname'},
+            );
+    push @itemstatusloop, \%row;
+}
+$template->param(statusloop=>\@itemstatusloop);
+
+=back
+
+=head3 in TEMPLATE
+
+=over 4
+
+<select name="statusloop">
+    <option value="">Default</option>
+<!-- TMPL_LOOP name="statusloop" -->
+    <option value="<!-- TMPL_VAR name="value" -->" <!-- TMPL_IF name="selected" -->selected<!-- /TMPL_IF -->><!-- TMPL_VAR name="statusname" --></option>
+<!-- /TMPL_LOOP -->
+</select>
+
+=back
+
+=cut
+
+sub GetItemStatus {
+
+    # returns a reference to a hash of references to status...
+    my ($fwk) = @_;
+    my %itemstatus;
+    my $dbh = C4::Context->dbh;
+    my $sth;
+    $fwk = '' unless ($fwk);
+    my ( $tag, $subfield ) =
+      GetMarcFromKohaField( "items.notforloan", $fwk );
+    if ( $tag and $subfield ) {
+        my $sth =
+          $dbh->prepare(
+            "SELECT authorised_value
+            FROM marc_subfield_structure
+            WHERE tagfield=?
+                AND tagsubfield=?
+                AND frameworkcode=?
+            "
+          );
+        $sth->execute( $tag, $subfield, $fwk );
+        if ( my ($authorisedvaluecat) = $sth->fetchrow ) {
+            my $authvalsth =
+              $dbh->prepare(
+                "SELECT authorised_value,lib
+                FROM authorised_values 
+                WHERE category=? 
+                ORDER BY lib
+                "
+              );
+            $authvalsth->execute($authorisedvaluecat);
+            while ( my ( $authorisedvalue, $lib ) = $authvalsth->fetchrow ) {
+                $itemstatus{$authorisedvalue} = $lib;
+            }
+            $authvalsth->finish;
+            return \%itemstatus;
+            exit 1;
+        }
+        else {
+
+            #No authvalue list
+            # build default
+        }
+        $sth->finish;
+    }
+
+    #No authvalue list
+    #build default
+    $itemstatus{"1"} = "Not For Loan";
+    return \%itemstatus;
+}
+
+=head2 GetItemLocation
+
+=over 4
+
+$itemlochash = GetItemLocation($fwk);
+
+=back
+
+Returns a list of valid values for the
+C<items.location> field.
+
+NOTE: does B<not> return an individual item's
+location.
+
+where fwk stands for an optional framework code.
+Create a location selector with the following code
+
+=head3 in PERL SCRIPT
+
+=over 4
+
+my $itemlochash = getitemlocation;
+my @itemlocloop;
+foreach my $thisloc (keys %$itemlochash) {
+    my $selected = 1 if $thisbranch eq $branch;
+    my %row =(locval => $thisloc,
+                selected => $selected,
+                locname => $itemlochash->{$thisloc},
+            );
+    push @itemlocloop, \%row;
+}
+$template->param(itemlocationloop => \@itemlocloop);
+
+=back
+
+=head3 in TEMPLATE
+
+=over 4
+
+<select name="location">
+    <option value="">Default</option>
+<!-- TMPL_LOOP name="itemlocationloop" -->
+    <option value="<!-- TMPL_VAR name="locval" -->" <!-- TMPL_IF name="selected" -->selected<!-- /TMPL_IF -->><!-- TMPL_VAR name="locname" --></option>
+<!-- /TMPL_LOOP -->
+</select>
+
+=back
+
+=cut
+
+sub GetItemLocation {
+
+    # returns a reference to a hash of references to location...
+    my ($fwk) = @_;
+    my %itemlocation;
+    my $dbh = C4::Context->dbh;
+    my $sth;
+    $fwk = '' unless ($fwk);
+    my ( $tag, $subfield ) =
+      GetMarcFromKohaField( "items.location", $fwk );
+    if ( $tag and $subfield ) {
+        my $sth =
+          $dbh->prepare(
+            "SELECT authorised_value
+            FROM marc_subfield_structure 
+            WHERE tagfield=? 
+                AND tagsubfield=? 
+                AND frameworkcode=?"
+          );
+        $sth->execute( $tag, $subfield, $fwk );
+        if ( my ($authorisedvaluecat) = $sth->fetchrow ) {
+            my $authvalsth =
+              $dbh->prepare(
+                "SELECT authorised_value,lib
+                FROM authorised_values
+                WHERE category=?
+                ORDER BY lib"
+              );
+            $authvalsth->execute($authorisedvaluecat);
+            while ( my ( $authorisedvalue, $lib ) = $authvalsth->fetchrow ) {
+                $itemlocation{$authorisedvalue} = $lib;
+            }
+            $authvalsth->finish;
+            return \%itemlocation;
+            exit 1;
+        }
+        else {
+
+            #No authvalue list
+            # build default
+        }
+        $sth->finish;
+    }
+
+    #No authvalue list
+    #build default
+    $itemlocation{"1"} = "Not For Loan";
+    return \%itemlocation;
+}
+
+=head2 GetLostItems
+
+=over 4
+
+$items = GetLostItems($where,$orderby);
+
+=back
+
+This function get the items lost into C<$items>.
+
+=over 2
+
+=item input:
+C<$where> is a hashref. it containts a field of the items table as key
+and the value to match as value.
+C<$orderby> is a field of the items table.
+
+=item return:
+C<$items> is a reference to an array full of hasref which keys are items' table column.
+
+=item usage in the perl script:
+
+my %where;
+$where{barcode} = 0001548;
+my $items = GetLostItems( \%where, "homebranch" );
+$template->param(itemsloop => $items);
+
+=back
+
+=cut
+
+sub GetLostItems {
+    # Getting input args.
+    my $where   = shift;
+    my $orderby = shift;
+    my $dbh     = C4::Context->dbh;
+
+    my $query   = "
+        SELECT *
+        FROM   items
+        WHERE  itemlost IS NOT NULL
+          AND  itemlost <> 0
+    ";
+    foreach my $key (keys %$where) {
+        $query .= " AND " . $key . " LIKE '%" . $where->{$key} . "%'";
+    }
+    $query .= " ORDER BY ".$orderby if defined $orderby;
+
+    my $sth = $dbh->prepare($query);
+    $sth->execute;
+    my @items;
+    while ( my $row = $sth->fetchrow_hashref ){
+        push @items, $row;
+    }
+    return \@items;
+}
+
+=head2 GetItemsForInventory
+
+=over 4
+
+$itemlist = GetItemsForInventory($minlocation,$maxlocation,$datelastseen,$offset,$size)
+
+=back
+
+Retrieve a list of title/authors/barcode/callnumber, for biblio inventory.
+
+The sub returns a list of hashes, containing itemnumber, author, title, barcode & item callnumber.
+It is ordered by callnumber,title.
+
+The 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)
+
+=cut
+
+sub GetItemsForInventory {
+    my ( $minlocation, $maxlocation,$location, $datelastseen, $branch, $offset, $size ) = @_;
+    my $dbh = C4::Context->dbh;
+    my $sth;
+    if ($datelastseen) {
+        $datelastseen=format_date_in_iso($datelastseen);  
+        my $query =
+                "SELECT itemnumber,barcode,itemcallnumber,title,author,biblio.biblionumber,datelastseen
+                 FROM items
+                   LEFT JOIN biblio ON items.biblionumber=biblio.biblionumber 
+                 WHERE itemcallnumber>= ?
+                   AND itemcallnumber <=?
+                   AND (datelastseen< ? OR datelastseen IS NULL)";
+        $query.= " AND items.location=".$dbh->quote($location) if $location;
+        $query.= " AND items.homebranch=".$dbh->quote($branch) if $branch;
+        $query .= " ORDER BY itemcallnumber,title";
+        $sth = $dbh->prepare($query);
+        $sth->execute( $minlocation, $maxlocation, $datelastseen );
+    }
+    else {
+        my $query ="
+                SELECT itemnumber,barcode,itemcallnumber,biblio.biblionumber,title,author,datelastseen
+                FROM items 
+                  LEFT JOIN biblio ON items.biblionumber=biblio.biblionumber 
+                WHERE itemcallnumber>= ?
+                  AND itemcallnumber <=?";
+        $query.= " AND items.location=".$dbh->quote($location) if $location;
+        $query.= " AND items.homebranch=".$dbh->quote($branch) if $branch;
+        $query .= " ORDER BY itemcallnumber,title";
+        $sth = $dbh->prepare($query);
+        $sth->execute( $minlocation, $maxlocation );
+    }
+    my @results;
+    while ( my $row = $sth->fetchrow_hashref ) {
+        $offset-- if ($offset);
+        $row->{datelastseen}=format_date($row->{datelastseen});
+        if ( ( !$offset ) && $size ) {
+            push @results, $row;
+            $size--;
+        }
+    }
+    return \@results;
+}
+
+=head2 GetItemsCount
+
+=over 4
+$count = &GetItemsCount( $biblionumber);
+
+=back
+
+This function return count of item with $biblionumber
+
+=cut
+
+sub GetItemsCount {
+    my ( $biblionumber ) = @_;
+    my $dbh = C4::Context->dbh;
+    my $query = "SELECT count(*)
+          FROM  items 
+          WHERE biblionumber=?";
+    my $sth = $dbh->prepare($query);
+    $sth->execute($biblionumber);
+    my $count = $sth->fetchrow;  
+    $sth->finish;
+    return ($count);
+}
+
+=head2 GetItemInfosOf
+
+=over 4
+
+GetItemInfosOf(@itemnumbers);
+
+=back
+
+=cut
+
+sub GetItemInfosOf {
+    my @itemnumbers = @_;
+
+    my $query = '
+        SELECT *
+        FROM items
+        WHERE itemnumber IN (' . join( ',', @itemnumbers ) . ')
+    ';
+    return get_infos_of( $query, 'itemnumber' );
+}
+
+=head2 GetItemsByBiblioitemnumber
+
+=over 4
+
+GetItemsByBiblioitemnumber($biblioitemnumber);
+
+=back
+
+Returns an arrayref of hashrefs suitable for use in a TMPL_LOOP
+Called by C<C4::XISBN>
+
+=cut
+
+sub GetItemsByBiblioitemnumber {
+    my ( $bibitem ) = @_;
+    my $dbh = C4::Context->dbh;
+    my $sth = $dbh->prepare("SELECT * FROM items WHERE items.biblioitemnumber = ?") || die $dbh->errstr;
+    # Get all items attached to a biblioitem
+    my $i = 0;
+    my @results; 
+    $sth->execute($bibitem) || die $sth->errstr;
+    while ( my $data = $sth->fetchrow_hashref ) {  
+        # Foreach item, get circulation information
+        my $sth2 = $dbh->prepare( "SELECT * FROM issues,borrowers
+                                   WHERE itemnumber = ?
+                                   AND returndate is NULL
+                                   AND issues.borrowernumber = borrowers.borrowernumber"
+        );
+        $sth2->execute( $data->{'itemnumber'} );
+        if ( my $data2 = $sth2->fetchrow_hashref ) {
+            # if item is out, set the due date and who it is out too
+            $data->{'date_due'}   = $data2->{'date_due'};
+            $data->{'cardnumber'} = $data2->{'cardnumber'};
+            $data->{'borrowernumber'}   = $data2->{'borrowernumber'};
+        }
+        else {
+            # 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 issues, borrowers WHERE itemnumber = ?
+                      AND issues.borrowernumber = borrowers.borrowernumber
+                      AND returndate is not NULL
+                      ORDER BY returndate desc,timestamp desc LIMIT 3";
+        $sth2 = $dbh->prepare($query2) || die $dbh->errstr;
+        $sth2->execute( $data->{'itemnumber'} ) || die $sth2->errstr;
+        my $i2 = 0;
+        while ( my $data2 = $sth2->fetchrow_hashref ) {
+            $data->{"timestamp$i2"} = $data2->{'timestamp'};
+            $data->{"card$i2"}      = $data2->{'cardnumber'};
+            $data->{"borrower$i2"}  = $data2->{'borrowernumber'};
+            $i2++;
+        }
+        $sth2->finish;
+        push(@results,$data);
+    } 
+    $sth->finish;
+    return (\@results); 
+}
+
+=head2 GetItemsInfo
+
+=over 4
+
+@results = GetItemsInfo($biblionumber, $type);
+
+=back
+
+Returns information about books with the given biblionumber.
+
+C<$type> may be either C<intra> or anything else. If it is not set to
+C<intra>, then the search will exclude lost, very overdue, and
+withdrawn items.
+
+C<GetItemsInfo> returns a list of references-to-hash. Each element
+contains a number of keys. Most of them are table items from the
+C<biblio>, C<biblioitems>, C<items>, and C<itemtypes> tables in the
+Koha database. Other keys include:
+
+=over 2
+
+=item C<$data-E<gt>{branchname}>
+
+The name (not the code) of the branch to which the book belongs.
+
+=item C<$data-E<gt>{datelastseen}>
+
+This is simply C<items.datelastseen>, except that while the date is
+stored in YYYY-MM-DD format in the database, here it is converted to
+DD/MM/YYYY format. A NULL date is returned as C<//>.
+
+=item C<$data-E<gt>{datedue}>
+
+=item C<$data-E<gt>{class}>
+
+This is the concatenation of C<biblioitems.classification>, the book's
+Dewey code, and C<biblioitems.subclass>.
+
+=item C<$data-E<gt>{ocount}>
+
+I think this is the number of copies of the book available.
+
+=item C<$data-E<gt>{order}>
+
+If this is set, it is set to C<One Order>.
+
+=back
+
+=cut
+
+sub GetItemsInfo {
+    my ( $biblionumber, $type ) = @_;
+    my $dbh   = C4::Context->dbh;
+    my $query = "SELECT *,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" ;
+    my $sth = $dbh->prepare($query);
+    $sth->execute($biblionumber);
+    my $i = 0;
+    my @results;
+    my ( $date_due, $count_reserves, $serial );
+
+    my $isth    = $dbh->prepare(
+        "SELECT issues.*,borrowers.cardnumber,borrowers.surname,borrowers.firstname,borrowers.branchcode as bcode
+        FROM   issues LEFT JOIN borrowers ON issues.borrowernumber=borrowers.borrowernumber
+        WHERE  itemnumber = ?
+            AND returndate IS NULL"
+       );
+       my $ssth = $dbh->prepare("SELECT serialseq,publisheddate from serial where itemnumber=?");
+       while ( my $data = $sth->fetchrow_hashref ) {
+        my $datedue = '';
+        $isth->execute( $data->{'itemnumber'} );
+        if ( my $idata = $isth->fetchrow_hashref ) {
+            $data->{borrowernumber} = $idata->{borrowernumber};
+            $data->{cardnumber}     = $idata->{cardnumber};
+            $data->{surname}     = $idata->{surname};
+            $data->{firstname}     = $idata->{firstname};
+            $datedue                = $idata->{'date_due'};
+        if (C4::Context->preference("IndependantBranches")){
+        my $userenv = C4::Context->userenv;
+        if ( ($userenv) && ( $userenv->{flags} != 1 ) ) { 
+            $data->{'NOTSAMEBRANCH'} = 1 if ($idata->{'bcode'} ne $userenv->{branch});
+        }
+        }
+        }
+               if ( $data->{'serial'}) {       
+                       $ssth->execute($data->{'itemnumber'}) ;
+                       ($data->{'serialseq'} , $data->{'publisheddate'}) = $ssth->fetchrow_array();
+                       $serial = 1;
+        }
+               if ( $datedue eq '' ) {
+            my ( $restype, $reserves ) =
+              C4::Reserves::CheckReserves( $data->{'itemnumber'} );
+            if ($restype) {
+                $count_reserves = $restype;
+            }
+        }
+        $isth->finish;
+        $ssth->finish;
+        #get branch information.....
+        my $bsth = $dbh->prepare(
+            "SELECT * FROM branches WHERE branchcode = ?
+        "
+        );
+        $bsth->execute( $data->{'holdingbranch'} );
+        if ( my $bdata = $bsth->fetchrow_hashref ) {
+            $data->{'branchname'} = $bdata->{'branchname'};
+        }
+        $data->{'datedue'}        = $datedue;
+        $data->{'count_reserves'} = $count_reserves;
+
+        # get notforloan complete status if applicable
+        my $sthnflstatus = $dbh->prepare(
+            'SELECT authorised_value
+            FROM   marc_subfield_structure
+            WHERE  kohafield="items.notforloan"
+        '
+        );
+
+        $sthnflstatus->execute;
+        my ($authorised_valuecode) = $sthnflstatus->fetchrow;
+        if ($authorised_valuecode) {
+            $sthnflstatus = $dbh->prepare(
+                "SELECT lib FROM authorised_values
+                 WHERE  category=?
+                 AND authorised_value=?"
+            );
+            $sthnflstatus->execute( $authorised_valuecode,
+                $data->{itemnotforloan} );
+            my ($lib) = $sthnflstatus->fetchrow;
+            $data->{notforloan} = $lib;
+        }
+
+        # my stack procedures
+        my $stackstatus = $dbh->prepare(
+            'SELECT authorised_value
+             FROM   marc_subfield_structure
+             WHERE  kohafield="items.stack"
+        '
+        );
+        $stackstatus->execute;
+
+        ($authorised_valuecode) = $stackstatus->fetchrow;
+        if ($authorised_valuecode) {
+            $stackstatus = $dbh->prepare(
+                "SELECT lib
+                 FROM   authorised_values
+                 WHERE  category=?
+                 AND    authorised_value=?
+            "
+            );
+            $stackstatus->execute( $authorised_valuecode, $data->{stack} );
+            my ($lib) = $stackstatus->fetchrow;
+            $data->{stack} = $lib;
+        }
+        # Find the last 3 people who borrowed this item.
+        my $sth2 = $dbh->prepare("SELECT * FROM issues,borrowers
+                                    WHERE itemnumber = ?
+                                    AND issues.borrowernumber = borrowers.borrowernumber
+                                    AND returndate IS NOT NULL LIMIT 3");
+        $sth2->execute($data->{'itemnumber'});
+        my $ii = 0;
+        while (my $data2 = $sth2->fetchrow_hashref()) {
+            $data->{"timestamp$ii"} = $data2->{'timestamp'} if $data2->{'timestamp'};
+            $data->{"card$ii"}      = $data2->{'cardnumber'} if $data2->{'cardnumber'};
+            $data->{"borrower$ii"}  = $data2->{'borrowernumber'} if $data2->{'borrowernumber'};
+            $ii++;
+        }
+
+        $results[$i] = $data;
+        $i++;
+    }
+    $sth->finish;
+       if($serial) {
+               return( sort { $b->{'publisheddate'} cmp $a->{'publisheddate'} } @results );
+       } else {
+       return (@results);
+       }
+}
+
+=head2 get_itemnumbers_of
+
+=over 4
+
+my @itemnumbers_of = get_itemnumbers_of(@biblionumbers);
+
+=back
+
+Given a list of biblionumbers, return the list of corresponding itemnumbers
+for each biblionumber.
+
+Return a reference on a hash where keys are biblionumbers and values are
+references on array of itemnumbers.
+
+=cut
+
+sub get_itemnumbers_of {
+    my @biblionumbers = @_;
+
+    my $dbh = C4::Context->dbh;
+
+    my $query = '
+        SELECT itemnumber,
+            biblionumber
+        FROM items
+        WHERE biblionumber IN (?' . ( ',?' x scalar @biblionumbers - 1 ) . ')
+    ';
+    my $sth = $dbh->prepare($query);
+    $sth->execute(@biblionumbers);
+
+    my %itemnumbers_of;
+
+    while ( my ( $itemnumber, $biblionumber ) = $sth->fetchrow_array ) {
+        push @{ $itemnumbers_of{$biblionumber} }, $itemnumber;
+    }
+
+    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,
+are not exported.  This is generally because they are
+meant to be used by only one script for a specific
+purpose, and should not be used in any other context
+without careful thought.
+
+=cut
+
+=head2 GetMarcItem
+
+=over 4
+
+my $item_marc = GetMarcItem($biblionumber, $itemnumber);
+
+=back
+
+Returns MARC::Record of the item passed in parameter.
+This function is meant for use only in C<cataloguing/additem.pl>,
+where it is needed to support that script's MARC-like
+editor.
+
+=cut
+
+sub GetMarcItem {
+    my ( $biblionumber, $itemnumber ) = @_;
+
+    # GetMarcItem has been revised so that it does the following:
+    #  1. Gets the item information from the items table.
+    #  2. Converts it to a MARC field for storage in the bib record.
+    #
+    # The previous behavior was:
+    #  1. Get the bib record.
+    #  2. Return the MARC tag corresponding to the item record.
+    #
+    # The difference is that one treats the items row as authoritative,
+    # while the other treats the MARC representation as authoritative
+    # under certain circumstances.
+
+    my $itemrecord = GetItem($itemnumber);
+
+    # 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 } };
+
+    my $itemmarc = TransformKohaToMarc($mungeditem);
+    return $itemmarc;
+
+}
+
 =head1 PRIVATE FUNCTIONS AND VARIABLES
 
 The following functions are not meant to be called
@@ -491,14 +1565,12 @@ sub _set_defaults_for_add {
     }
 
     # 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 _set_calculated_values
-
 =head2 _koha_new_item
 
 =over 4
@@ -507,13 +1579,15 @@ my ($itemnumber,$error) = _koha_new_item( $dbh, $item, $barcode );
 
 =back
 
+Perform the actual insert into the C<items> table.
+
 =cut
 
 sub _koha_new_item {
     my ( $dbh, $item, $barcode ) = @_;
     my $error;
 
-    my $query = 
+    my $query =
            "INSERT INTO items SET
             biblionumber        = ?,
             biblioitemnumber    = ?,
@@ -546,10 +1620,10 @@ sub _koha_new_item {
             ccode               = ?,
             itype               = ?,
             materials           = ?,
-            uri                 = ?
+            uri                 = ?,
           ";
     my $sth = $dbh->prepare($query);
-    $sth->execute(
+   $sth->execute(
             $item->{'biblionumber'},
             $item->{'biblioitemnumber'},
             $barcode,
@@ -597,6 +1671,9 @@ my ($itemnumber,$error) =_koha_modify_item( $dbh, $item, $op );
 
 =back
 
+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 {
@@ -622,6 +1699,44 @@ sub _koha_modify_item {
     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
@@ -642,7 +1757,7 @@ sub _marc_from_item_hash {
    
     # Tack on 'items.' prefix to column names so lookup from MARC frameworks will work
     # Also, don't emit a subfield if the underlying field is blank.
-    my $mungeditem = { map {  $item->{$_} ne '' ? 
+    my $mungeditem = { map {  (defined($item->{$_}) and $item->{$_} ne '') ? 
                                 (/^items\./ ? ($_ => $item->{$_}) : ("items.$_" => $item->{$_})) 
                                 : ()  } keys %{ $item } }; 
 
@@ -664,7 +1779,7 @@ sub _marc_from_item_hash {
 
 =over 4
 
-_add_item_field_to_biblio($record, $biblionumber, $frameworkcode);
+_add_item_field_to_biblio($item_marc, $biblionumber, $frameworkcode);
 
 =back
 
@@ -687,4 +1802,74 @@ sub _add_item_field_to_biblio {
 
     ModBiblioMarc($biblio_marc, $biblionumber, $frameworkcode);
 }
+
+=head2 _replace_item_field_in_biblio
+
+=over
+
+&_replace_item_field_in_biblio($item_marc, $biblionumber, $itemnumber, $frameworkcode)
+
+=back
+
+Given a MARC::Record C<$item_marc> containing one tag with the MARC 
+representation of the item, examine the biblio MARC
+for the corresponding tag for that item and 
+replace it with the tag from C<$item_marc>.
+
+=cut
+
+sub _replace_item_field_in_biblio {
+    my ($ItemRecord, $biblionumber, $itemnumber, $frameworkcode) = @_;
+    my $dbh = C4::Context->dbh;
+    
+    # get complete MARC record & replace the item field by the new one
+    my $completeRecord = GetMarcBiblio($biblionumber);
+    my ($itemtag,$itemsubfield) = GetMarcFromKohaField("items.itemnumber",$frameworkcode);
+    my $itemField = $ItemRecord->field($itemtag);
+    my @items = $completeRecord->field($itemtag);
+    my $found = 0;
+    foreach (@items) {
+        if ($_->subfield($itemsubfield) eq $itemnumber) {
+            $_->replace_with($itemField);
+            $found = 1;
+        }
+    }
+  
+    unless ($found) { 
+        # If we haven't found the matching field,
+        # just add it.  However, this means that
+        # there is likely a bug.
+        $completeRecord->append_fields($itemField);
+    }
+
+    # save the record
+    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;