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

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

Return to bug 23463