start of BIB change -- introduce C4::Items
authorGalen Charlton <galen.charlton@liblime.com>
Thu, 3 Jan 2008 18:36:15 +0000 (12:36 -0600)
committerJoshua Ferraro <jmf@liblime.com>
Thu, 3 Jan 2008 22:23:12 +0000 (16:23 -0600)
Introduced C4::Items module to separate items API
from biblio API.  Details on changes will be
put in later commit messages.

Signed-off-by: Chris Cormack <crc@liblime.com>
Signed-off-by: Joshua Ferraro <jmf@liblime.com>
14 files changed:
C4/Biblio.pm
C4/Circulation.pm
C4/ImportBatch.pm
C4/Items.pm [new file with mode: 0644]
C4/Serials.pm
acqui/finishreceive.pl
catalogue/updateitem.pl
cataloguing/additem.pl
circ/returns.pl
circ/transferstodo.pl
circ/waitingreserves.pl
reports/inventory.pl
serials/serials-edit.pl
tools/inventory.pl

index 35cb1b3..6edc0ee 100755 (executable)
@@ -41,7 +41,7 @@ use vars qw($VERSION @ISA @EXPORT);
 # EXPORTED FUNCTIONS.
 
 # to add biblios or items
-push @EXPORT, qw( &AddBiblio &AddItem &AddBiblioAndItems );
+push @EXPORT, qw( &AddBiblio &AddBiblioAndItems );
 
 # to get something
 push @EXPORT, qw(
@@ -86,13 +86,9 @@ push @EXPORT, qw(
 # To modify something
 push @EXPORT, qw(
   &ModBiblio
-  &ModItem
-  &ModItemTransfer
   &ModBiblioframework
   &ModZebra
   &ModItemInMarc
-  &ModItemInMarconefield
-  &ModDateLastSeen
 );
 
 # To delete something
@@ -107,7 +103,6 @@ push @EXPORT, qw(
 # but don't use them unless you're a core developer ;-)
 push @EXPORT, qw(
   &ModBiblioMarc
-  &AddItemInMarc
 );
 
 # Others functions
@@ -397,75 +392,6 @@ sub _repack_item_errors {
     return @repacked_errors;
 }
 
-=head2 AddItem
-
-=over 2
-
-    $biblionumber = AddItem( $record, $biblionumber)
-    Exported function (core API) for adding a new item to Koha
-
-=back
-
-=cut
-
-sub AddItem {
-    my ( $record, $biblionumber ) = @_;
-    my $dbh = C4::Context->dbh;
-    # add item in old-DB
-    my $frameworkcode = GetFrameworkCode( $biblionumber );
-    my $item = &TransformMarcToKoha( $dbh, $record, $frameworkcode );
-
-    # needs old biblionumber and biblioitemnumber
-    $item->{'biblionumber'} = $biblionumber;
-    my $sth =
-      $dbh->prepare(
-        "SELECT biblioitemnumber,itemtype FROM biblioitems WHERE biblionumber=?"
-      );
-    $sth->execute( $item->{'biblionumber'} );
-    my $itemtype;
-    ( $item->{'biblioitemnumber'}, $itemtype ) = $sth->fetchrow;
-    $sth =
-      $dbh->prepare(
-        "SELECT notforloan FROM itemtypes WHERE itemtype=?");
-    $sth->execute( C4::Context->preference('item-level_itypes') ? $item->{'itype'} : $itemtype );
-    my $notforloan = $sth->fetchrow;
-    ##Change the notforloan field if $notforloan found
-    if ( $notforloan > 0 ) {
-        $item->{'notforloan'} = $notforloan;
-        &MARCitemchange( $record, "items.notforloan", $notforloan );
-    }
-    if ( !$item->{'dateaccessioned'} || $item->{'dateaccessioned'} eq '' ) {
-
-        # find today's date
-        my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst ) =
-          localtime(time);
-        $year += 1900;
-        $mon  += 1;
-        my $date =
-          "$year-" . sprintf( "%0.2d", $mon ) . "-" . sprintf( "%0.2d", $mday );
-        $item->{'dateaccessioned'} = $date;
-        &MARCitemchange( $record, "items.dateaccessioned", $date );
-    }
-    my ( $itemnumber, $error ) = &_koha_new_items( $dbh, $item, $item->{barcode} );
-    # add itemnumber to MARC::Record before adding the item.
-    $sth = $dbh->prepare(
-"SELECT tagfield,tagsubfield 
-FROM marc_subfield_structure
-WHERE frameworkcode=? 
-    AND kohafield=?"
-      );
-    &TransformKohaToMarcOneField( $sth, $record, "items.itemnumber", $itemnumber,
-        $frameworkcode );
-
-    # add the item
-    &AddItemInMarc( $record, $item->{'biblionumber'},$frameworkcode );
-   
-    &logaction(C4::Context->userenv->{'number'},"CATALOGUING","ADD",$itemnumber,"item") 
-        if C4::Context->preference("CataloguingLog");
-    
-    return ($item->{biblionumber}, $item->{biblioitemnumber},$itemnumber);
-}
-
 =head2 ModBiblio
 
     ModBiblio( $record,$biblionumber,$frameworkcode);
@@ -527,76 +453,6 @@ sub ModBiblio {
     return 1;
 }
 
-=head2 ModItem
-
-=over 2
-
-Exported function (core API) for modifying an item in Koha.
-
-=back
-
-=cut
-
-sub ModItem {
-    my ( $record, $biblionumber, $itemnumber, $delete, $new_item_hashref )
-      = @_;
-    
-    #logging
-    &logaction(C4::Context->userenv->{'number'},"CATALOGUING","MODIFY",$itemnumber,$record->as_formatted) 
-        if C4::Context->preference("CataloguingLog");
-      
-    my $dbh = C4::Context->dbh;
-    
-    # if we have a MARC record, we're coming from cataloging and so
-    # we do the whole routine: update the MARC and zebra, then update the koha
-    # tables
-    if ($record) {
-        my $frameworkcode = GetFrameworkCode( $biblionumber );
-        ModItemInMarc( $record, $biblionumber, $itemnumber, $frameworkcode );
-        my $olditem       = TransformMarcToKoha( $dbh, $record, $frameworkcode,'items');
-        $olditem->{'biblionumber'} = $biblionumber;
-        my $sth =  $dbh->prepare("select biblioitemnumber from biblioitems where biblionumber=?");
-        $sth->execute($biblionumber);
-        my ($biblioitemnumber) = $sth->fetchrow;
-        $sth->finish(); 
-        $olditem->{'biblioitemnumber'} = $biblioitemnumber;
-        _koha_modify_item( $dbh, $olditem );
-        return $biblionumber;
-    }
-
-    # otherwise, we're just looking to modify something quickly
-    # (like a status) so we just update the koha tables
-    elsif ($new_item_hashref) {
-        _koha_modify_item( $dbh, $new_item_hashref );
-    }
-}
-
-sub ModItemTransfer {
-    my ( $itemnumber, $frombranch, $tobranch ) = @_;
-    
-    my $dbh = C4::Context->dbh;
-    
-    #new entry in branchtransfers....
-    my $sth = $dbh->prepare(
-        "INSERT INTO branchtransfers (itemnumber, frombranch, datesent, tobranch)
-        VALUES (?, ?, NOW(), ?)");
-    $sth->execute($itemnumber, $frombranch, $tobranch);
-    #update holdingbranch in items .....
-     $sth= $dbh->prepare(
-          "UPDATE items SET holdingbranch = ? WHERE items.itemnumber = ?");
-    $sth->execute($tobranch,$itemnumber);
-    &ModDateLastSeen($itemnumber);
-    $sth = $dbh->prepare(
-        "SELECT biblionumber FROM items WHERE itemnumber=?"
-      );
-    $sth->execute($itemnumber);
-    while ( my ( $biblionumber ) = $sth->fetchrow ) {
-        &ModItemInMarconefield( $biblionumber, $itemnumber,
-            'items.holdingbranch', $tobranch );
-    }
-    return;
-}
-
 =head2 ModBiblioframework
 
     ModBiblioframework($biblionumber,$frameworkcode);
@@ -614,46 +470,6 @@ sub ModBiblioframework {
     return 1;
 }
 
-=head2 ModItemInMarconefield
-
-=over
-
-modify only 1 field in a MARC item (mainly used for holdingbranch, but could also be used for status modif - moving a book to "lost" on a long overdu for example)
-&ModItemInMarconefield( $biblionumber, $itemnumber, $itemfield, $newvalue )
-
-=back
-
-=cut
-
-sub ModItemInMarconefield {
-    my ( $biblionumber, $itemnumber, $itemfield, $newvalue ) = @_;
-    my $dbh = C4::Context->dbh;
-    if ( !defined $newvalue ) {
-        $newvalue = "";
-    }
-
-    my $record = GetMarcItem( $biblionumber, $itemnumber );
-    my ($tagfield, $tagsubfield) = GetMarcFromKohaField( $itemfield,'');
-    # FIXME - the condition is done this way because GetMarcFromKohaField
-    # returns (0, 0) if it can't field a MARC tag for the kohafield.  However,
-    # some fields like items.wthdrawn are mapped to subfield $0, making the
-    # customary test of "if ($tagfield && $tagsubfield)" incorrect.
-    # GetMarcFromKohaField should probably be returning (undef, undef), making
-    # the correct test "if (defined $tagfield && defined $tagsubfield)", but
-    # this would be a large change and consequently deferred for after 3.0.
-    if (not(int($tagfield) == 0 && int($tagsubfield) == 0)) { 
-        my $tag = $record->field($tagfield);
-        if ($tag) {
-#             my $tagsubs = $record->field($tagfield)->subfield($tagsubfield);
-            $tag->update( $tagsubfield => $newvalue );
-            $record->delete_field($tag);
-            $record->insert_fields_ordered($tag);
-            my $frameworkcode = GetFrameworkCode( $biblionumber );
-            &ModItemInMarc( $record, $biblionumber, $itemnumber, $frameworkcode );
-        }
-    }
-}
-
 =head2 ModItemInMarc
 
 =over
@@ -686,24 +502,6 @@ sub ModItemInMarc {
     ModZebra($biblionumber,"specialUpdate","biblioserver",$completeRecord);
 }
 
-=head2 ModDateLastSeen
-
-&ModDateLastSeen($itemnum)
-Mark item as seen. Is called when an item is issued, returned or manually marked during inventory/stocktaking
-C<$itemnum> is the item number
-
-=cut
-
-sub ModDateLastSeen {
-    my ($itemnum) = @_;
-    my $dbh       = C4::Context->dbh;
-    my $sth       =
-      $dbh->prepare(
-          "UPDATE items SET itemlost=0,datelastseen  = NOW() WHERE items.itemnumber = ?"
-      );
-    $sth->execute($itemnum);
-    return;
-}
 =head2 DelBiblio
 
 =over
@@ -4212,54 +4010,6 @@ sub _koha_new_items {
     return ( $itemnumber, $error );
 }
 
-=head2 _koha_modify_item
-
-=over 4
-
-my ($itemnumber,$error) =_koha_modify_item( $dbh, $item, $op );
-
-=back
-
-=cut
-
-sub _koha_modify_item {
-    my ( $dbh, $item ) = @_;
-    my $error;
-
-    # calculate items.cn_sort
-    if($item->{'itemcallnumber'}) {
-        # This works, even when user is setting the call number blank (in which case
-        # how would we get here to calculate new (blank) of items.cn_sort?).
-        # 
-        # Why?  Because at present the only way to update itemcallnumber is via
-        # additem.pl; since it uses a MARC data-entry form, TransformMarcToKoha
-        # already has created $item->{'items.cn_sort'} and set it to undef because the 
-        # subfield for items.cn_sort in the framework is specified as ignored, meaning
-        # that it is not supplied or passed to the form.  Thus, if the user has
-        # blanked itemcallnumber, there is already a undef value for $item->{'items.cn_sort'}.
-        #
-        # This is subtle; it is also fragile.
-        $item->{'items.cn_sort'} = GetClassSort($item->{'items.cn_source'}, $item->{'itemcallnumber'}, "");
-    }
-    my $query = "UPDATE items SET ";
-    my @bind;
-    for my $key ( keys %$item ) {
-        $query.="$key=?,";
-        push @bind, $item->{$key};
-    }
-    $query =~ s/,$//;
-    $query .= " WHERE itemnumber=?";
-    push @bind, $item->{'itemnumber'};
-    my $sth = $dbh->prepare($query);
-    $sth->execute(@bind);
-    if ( $dbh->errstr ) {
-        $error.="ERROR in _koha_modify_item $query".$dbh->errstr;
-        warn $error;
-    }
-    $sth->finish();
-    return ($item->{'itemnumber'},$error);
-}
-
 =head2 _koha_delete_biblio
 
 =over 4
@@ -4456,38 +4206,6 @@ sub ModBiblioMarc {
     return $biblionumber;
 }
 
-=head2 AddItemInMarc
-
-=over 4
-
-$newbiblionumber = AddItemInMarc( $record, $biblionumber, $frameworkcode );
-
-Add an item in a MARC record and save the MARC record
-
-Function exported, but should NOT be used, unless you really know what you're doing
-
-=back
-
-=cut
-
-sub AddItemInMarc {
-
-    # pass the MARC::Record to this function, and it will create the records in the marc tables
-    my ( $record, $biblionumber, $frameworkcode ) = @_;
-    my $newrec = &GetMarcBiblio($biblionumber);
-
-    # create it
-    my @fields = $record->fields();
-    foreach my $field (@fields) {
-        $newrec->append_fields($field);
-    }
-
-    # FIXME: should we be making sure the biblionumbers are the same?
-    my $newbiblionumber =
-      &ModBiblioMarc( $newrec, $biblionumber, $frameworkcode );
-    return $newbiblionumber;
-}
-
 =head2 z3950_extended_services
 
 z3950_extended_services($serviceType,$serviceOptions,$record);
index 15eb7d0..ca3c34f 100644 (file)
@@ -25,6 +25,7 @@ use C4::Stats;
 use C4::Reserves;
 use C4::Koha;
 use C4::Biblio;
+use C4::Items;
 use C4::Members;
 use C4::Dates;
 use Date::Calc qw(
index 06d5d07..764b8bd 100644 (file)
@@ -21,6 +21,7 @@ use strict;
 use C4::Context;
 use C4::Koha;
 use C4::Biblio;
+use C4::Items;
 require Exporter;
 
 
@@ -528,7 +529,7 @@ sub BatchCommitItems {
     $sth->execute();
     while (my $row = $sth->fetchrow_hashref()) {
         my $item_marc = MARC::Record->new_from_xml($row->{'marcxml'}, 'UTF-8', $row->{'encoding'});
-        # FIXME - duplicate barcode check needs to become part of AddItem()
+        # FIXME - duplicate barcode check needs to become part of AddItemFromMarc()
         my $item = TransformMarcToKoha($dbh, $item_marc);
         my $duplicate_barcode = exists($item->{'barcode'}) && GetItemnumberFromBarcode($item->{'barcode'});
         if ($duplicate_barcode) {
@@ -539,7 +540,7 @@ sub BatchCommitItems {
             $updsth->execute();
             $num_items_errored++;
         } else {
-            my ($item_biblionumber, $biblioitemnumber, $itemnumber) = AddItem($item_marc, $biblionumber);
+            my ($item_biblionumber, $biblioitemnumber, $itemnumber) = AddItemFromMarc($item_marc, $biblionumber);
             my $updsth = $dbh->prepare("UPDATE import_items SET status = ?, itemnumber = ? WHERE import_items_id = ?");
             $updsth->bind_param(1, 'imported');
             $updsth->bind_param(2, $itemnumber);
diff --git a/C4/Items.pm b/C4/Items.pm
new file mode 100644 (file)
index 0000000..6603b18
--- /dev/null
@@ -0,0 +1,639 @@
+package C4::Items;
+
+# Copyright 2007 LibLime, Inc.
+#
+# This file is part of Koha.
+#
+# Koha is free software; you can redistribute it and/or modify it under the
+# terms of the GNU General Public License as published by the Free Software
+# Foundation; either version 2 of the License, or (at your option) any later
+# version.
+#
+# Koha is distributed in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along with
+# Koha; if not, write to the Free Software Foundation, Inc., 59 Temple Place,
+# Suite 330, Boston, MA  02111-1307 USA
+
+use strict;
+
+require Exporter;
+
+use C4::Context;
+use C4::Biblio;
+use C4::Dates;
+use MARC::Record;
+use C4::ClassSource;
+use C4::Log;
+
+use vars qw($VERSION @ISA @EXPORT);
+
+my $VERSION = 3.00;
+
+@ISA = qw( Exporter );
+
+# function exports
+@EXPORT = qw(
+    AddItemFromMarc
+    AddItem
+    ModItemFromMarc
+    ModItem
+    ModDateLastSeen
+    ModItemTransfer
+);
+
+=head1 NAME
+
+C4::Items - item management functions
+
+=head1 DESCRIPTION
+
+This module contains an API for manipulating item 
+records in Koha, and is used by cataloguing, circulation,
+acquisitions, and serials management.
+
+A Koha item record is stored in two places: the
+items table and embedded in a MARC tag in the XML
+version of the associated bib record in C<biblioitems.marcxml>.
+This is done to allow the item information to be readily
+indexed (e.g., by Zebra), but means that each item
+modification transaction must keep the items table
+and the MARC XML in sync at all times.
+
+Consequently, all code that creates, modifies, or deletes
+item records B<must> use an appropriate function from 
+C<C4::Items>.  If no existing function is suitable, it is
+better to add one to C<C4::Items> than to use add
+one-off SQL statements to add or modify items.
+
+The items table will be considered authoritative.  In other
+words, if there is ever a discrepancy between the items
+table and the MARC XML, the items table should be considered
+accurate.
+
+=head1 HISTORICAL NOTE
+
+Most of the functions in C<C4::Items> were originally in
+the C<C4::Biblio> module.
+
+=head1 EXPORTED FUNCTIONS
+
+The following functions are meant for use by users
+of C<C4::Items>
+
+=cut
+
+=head2 AddItemFromMarc
+
+=over 4
+
+my ($biblionumber, $biblioitemnumber, $itemnumber) 
+    = AddItemFromMarc($source_item_marc, $biblionumber);
+
+=back
+
+Given a MARC::Record object containing an embedded item
+record and a biblionumber, create a new item record.
+
+=cut
+
+sub AddItemFromMarc {
+    my ( $source_item_marc, $biblionumber ) = @_;
+    my $dbh = C4::Context->dbh;
+
+    # parse item hash from MARC
+    my $frameworkcode = GetFrameworkCode( $biblionumber );
+    my $item = &TransformMarcToKoha( $dbh, $source_item_marc, $frameworkcode );
+
+    return AddItem($item, $biblionumber, $dbh, $frameworkcode);
+}
+
+=head2 AddItem
+
+=over 4
+
+my ($biblionumber, $biblioitemnumber, $itemnumber) 
+    = AddItem($item, $biblionumber[, $dbh, $frameworkcode]);
+
+=back
+
+Given a hash containing item column names as keys,
+create a new Koha item record.
+
+The 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.
+
+=cut
+
+sub AddItem {
+    my $item = shift;
+    my $biblionumber = shift;
+
+    my $dbh           = @_ ? shift : C4::Context->dbh;
+    my $frameworkcode = @_ ? shift : GetFrameworkCode( $biblionumber );
+
+    # 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);
+    # FIXME - checks here
+    my ( $itemnumber, $error ) = _koha_new_item( $dbh, $item, $item->{barcode} );
+    $item->{'itemnumber'} = $itemnumber;
+
+    # create MARC tag representing item and add to bib
+    my $new_item_marc = _marc_from_item_hash($item, $frameworkcode);
+    _add_item_field_to_biblio($new_item_marc, $item->{'biblionumber'}, $frameworkcode );
+   
+    logaction(C4::Context->userenv->{'number'},"CATALOGUING","ADD",$itemnumber,"item") 
+        if C4::Context->preference("CataloguingLog");
+    
+    return ($item->{biblionumber}, $item->{biblioitemnumber}, $itemnumber);
+}
+
+=head2 ModItemFromMarc
+
+=cut
+
+sub ModItemFromMarc {
+    my $item_marc = shift;
+    my $biblionumber = shift;
+    my $itemnumber = shift;
+
+    my $dbh = C4::Context->dbh;
+    my $frameworkcode = GetFrameworkCode( $biblionumber );
+    my $item = &TransformMarcToKoha( $dbh, $item_marc, $frameworkcode );
+   
+    return ModItem($item, $biblionumber, $itemnumber, $dbh, $frameworkcode); 
+}
+
+=head2 ModItem
+
+=cut
+
+sub ModItem {
+    my $item = shift;
+    my $biblionumber = shift;
+    my $itemnumber = shift;
+
+    # if $biblionumber is undefined, get it from the current item
+    unless (defined $biblionumber) {
+        $biblionumber = _get_single_item_column('biblionumber', $itemnumber);
+    }
+
+    my $dbh           = @_ ? shift : C4::Context->dbh;
+    my $frameworkcode = @_ ? shift : GetFrameworkCode( $biblionumber );
+
+    $item->{'itemnumber'} = $itemnumber;
+    _set_derived_columns_for_mod($item);
+    # FIXME add fixes
+    # FIXME add checks
+
+    # 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);
+    
+    logaction(C4::Context->userenv->{'number'},"CATALOGUING","MODIFY",$itemnumber,$new_item_marc->as_formatted)
+        if C4::Context->preference("CataloguingLog");
+}
+
+=head2 ModItemTransfer
+
+=cut
+
+sub ModItemTransfer {
+    my ( $itemnumber, $frombranch, $tobranch ) = @_;
+
+    my $dbh = C4::Context->dbh;
+
+    #new entry in branchtransfers....
+    my $sth = $dbh->prepare(
+        "INSERT INTO branchtransfers (itemnumber, frombranch, datesent, tobranch)
+        VALUES (?, ?, NOW(), ?)");
+    $sth->execute($itemnumber, $frombranch, $tobranch);
+
+    ModItem({ holdingbranch => $tobranch }, undef, $itemnumber);
+    ModDateLastSeen($itemnumber);
+    return;
+}
+
+=head2 ModDateLastSeen
+
+=over 4
+
+ModDateLastSeen($itemnum);
+
+=back
+
+Mark item as seen. Is called when an item is issued, returned or manually marked during inventory/stocktaking.
+C<$itemnum> is the item number
+
+=cut
+
+sub ModDateLastSeen {
+    my ($itemnumber) = @_;
+    
+    my $today = C4::Dates->new();    
+    ModItem({ itemlost => 0, datelastseen => $today->output("iso") }, undef, $itemnumber);
+}
+
+=head1 PRIVATE FUNCTIONS AND VARIABLES
+
+The following functions are not meant to be called
+directly, but are documented in order to explain
+the inner workings of C<C4::Items>.
+
+=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 
+
+=over 4
+
+_set_derived_column_for_add($item);
+
+=back
+
+Given an item hash representing a new item to be added,
+calculate any derived columns.  Currently the only
+such column is C<items.cn_sort>.
+
+=cut
+
+sub _set_derived_columns_for_add {
+    my $item = shift;
+
+    foreach my $column (keys %derived_columns) {
+        my $builder = $derived_columns{$column}->{'BUILDER'};
+        my $source_values = {};
+        foreach my $source_column (keys %{ $derived_columns{$column} }) {
+            next if $source_column eq 'BUILDER';
+            $source_values->{$source_column} = $item->{$source_column};
+        }
+        $builder->($item, $source_values);
+    }
+}
+
+=head2 _set_derived_columns_for_mod 
+
+=over 4
+
+_set_derived_column_for_mod($item);
+
+=back
+
+Given an item hash representing a new item to be modified.
+calculate any derived columns.  Currently the only
+such column is C<items.cn_sort>.
+
+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<ModItem> 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);
+        }
+    }
+}
+
+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
+
+=over 4
+
+_calc_items_cn_sort($item, $source_values);
+
+=back
+
+Helper routine to calculate C<items.cn_sort>.
+
+=cut
+
+sub _calc_items_cn_sort {
+    my $item = shift;
+    my $source_values = shift;
+
+    $item->{'items.cn_sort'} = GetClassSort($source_values->{'items.cn_source'}, $source_values->{'itemcallnumber'}, "");
+}
+
+=head2 _set_defaults_for_add 
+
+=over 4
+
+_set_defaults_for_add($item_hash);
+
+=back
+
+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<items.dateaccessioned>
+
+=item *
+
+C<items.notforloan>
+
+=item *
+
+C<items.damaged>
+
+=item *
+
+C<items.itemlost>
+
+=item *
+
+C<items.wthdrawn>
+
+=back
+
+=cut
+
+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'};
+    $item->{'damaged'}    = 0 unless exists $item->{'damaged'};
+    $item->{'itemlost'}   = 0 unless exists $item->{'itemlost'};
+    $item->{'wthdrawn'}   = 0 unless exists $item->{'wthdrawn'};
+}
+
+=head2 _set_calculated_values
+
+=head2 _koha_new_item
+
+=over 4
+
+my ($itemnumber,$error) = _koha_new_item( $dbh, $item, $barcode );
+
+=back
+
+=cut
+
+sub _koha_new_item {
+    my ( $dbh, $item, $barcode ) = @_;
+    my $error;
+
+    my $query = 
+           "INSERT INTO items SET
+            biblionumber        = ?,
+            biblioitemnumber    = ?,
+            barcode             = ?,
+            dateaccessioned     = ?,
+            booksellerid        = ?,
+            homebranch          = ?,
+            price               = ?,
+            replacementprice    = ?,
+            replacementpricedate = NOW(),
+            datelastborrowed    = ?,
+            datelastseen        = NOW(),
+            stack               = ?,
+            notforloan          = ?,
+            damaged             = ?,
+            itemlost            = ?,
+            wthdrawn            = ?,
+            itemcallnumber      = ?,
+            restricted          = ?,
+            itemnotes           = ?,
+            holdingbranch       = ?,
+            paidfor             = ?,
+            location            = ?,
+            onloan              = ?,
+            issues              = ?,
+            renewals            = ?,
+            reserves            = ?,
+            cn_source           = ?,
+            cn_sort             = ?,
+            ccode               = ?,
+            itype               = ?,
+            materials           = ?,
+            uri                 = ?
+          ";
+    my $sth = $dbh->prepare($query);
+    $sth->execute(
+            $item->{'biblionumber'},
+            $item->{'biblioitemnumber'},
+            $barcode,
+            $item->{'dateaccessioned'},
+            $item->{'booksellerid'},
+            $item->{'homebranch'},
+            $item->{'price'},
+            $item->{'replacementprice'},
+            $item->{datelastborrowed},
+            $item->{stack},
+            $item->{'notforloan'},
+            $item->{'damaged'},
+            $item->{'itemlost'},
+            $item->{'wthdrawn'},
+            $item->{'itemcallnumber'},
+            $item->{'restricted'},
+            $item->{'itemnotes'},
+            $item->{'holdingbranch'},
+            $item->{'paidfor'},
+            $item->{'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'},
+    );
+    my $itemnumber = $dbh->{'mysql_insertid'};
+    if ( defined $sth->errstr ) {
+        $error.="ERROR in _koha_new_item $query".$sth->errstr;
+    }
+    $sth->finish();
+    return ( $itemnumber, $error );
+}
+
+=head2 _koha_modify_item
+
+=over 4
+
+my ($itemnumber,$error) =_koha_modify_item( $dbh, $item, $op );
+
+=back
+
+=cut
+
+sub _koha_modify_item {
+    my ( $dbh, $item ) = @_;
+    my $error;
+
+    my $query = "UPDATE items SET ";
+    my @bind;
+    for my $key ( keys %$item ) {
+        $query.="$key=?,";
+        push @bind, $item->{$key};
+    }
+    $query =~ s/,$//;
+    $query .= " WHERE itemnumber=?";
+    push @bind, $item->{'itemnumber'};
+    my $sth = $dbh->prepare($query);
+    $sth->execute(@bind);
+    if ( $dbh->errstr ) {
+        $error.="ERROR in _koha_modify_item $query".$dbh->errstr;
+        warn $error;
+    }
+    $sth->finish();
+    return ($item->{'itemnumber'},$error);
+}
+
+=head2 _marc_from_item_hash
+
+=over 4
+
+my $item_marc = _marc_from_item_hash($item, $frameworkcode);
+
+=back
+
+Given an item hash representing a complete item record,
+create a C<MARC::Record> object containing an embedded
+tag representing that item.
+
+=cut
+
+sub _marc_from_item_hash {
+    my $item = shift;
+    my $frameworkcode = shift;
+   
+    # 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 '' ? 
+                                (/^items\./ ? ($_ => $item->{$_}) : ("items.$_" => $item->{$_})) 
+                                : ()  } keys %{ $item } }; 
+
+    my $item_marc = MARC::Record->new();
+    foreach my $item_field (keys %{ $mungeditem }) {
+        my ($tag, $subfield) = GetMarcFromKohaField($item_field, $frameworkcode);
+        next unless defined $tag and defined $subfield; # skip if not mapped to MARC field
+        if (my $field = $item_marc->field($tag)) {
+            $field->add_subfields($subfield => $mungeditem->{$item_field});
+        } else {
+            $item_marc->add_fields( $tag, " ", " ", $subfield =>  $mungeditem->{$item_field});
+        }
+    }
+
+    return $item_marc;
+}
+
+=head2 _add_item_field_to_biblio
+
+=over 4
+
+_add_item_field_to_biblio($record, $biblionumber, $frameworkcode);
+
+=back
+
+Adds the fields from a MARC record containing the
+representation of a Koha item record to the MARC
+biblio record.  The input C<$item_marc> record
+is expect to contain just one field, the embedded
+item information field.
+
+=cut
+
+sub _add_item_field_to_biblio {
+    my ($item_marc, $biblionumber, $frameworkcode) = @_;
+
+    my $biblio_marc = GetMarcBiblio($biblionumber);
+
+    foreach my $field ($item_marc->fields()) {
+        $biblio_marc->append_fields($field);
+    }
+
+    ModBiblioMarc($biblio_marc, $biblionumber, $frameworkcode);
+}
+1;
index 38b5a41..5673dea 100644 (file)
@@ -25,6 +25,7 @@ use POSIX qw(strftime);
 use C4::Suggestions;
 use C4::Koha;
 use C4::Biblio;
+use C4::Items;
 use C4::Search;
 use C4::Letters;
 use C4::Log; # logaction
@@ -1695,7 +1696,7 @@ sub ItemizeSerials {
                     $marcrecord->insert_fields_ordered($newField);
                 }
             }
-            AddItem( $marcrecord, $data->{'biblionumber'} );
+            AddItemFromMarc( $marcrecord, $data->{'biblionumber'} );
             return 1;
         }
         return ( 0, @errors );
index 0a70e97..1d5b169 100755 (executable)
@@ -28,6 +28,7 @@ use C4::Output;
 use C4::Context;
 use C4::Acquisition;
 use C4::Biblio;
+use C4::Items;
 use C4::Search;
 
 my $input=new CGI;
@@ -75,7 +76,7 @@ if ($quantityrec > $origquantityrec ) {
                     "items.itype"          => $itemtype[$cnt],
                     "items.location"          => $location[$cnt],
                     "items.loan"             => 0, });
-               AddItem($itemRecord,$biblionumber);
+        AddItemFromMarc($itemRecord,$biblionumber);
     }
 }
     print $input->redirect("/cgi-bin/koha/acqui/parcel.pl?invoice=$invoiceno&supplierid=$supplierid&freight=$freight&gst=$gst&datereceived=$datereceived");
index 83a11ee..24b1a7e 100755 (executable)
@@ -22,6 +22,7 @@ use warnings;
 use CGI;
 use C4::Context;
 use C4::Biblio;
+use C4::Items;
 use C4::Output;
 use C4::Circulation;
 use C4::Accounts;
@@ -51,32 +52,22 @@ for ($damaged,$itemlost,$wthdrawn) {
 }
 
 # modify MARC item if input differs from items table.
-if ( $itemnotes ne $item_data_hashref->{'itemnotes'}) {
-    ModItemInMarconefield($biblionumber, $itemnumber, 'items.itemnotes', $itemnotes);
-    $item_data_hashref->{'itemnotes'} = $itemnotes;
+my $item_changes = {};
+if (defined $itemnotes and ($itemnotes ne $item_data_hashref->{'itemnotes'})) {
+    $item_changes->{'itemnotes'} = $itemnotes;
 } elsif ($itemlost ne $item_data_hashref->{'itemlost'}) {
-    ModItemInMarconefield($biblionumber, $itemnumber, 'items.itemlost', $itemlost);
-    $item_data_hashref->{'itemlost'} = $itemlost;
+    $item_changes->{'itemlost'} = $itemlost;
 } elsif ($wthdrawn ne $item_data_hashref->{'wthdrawn'}) {
-    ModItemInMarconefield($biblionumber, $itemnumber, 'items.wthdrawn', $wthdrawn);
-    $item_data_hashref->{'wthdrawn'} = $wthdrawn;
+    $item_changes->{'wthdrawn'} = $wthdrawn;
 } elsif ($damaged ne $item_data_hashref->{'damaged'}) {
-    ModItemInMarconefield($biblionumber, $itemnumber, 'items.damaged', $damaged);
-    $item_data_hashref->{'damaged'} = $damaged;
+    $item_changes->{'damaged'} = $damaged;
 } else {
     #nothings changed, so do nothing.
     print $cgi->redirect("moredetail.pl?biblionumber=$biblionumber&itemnumber=$itemnumber");
 }
 
-# FIXME: eventually we'll use Biblio.pm, but it's currently too buggy  (is this current ??)
-# yes as of dec 30 2007, ModItem doesn't update zebra for status changes, it requires
-# a MARC record be passed in
-#ModItem( $dbh,'',$biblionumber,$itemnumber,'',$item_hashref );
-#   &C4::Biblio::_koha_modify_item($dbh,$item_data_hashref);
-    my $sth = $dbh->prepare("UPDATE items SET wthdrawn=?,itemlost=?,damaged=?,itemnotes=? WHERE itemnumber=?");
-    $sth->execute($wthdrawn,$itemlost,$damaged,$itemnotes,$itemnumber);
-    &ModZebra($biblionumber,"specialUpdate","biblioserver");
-    
+ModItem($item_changes, $biblionumber, $itemnumber);
+
 # check issues iff itemlost.
 # http://wiki.koha.org/doku.php?id=en:development:kohastatuses
 # lost ==1 Lost, lost==2 longoverdue, lost==3 lost and paid for
index 88b9f49..e575fa6 100755 (executable)
@@ -23,6 +23,7 @@ use strict;
 use C4::Auth;
 use C4::Output;
 use C4::Biblio;
+use C4::Items;
 use C4::Context;
 use C4::Koha; # XXX subfield_is_koha_internal_p
 use C4::Branch; # XXX subfield_is_koha_internal_p
@@ -115,7 +116,7 @@ if ($op eq "additem") {
     my $exist_itemnumber = get_item_from_barcode($addedolditem->{'barcode'});
     push @errors,"barcode_not_unique" if($exist_itemnumber);
     # if barcode exists, don't create, but report The problem.
-    my ($oldbiblionumber,$oldbibnum,$oldbibitemnum) = AddItem($record,$biblionumber) unless ($exist_itemnumber);
+    my ($oldbiblionumber,$oldbibnum,$oldbibitemnum) = AddItemFromMarc($record,$biblionumber) unless ($exist_itemnumber);
     if ($exist_itemnumber) {
         $nextop = "additem";
         $itemrecord = $record;
@@ -174,7 +175,7 @@ if ($op eq "additem") {
     if ($exist_itemnumber && $exist_itemnumber != $itemnumber) {
         push @errors,"barcode_not_unique";
     } else {
-        my ($oldbiblionumber,$oldbibnum,$oldbibitemnum) = ModItem($itemtosave,$biblionumber,$itemnumber,0);
+        my ($oldbiblionumber,$oldbibnum,$oldbibitemnum) = ModItemFromMarc($itemtosave,$biblionumber,$itemnumber);
     $itemnumber="";
     }
     $nextop="additem";
index c7fcb87..c97bd60 100755 (executable)
@@ -35,6 +35,7 @@ use C4::Dates qw/format_date/;
 use C4::Print;
 use C4::Reserves;
 use C4::Biblio;
+use C4::Items;
 use C4::Members;
 use C4::Branch; # GetBranchName
 use C4::Koha;   # FIXME : is it still useful ?
index 3ad348f..18466ab 100755 (executable)
@@ -35,6 +35,7 @@ use Date::Calc qw(
 );
 use C4::Koha;
 use C4::Biblio;
+use C4::Items;
 
 my $input = new CGI;
 
index 5e937f2..7d805b8 100755 (executable)
@@ -28,6 +28,7 @@ use C4::Dates qw/format_date/;
 use C4::Circulation;
 use C4::Members;
 use C4::Biblio;
+use C4::Items;
 
 use Date::Calc qw(
   Today
index eebd634..aadbf28 100755 (executable)
@@ -23,6 +23,7 @@ use C4::Auth;
 use C4::Context;
 use C4::Output;
 use C4::Biblio;
+use C4::Items;
 use C4::Dates qw/format_date/;
 
 # Fixed variables
index 70e2208..f5024a9 100755 (executable)
@@ -67,6 +67,7 @@ use CGI;
 use C4::Auth;
 use C4::Dates qw/format_date format_date_in_iso/;
 use C4::Biblio;
+use C4::Items;
 use C4::Koha;
 use C4::Output;
 use C4::Context;
@@ -241,12 +242,12 @@ if ($op eq 'serialchangestatus') {
             $template->param("barcode_not_unique" => 1,'errserialseq'=>$serialseqs[$index]);
             # if barcode exists, don't create, but report The problem.
             unless ($exists){
-              my ($biblionumber,$bibitemnum,$itemnumber) = AddItem($record,$itemhash{$item}->{'bibnum'});
+              my ($biblionumber,$bibitemnum,$itemnumber) = AddItemFromMarc($record,$itemhash{$item}->{'bibnum'});
               AddItem2Serial($itemhash{$item}->{'serial'},$itemnumber);
             }
           } else {
             #modify item
-            my ($oldbiblionumber,$oldbibnum,$itemnumber) = ModItem($record,$itemhash{$item}->{'bibnum'},$item,0);
+            my ($oldbiblionumber,$oldbibnum,$itemnumber) = ModItemFromMarc($record,$itemhash{$item}->{'bibnum'},$item);
           }
         }
       }
index 6c33008..48d9c50 100755 (executable)
@@ -23,6 +23,7 @@ use C4::Auth;
 use C4::Context;
 use C4::Output;
 use C4::Biblio;
+use C4::Items;
 use C4::Dates qw/format_date format_date_in_iso/;
 use C4::Koha;
 use C4::Branch; # GetBranches