View | Details | Raw Unified | Return to bug 23463
Collapse All | Expand All

(-)a/C4/Items.pm (-310 / +8 lines)
Lines 247-261 sub AddItemBatchFromMarc { Link Here
247
            next ITEMFIELD;
247
            next ITEMFIELD;
248
        }
248
        }
249
249
250
        _set_derived_columns_for_add($item);
250
        my $item_object = Koha::Item->new($item)->store;
251
        my ( $itemnumber, $error ) = _koha_new_item( $item, $item->{barcode} );
251
        push @itemnumbers, $item_object->itemnumber; # FIXME not checking error
252
        warn $error if $error;
253
        push @itemnumbers, $itemnumber; # FIXME not checking error
254
        $item->{'itemnumber'} = $itemnumber;
255
252
256
        logaction("CATALOGUING", "ADD", $itemnumber, "item") if C4::Context->preference("CataloguingLog"); 
253
        logaction("CATALOGUING", "ADD", $item->itemnumber, "item") if C4::Context->preference("CataloguingLog"); 
257
254
258
        my $new_item_marc = _marc_from_item_hash($item, $frameworkcode, $unlinked_item_subfields);
255
        my $new_item_marc = _marc_from_item_hash($item->unblessed, $frameworkcode, $unlinked_item_subfields);
259
        $item_field->replace_with($new_item_marc->field($itemtag));
256
        $item_field->replace_with($new_item_marc->field($itemtag));
260
    }
257
    }
261
258
Lines 270-352 sub AddItemBatchFromMarc { Link Here
270
    return (\@itemnumbers, \@errors);
267
    return (\@itemnumbers, \@errors);
271
}
268
}
272
269
273
=head2 ModItemFromMarc
274
275
  ModItemFromMarc($item_marc, $biblionumber, $itemnumber);
276
277
This function updates an item record based on a supplied
278
C<MARC::Record> object containing an embedded item field.
279
This API is meant for the use of C<additem.pl>; for 
280
other purposes, C<ModItem> should be used.
281
282
This function uses the hash %default_values_for_mod_from_marc,
283
which contains default values for item fields to
284
apply when modifying an item.  This is needed because
285
if an item field's value is cleared, TransformMarcToKoha
286
does not include the column in the
287
hash that's passed to ModItem, which without
288
use of this hash makes it impossible to clear
289
an item field's value.  See bug 2466.
290
291
Note that only columns that can be directly
292
changed from the cataloging and serials
293
item editors are included in this hash.
294
295
Returns item record
296
297
=cut
298
299
sub _build_default_values_for_mod_marc {
300
    # Has no framework parameter anymore, since Default is authoritative
301
    # for Koha to MARC mappings.
302
303
    my $cache     = Koha::Caches->get_instance();
304
    my $cache_key = "default_value_for_mod_marc-";
305
    my $cached    = $cache->get_from_cache($cache_key);
306
    return $cached if $cached;
307
308
    my $default_values = {
309
        barcode                  => undef,
310
        booksellerid             => undef,
311
        ccode                    => undef,
312
        'items.cn_source'        => undef,
313
        coded_location_qualifier => undef,
314
        copynumber               => undef,
315
        damaged                  => 0,
316
        enumchron                => undef,
317
        holdingbranch            => undef,
318
        homebranch               => undef,
319
        itemcallnumber           => undef,
320
        itemlost                 => 0,
321
        itemnotes                => undef,
322
        itemnotes_nonpublic      => undef,
323
        itype                    => undef,
324
        location                 => undef,
325
        permanent_location       => undef,
326
        materials                => undef,
327
        new_status               => undef,
328
        notforloan               => 0,
329
        price                    => undef,
330
        replacementprice         => undef,
331
        replacementpricedate     => undef,
332
        restricted               => undef,
333
        stack                    => undef,
334
        stocknumber              => undef,
335
        uri                      => undef,
336
        withdrawn                => 0,
337
    };
338
    my %default_values_for_mod_from_marc;
339
    while ( my ( $field, $default_value ) = each %$default_values ) {
340
        my $kohafield = $field;
341
        $kohafield =~ s|^([^\.]+)$|items.$1|;
342
        $default_values_for_mod_from_marc{$field} = $default_value
343
            if C4::Biblio::GetMarcFromKohaField( $kohafield );
344
    }
345
346
    $cache->set_in_cache($cache_key, \%default_values_for_mod_from_marc);
347
    return \%default_values_for_mod_from_marc;
348
}
349
350
sub ModItemFromMarc {
270
sub ModItemFromMarc {
351
    my $item_marc = shift;
271
    my $item_marc = shift;
352
    my $biblionumber = shift;
272
    my $biblionumber = shift;
Lines 359-374 sub ModItemFromMarc { Link Here
359
    $localitemmarc->append_fields( $item_marc->field($itemtag) );
279
    $localitemmarc->append_fields( $item_marc->field($itemtag) );
360
    my $item = TransformMarcToKoha( $localitemmarc, $frameworkcode, 'items' );
280
    my $item = TransformMarcToKoha( $localitemmarc, $frameworkcode, 'items' );
361
    my $default_values = _build_default_values_for_mod_marc();
281
    my $default_values = _build_default_values_for_mod_marc();
282
    my $item_object = Koha::Items->find($itemnumber);
362
    foreach my $item_field ( keys %$default_values ) {
283
    foreach my $item_field ( keys %$default_values ) {
363
        $item->{$item_field} = $default_values->{$item_field}
284
        $item_object->$item_field($default_values->{$item_field})
364
          unless exists $item->{$item_field};
285
          unless exists $item->{$item_field};
365
    }
286
    }
366
    my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
287
    my $unlinked_item_subfields = _get_unlinked_item_subfields( $localitemmarc, $frameworkcode );
367
288
368
    my $item_object = Koha::Items->find($itemnumber);
369
    $item_object->more_subfields_xml(_get_unlinked_subfields_xml($unlinked_item_subfields))->store;
289
    $item_object->more_subfields_xml(_get_unlinked_subfields_xml($unlinked_item_subfields))->store;
290
    $item_object->store;
370
291
371
    return $item_object->get_from_storage->unblessed;
292
    return $item_object->unblessed;
372
}
293
}
373
294
374
=head2 ModItemTransfer
295
=head2 ModItemTransfer
Lines 1121-1321 the inner workings of C<C4::Items>. Link Here
1121
1042
1122
=cut
1043
=cut
1123
1044
1124
=head2 %derived_columns
1125
1126
This hash keeps track of item columns that
1127
are strictly derived from other columns in
1128
the item record and are not meant to be set
1129
independently.
1130
1131
Each key in the hash should be the name of a
1132
column (as named by TransformMarcToKoha).  Each
1133
value should be hashref whose keys are the
1134
columns on which the derived column depends.  The
1135
hashref should also contain a 'BUILDER' key
1136
that is a reference to a sub that calculates
1137
the derived value.
1138
1139
=cut
1140
1141
my %derived_columns = (
1142
    'items.cn_sort' => {
1143
        'itemcallnumber' => 1,
1144
        'items.cn_source' => 1,
1145
        'BUILDER' => \&_calc_items_cn_sort,
1146
    }
1147
);
1148
1149
=head2 _set_derived_columns_for_add 
1150
1151
  _set_derived_column_for_add($item);
1152
1153
Given an item hash representing a new item to be added,
1154
calculate any derived columns.  Currently the only
1155
such column is C<items.cn_sort>.
1156
1157
=cut
1158
1159
sub _set_derived_columns_for_add {
1160
    my $item = shift;
1161
1162
    foreach my $column (keys %derived_columns) {
1163
        my $builder = $derived_columns{$column}->{'BUILDER'};
1164
        my $source_values = {};
1165
        foreach my $source_column (keys %{ $derived_columns{$column} }) {
1166
            next if $source_column eq 'BUILDER';
1167
            $source_values->{$source_column} = $item->{$source_column};
1168
        }
1169
        $builder->($item, $source_values);
1170
    }
1171
}
1172
1173
=head2 _get_single_item_column
1174
1175
  _get_single_item_column($column, $itemnumber);
1176
1177
Retrieves the value of a single column from an C<items>
1178
row specified by C<$itemnumber>.
1179
1180
=cut
1181
1182
sub _get_single_item_column {
1183
    my $column = shift;
1184
    my $itemnumber = shift;
1185
    
1186
    my $dbh = C4::Context->dbh;
1187
    my $sth = $dbh->prepare("SELECT $column FROM items WHERE itemnumber = ?");
1188
    $sth->execute($itemnumber);
1189
    my ($value) = $sth->fetchrow();
1190
    return $value; 
1191
}
1192
1193
=head2 _calc_items_cn_sort
1194
1195
  _calc_items_cn_sort($item, $source_values);
1196
1197
Helper routine to calculate C<items.cn_sort>.
1198
1199
=cut
1200
1201
sub _calc_items_cn_sort {
1202
    my $item = shift;
1203
    my $source_values = shift;
1204
1205
    $item->{'items.cn_sort'} = GetClassSort($source_values->{'items.cn_source'}, $source_values->{'itemcallnumber'}, "");
1206
}
1207
1208
=head2 _koha_new_item
1209
1210
  my ($itemnumber,$error) = _koha_new_item( $item, $barcode );
1211
1212
Perform the actual insert into the C<items> table.
1213
1214
=cut
1215
1216
sub _koha_new_item {
1217
    my ( $item, $barcode ) = @_;
1218
    my $dbh=C4::Context->dbh;  
1219
    my $error;
1220
    $item->{permanent_location} //= $item->{location};
1221
    _mod_item_dates( $item );
1222
    my $query =
1223
           "INSERT INTO items SET
1224
            biblionumber        = ?,
1225
            biblioitemnumber    = ?,
1226
            barcode             = ?,
1227
            dateaccessioned     = ?,
1228
            booksellerid        = ?,
1229
            homebranch          = ?,
1230
            price               = ?,
1231
            replacementprice    = ?,
1232
            replacementpricedate = ?,
1233
            datelastborrowed    = ?,
1234
            datelastseen        = ?,
1235
            stack               = ?,
1236
            notforloan          = ?,
1237
            damaged             = ?,
1238
            itemlost            = ?,
1239
            withdrawn           = ?,
1240
            itemcallnumber      = ?,
1241
            coded_location_qualifier = ?,
1242
            restricted          = ?,
1243
            itemnotes           = ?,
1244
            itemnotes_nonpublic = ?,
1245
            holdingbranch       = ?,
1246
            location            = ?,
1247
            permanent_location  = ?,
1248
            onloan              = ?,
1249
            issues              = ?,
1250
            renewals            = ?,
1251
            reserves            = ?,
1252
            cn_source           = ?,
1253
            cn_sort             = ?,
1254
            ccode               = ?,
1255
            itype               = ?,
1256
            materials           = ?,
1257
            uri                 = ?,
1258
            enumchron           = ?,
1259
            more_subfields_xml  = ?,
1260
            copynumber          = ?,
1261
            stocknumber         = ?,
1262
            new_status          = ?
1263
          ";
1264
    my $sth = $dbh->prepare($query);
1265
    my $today = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
1266
   $sth->execute(
1267
            $item->{'biblionumber'},
1268
            $item->{'biblioitemnumber'},
1269
            $barcode,
1270
            $item->{'dateaccessioned'},
1271
            $item->{'booksellerid'},
1272
            $item->{'homebranch'},
1273
            $item->{'price'},
1274
            $item->{'replacementprice'},
1275
            $item->{'replacementpricedate'} || $today,
1276
            $item->{datelastborrowed},
1277
            $item->{datelastseen} || $today,
1278
            $item->{stack},
1279
            $item->{'notforloan'},
1280
            $item->{'damaged'},
1281
            $item->{'itemlost'},
1282
            $item->{'withdrawn'},
1283
            $item->{'itemcallnumber'},
1284
            $item->{'coded_location_qualifier'},
1285
            $item->{'restricted'},
1286
            $item->{'itemnotes'},
1287
            $item->{'itemnotes_nonpublic'},
1288
            $item->{'holdingbranch'},
1289
            $item->{'location'},
1290
            $item->{'permanent_location'},
1291
            $item->{'onloan'},
1292
            $item->{'issues'},
1293
            $item->{'renewals'},
1294
            $item->{'reserves'},
1295
            $item->{'items.cn_source'},
1296
            $item->{'items.cn_sort'},
1297
            $item->{'ccode'},
1298
            $item->{'itype'},
1299
            $item->{'materials'},
1300
            $item->{'uri'},
1301
            $item->{'enumchron'},
1302
            $item->{'more_subfields_xml'},
1303
            $item->{'copynumber'},
1304
            $item->{'stocknumber'},
1305
            $item->{'new_status'},
1306
    );
1307
1308
    my $itemnumber;
1309
    if ( defined $sth->errstr ) {
1310
        $error.="ERROR in _koha_new_item $query".$sth->errstr;
1311
    }
1312
    else {
1313
        $itemnumber = $dbh->{'mysql_insertid'};
1314
    }
1315
1316
    return ( $itemnumber, $error );
1317
}
1318
1319
=head2 MoveItemFromBiblio
1045
=head2 MoveItemFromBiblio
1320
1046
1321
  MoveItemFromBiblio($itenumber, $frombiblio, $tobiblio);
1047
  MoveItemFromBiblio($itenumber, $frombiblio, $tobiblio);
Lines 1366-1399 sub MoveItemFromBiblio { Link Here
1366
    return;
1092
    return;
1367
}
1093
}
1368
1094
1369
sub _mod_item_dates { # date formatting for date fields in item hash
1370
    my ( $item ) = @_;
1371
    return if !$item || ref($item) ne 'HASH';
1372
1373
    my @keys = grep
1374
        { $_ =~ /^onloan$|^date|date$|datetime$/ }
1375
        keys %$item;
1376
    # Incl. dateaccessioned,replacementpricedate,datelastborrowed,datelastseen
1377
    # NOTE: We do not (yet) have items fields ending with datetime
1378
    # Fields with _on$ have been handled already
1379
1380
    foreach my $key ( @keys ) {
1381
        next if !defined $item->{$key}; # skip undefs
1382
        my $dt = eval { dt_from_string( $item->{$key} ) };
1383
            # eval: dt_from_string will die on us if we pass illegal dates
1384
1385
        my $newstr;
1386
        if( defined $dt  && ref($dt) eq 'DateTime' ) {
1387
            if( $key =~ /datetime/ ) {
1388
                $newstr = DateTime::Format::MySQL->format_datetime($dt);
1389
            } else {
1390
                $newstr = DateTime::Format::MySQL->format_date($dt);
1391
            }
1392
        }
1393
        $item->{$key} = $newstr; # might be undef to clear garbage
1394
    }
1395
}
1396
1397
=head2 _marc_from_item_hash
1095
=head2 _marc_from_item_hash
1398
1096
1399
  my $item_marc = _marc_from_item_hash($item, $frameworkcode[, $unlinked_item_subfields]);
1097
  my $item_marc = _marc_from_item_hash($item, $frameworkcode[, $unlinked_item_subfields]);
(-)a/t/db_dependent/Items.t (-134 / +1 lines)
Lines 34-40 use Koha::AuthorisedValues; Link Here
34
use t::lib::Mocks;
34
use t::lib::Mocks;
35
use t::lib::TestBuilder;
35
use t::lib::TestBuilder;
36
36
37
use Test::More tests => 15;
37
use Test::More tests => 13;
38
38
39
use Test::Warn;
39
use Test::Warn;
40
40
Lines 733-870 subtest 'C4::Biblio::EmbedItemsInMarcBiblio' => sub { Link Here
733
};
733
};
734
734
735
735
736
subtest 'C4::Items::_build_default_values_for_mod_marc' => sub {
737
    plan tests => 4;
738
739
    $schema->storage->txn_begin();
740
741
    my $builder = t::lib::TestBuilder->new;
742
    my $framework = $builder->build({ source => 'BiblioFramework' });
743
744
    # Link biblio.biblionumber and biblioitems.biblioitemnumber to avoid _koha_marc_update_bib_ids to fail with 'no biblio[item]number tag for framework"
745
    Koha::MarcSubfieldStructures->search({ frameworkcode => '', tagfield => '999', tagsubfield => [ 'c', 'd' ] })->delete;
746
    Koha::MarcSubfieldStructure->new({ frameworkcode => '', tagfield => '999', tagsubfield => 'c', kohafield => 'biblio.biblionumber' })->store;
747
    Koha::MarcSubfieldStructure->new({ frameworkcode => '', tagfield => '999', tagsubfield => 'd', kohafield => 'biblioitems.biblioitemnumber' })->store;
748
749
    # Same for item fields: itemnumber, barcode, itype
750
    Koha::MarcSubfieldStructures->search({ frameworkcode => '', tagfield => '952', tagsubfield => [ '9', 'p', 'y' ] })->delete;
751
    Koha::MarcSubfieldStructure->new({ frameworkcode => '', tagfield => '952', tagsubfield => '9', kohafield => 'items.itemnumber' })->store;
752
    Koha::MarcSubfieldStructure->new({ frameworkcode => '', tagfield => '952', tagsubfield => 'p', kohafield => 'items.barcode' })->store;
753
    Koha::MarcSubfieldStructure->new({ frameworkcode => '', tagfield => '952', tagsubfield => 'y', kohafield => 'items.itype' })->store;
754
    Koha::Caches->get_instance->clear_from_cache( "MarcSubfieldStructure-" );
755
756
    my $itemtype = $builder->build({ source => 'Itemtype' })->{itemtype};
757
758
    # Create a record with a barcode
759
    my $biblio = $builder->build_sample_biblio({ frameworkcode => $framework->{frameworkcode} });
760
    my $item_record = new MARC::Record;
761
    my $a_barcode = 'a barcode';
762
    my $barcode_field = MARC::Field->new(
763
        '952', ' ', ' ',
764
        p => $a_barcode,
765
        y => $itemtype
766
    );
767
    my $itemtype_field = MARC::Field->new(
768
        '952', ' ', ' ',
769
        y => $itemtype
770
    );
771
    $item_record->append_fields( $barcode_field );
772
    my (undef, undef, $item_itemnumber) = AddItemFromMarc($item_record, $biblio->biblionumber);
773
774
    # Make sure everything has been set up
775
    my $item = Koha::Items->find($item_itemnumber);
776
    is( $item->barcode, $a_barcode, 'Everything has been set up correctly, the barcode is defined as expected' );
777
778
    # Delete the barcode field and save the record
779
    $item_record->delete_fields( $barcode_field );
780
    $item_record->append_fields( $itemtype_field ); # itemtype is mandatory
781
    ModItemFromMarc($item_record, $biblio->biblionumber, $item_itemnumber);
782
    $item = Koha::Items->find($item_itemnumber);
783
    is( $item->barcode, undef, 'The default value should have been set to the barcode, the field is mapped to a kohafield' );
784
785
    # Re-add the barcode field and save the record
786
    $item_record->append_fields( $barcode_field );
787
    ModItemFromMarc($item_record, $biblio->biblionumber, $item_itemnumber);
788
    $item = Koha::Items->find($item_itemnumber);
789
    is( $item->barcode, $a_barcode, 'Everything has been set up correctly, the barcode is defined as expected' );
790
791
    # Remove the mapping for barcode
792
    Koha::MarcSubfieldStructures->search({ frameworkcode => '', tagfield => '952', tagsubfield => 'p' })->delete;
793
794
    # And make sure the caches are cleared
795
    my $cache = Koha::Caches->get_instance();
796
    $cache->clear_from_cache("default_value_for_mod_marc-");
797
    $cache->clear_from_cache("MarcSubfieldStructure-");
798
799
    # Update the MARC field with another value
800
    $item_record->delete_fields( $barcode_field );
801
    my $another_barcode = 'another_barcode';
802
    my $another_barcode_field = MARC::Field->new(
803
        '952', ' ', ' ',
804
        p => $another_barcode,
805
    );
806
    $item_record->append_fields( $another_barcode_field );
807
    # The DB value should not have been updated
808
    ModItemFromMarc($item_record, $biblio->biblionumber, $item_itemnumber);
809
    $item = Koha::Items->find($item_itemnumber);
810
    is ( $item->barcode, $a_barcode, 'items.barcode is not mapped anymore, so the DB column has not been updated' );
811
812
    $cache->clear_from_cache("default_value_for_mod_marc-");
813
    $cache->clear_from_cache( "MarcSubfieldStructure-" );
814
    $schema->storage->txn_rollback;
815
};
816
817
subtest '_mod_item_dates' => sub {
818
    plan tests => 11;
819
820
    is( C4::Items::_mod_item_dates(), undef, 'Call without parameters' );
821
    is( C4::Items::_mod_item_dates(1), undef, 'Call without hashref' );
822
823
    my $orgitem;
824
    my $item = {
825
        itemcallnumber  => 'V II 149 1963',
826
        barcode         => '109304',
827
    };
828
    $orgitem = { %$item };
829
    C4::Items::_mod_item_dates($item);
830
    is_deeply( $item, $orgitem, 'No dates passed to _mod_item_dates' );
831
832
    # add two correct dates
833
    t::lib::Mocks::mock_preference('dateformat', 'us');
834
    $item->{dateaccessioned} = '01/31/2016';
835
    $item->{onloan} =  $item->{dateaccessioned};
836
    $orgitem = { %$item };
837
    C4::Items::_mod_item_dates($item);
838
    is( $item->{dateaccessioned}, '2016-01-31', 'dateaccessioned is fine' );
839
    is( $item->{onloan}, '2016-01-31', 'onloan is fine too' );
840
841
842
    # add some invalid dates
843
    $item->{notexistingcolumndate} = '13/1/2015'; # wrong format
844
    $item->{anotherdate} = 'tralala'; # even worse
845
    $item->{myzerodate} = '0000-00-00'; # wrong too
846
    C4::Items::_mod_item_dates($item);
847
    is( $item->{notexistingcolumndate}, undef, 'Invalid date became NULL' );
848
    is( $item->{anotherdate}, undef, 'Second invalid date became NULL too' );
849
    is( $item->{myzerodate}, undef, '0000-00-00 became NULL too' );
850
851
    # check if itemlost_on was not touched
852
    $item->{itemlost_on} = '12345678';
853
    $item->{withdrawn_on} = '12/31/2015 23:59:00';
854
    $item->{damaged_on} = '01/20/2017 09:00:00';
855
    $orgitem = { %$item };
856
    C4::Items::_mod_item_dates($item);
857
    is_deeply( $item, $orgitem, 'Colums with _on are not touched' );
858
859
    t::lib::Mocks::mock_preference('dateformat', 'metric');
860
    $item->{dateaccessioned} = '01/31/2016'; #wrong
861
    $item->{yetanotherdatetime} = '20/01/2016 13:58:00'; #okay
862
    C4::Items::_mod_item_dates($item);
863
    is( $item->{dateaccessioned}, undef, 'dateaccessioned wrong format' );
864
    is( $item->{yetanotherdatetime}, '2016-01-20 13:58:00',
865
        'yetanotherdatetime is ok' );
866
};
867
868
subtest 'get_hostitemnumbers_of' => sub {
736
subtest 'get_hostitemnumbers_of' => sub {
869
    plan tests => 3;
737
    plan tests => 3;
870
738
871
- 

Return to bug 23463