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 1117-1317 the inner workings of C<C4::Items>. Link Here
1117
1038
1118
=cut
1039
=cut
1119
1040
1120
=head2 %derived_columns
1121
1122
This hash keeps track of item columns that
1123
are strictly derived from other columns in
1124
the item record and are not meant to be set
1125
independently.
1126
1127
Each key in the hash should be the name of a
1128
column (as named by TransformMarcToKoha).  Each
1129
value should be hashref whose keys are the
1130
columns on which the derived column depends.  The
1131
hashref should also contain a 'BUILDER' key
1132
that is a reference to a sub that calculates
1133
the derived value.
1134
1135
=cut
1136
1137
my %derived_columns = (
1138
    'items.cn_sort' => {
1139
        'itemcallnumber' => 1,
1140
        'items.cn_source' => 1,
1141
        'BUILDER' => \&_calc_items_cn_sort,
1142
    }
1143
);
1144
1145
=head2 _set_derived_columns_for_add 
1146
1147
  _set_derived_column_for_add($item);
1148
1149
Given an item hash representing a new item to be added,
1150
calculate any derived columns.  Currently the only
1151
such column is C<items.cn_sort>.
1152
1153
=cut
1154
1155
sub _set_derived_columns_for_add {
1156
    my $item = shift;
1157
1158
    foreach my $column (keys %derived_columns) {
1159
        my $builder = $derived_columns{$column}->{'BUILDER'};
1160
        my $source_values = {};
1161
        foreach my $source_column (keys %{ $derived_columns{$column} }) {
1162
            next if $source_column eq 'BUILDER';
1163
            $source_values->{$source_column} = $item->{$source_column};
1164
        }
1165
        $builder->($item, $source_values);
1166
    }
1167
}
1168
1169
=head2 _get_single_item_column
1170
1171
  _get_single_item_column($column, $itemnumber);
1172
1173
Retrieves the value of a single column from an C<items>
1174
row specified by C<$itemnumber>.
1175
1176
=cut
1177
1178
sub _get_single_item_column {
1179
    my $column = shift;
1180
    my $itemnumber = shift;
1181
    
1182
    my $dbh = C4::Context->dbh;
1183
    my $sth = $dbh->prepare("SELECT $column FROM items WHERE itemnumber = ?");
1184
    $sth->execute($itemnumber);
1185
    my ($value) = $sth->fetchrow();
1186
    return $value; 
1187
}
1188
1189
=head2 _calc_items_cn_sort
1190
1191
  _calc_items_cn_sort($item, $source_values);
1192
1193
Helper routine to calculate C<items.cn_sort>.
1194
1195
=cut
1196
1197
sub _calc_items_cn_sort {
1198
    my $item = shift;
1199
    my $source_values = shift;
1200
1201
    $item->{'items.cn_sort'} = GetClassSort($source_values->{'items.cn_source'}, $source_values->{'itemcallnumber'}, "");
1202
}
1203
1204
=head2 _koha_new_item
1205
1206
  my ($itemnumber,$error) = _koha_new_item( $item, $barcode );
1207
1208
Perform the actual insert into the C<items> table.
1209
1210
=cut
1211
1212
sub _koha_new_item {
1213
    my ( $item, $barcode ) = @_;
1214
    my $dbh=C4::Context->dbh;  
1215
    my $error;
1216
    $item->{permanent_location} //= $item->{location};
1217
    _mod_item_dates( $item );
1218
    my $query =
1219
           "INSERT INTO items SET
1220
            biblionumber        = ?,
1221
            biblioitemnumber    = ?,
1222
            barcode             = ?,
1223
            dateaccessioned     = ?,
1224
            booksellerid        = ?,
1225
            homebranch          = ?,
1226
            price               = ?,
1227
            replacementprice    = ?,
1228
            replacementpricedate = ?,
1229
            datelastborrowed    = ?,
1230
            datelastseen        = ?,
1231
            stack               = ?,
1232
            notforloan          = ?,
1233
            damaged             = ?,
1234
            itemlost            = ?,
1235
            withdrawn           = ?,
1236
            itemcallnumber      = ?,
1237
            coded_location_qualifier = ?,
1238
            restricted          = ?,
1239
            itemnotes           = ?,
1240
            itemnotes_nonpublic = ?,
1241
            holdingbranch       = ?,
1242
            location            = ?,
1243
            permanent_location  = ?,
1244
            onloan              = ?,
1245
            issues              = ?,
1246
            renewals            = ?,
1247
            reserves            = ?,
1248
            cn_source           = ?,
1249
            cn_sort             = ?,
1250
            ccode               = ?,
1251
            itype               = ?,
1252
            materials           = ?,
1253
            uri                 = ?,
1254
            enumchron           = ?,
1255
            more_subfields_xml  = ?,
1256
            copynumber          = ?,
1257
            stocknumber         = ?,
1258
            new_status          = ?
1259
          ";
1260
    my $sth = $dbh->prepare($query);
1261
    my $today = output_pref({ dt => dt_from_string, dateformat => 'iso', dateonly => 1 });
1262
   $sth->execute(
1263
            $item->{'biblionumber'},
1264
            $item->{'biblioitemnumber'},
1265
            $barcode,
1266
            $item->{'dateaccessioned'},
1267
            $item->{'booksellerid'},
1268
            $item->{'homebranch'},
1269
            $item->{'price'},
1270
            $item->{'replacementprice'},
1271
            $item->{'replacementpricedate'} || $today,
1272
            $item->{datelastborrowed},
1273
            $item->{datelastseen} || $today,
1274
            $item->{stack},
1275
            $item->{'notforloan'},
1276
            $item->{'damaged'},
1277
            $item->{'itemlost'},
1278
            $item->{'withdrawn'},
1279
            $item->{'itemcallnumber'},
1280
            $item->{'coded_location_qualifier'},
1281
            $item->{'restricted'},
1282
            $item->{'itemnotes'},
1283
            $item->{'itemnotes_nonpublic'},
1284
            $item->{'holdingbranch'},
1285
            $item->{'location'},
1286
            $item->{'permanent_location'},
1287
            $item->{'onloan'},
1288
            $item->{'issues'},
1289
            $item->{'renewals'},
1290
            $item->{'reserves'},
1291
            $item->{'items.cn_source'},
1292
            $item->{'items.cn_sort'},
1293
            $item->{'ccode'},
1294
            $item->{'itype'},
1295
            $item->{'materials'},
1296
            $item->{'uri'},
1297
            $item->{'enumchron'},
1298
            $item->{'more_subfields_xml'},
1299
            $item->{'copynumber'},
1300
            $item->{'stocknumber'},
1301
            $item->{'new_status'},
1302
    );
1303
1304
    my $itemnumber;
1305
    if ( defined $sth->errstr ) {
1306
        $error.="ERROR in _koha_new_item $query".$sth->errstr;
1307
    }
1308
    else {
1309
        $itemnumber = $dbh->{'mysql_insertid'};
1310
    }
1311
1312
    return ( $itemnumber, $error );
1313
}
1314
1315
=head2 MoveItemFromBiblio
1041
=head2 MoveItemFromBiblio
1316
1042
1317
  MoveItemFromBiblio($itenumber, $frombiblio, $tobiblio);
1043
  MoveItemFromBiblio($itenumber, $frombiblio, $tobiblio);
Lines 1362-1395 sub MoveItemFromBiblio { Link Here
1362
    return;
1088
    return;
1363
}
1089
}
1364
1090
1365
sub _mod_item_dates { # date formatting for date fields in item hash
1366
    my ( $item ) = @_;
1367
    return if !$item || ref($item) ne 'HASH';
1368
1369
    my @keys = grep
1370
        { $_ =~ /^onloan$|^date|date$|datetime$/ }
1371
        keys %$item;
1372
    # Incl. dateaccessioned,replacementpricedate,datelastborrowed,datelastseen
1373
    # NOTE: We do not (yet) have items fields ending with datetime
1374
    # Fields with _on$ have been handled already
1375
1376
    foreach my $key ( @keys ) {
1377
        next if !defined $item->{$key}; # skip undefs
1378
        my $dt = eval { dt_from_string( $item->{$key} ) };
1379
            # eval: dt_from_string will die on us if we pass illegal dates
1380
1381
        my $newstr;
1382
        if( defined $dt  && ref($dt) eq 'DateTime' ) {
1383
            if( $key =~ /datetime/ ) {
1384
                $newstr = DateTime::Format::MySQL->format_datetime($dt);
1385
            } else {
1386
                $newstr = DateTime::Format::MySQL->format_date($dt);
1387
            }
1388
        }
1389
        $item->{$key} = $newstr; # might be undef to clear garbage
1390
    }
1391
}
1392
1393
=head2 _marc_from_item_hash
1091
=head2 _marc_from_item_hash
1394
1092
1395
  my $item_marc = _marc_from_item_hash($item, $frameworkcode[, $unlinked_item_subfields]);
1093
  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 764-901 subtest 'C4::Biblio::EmbedItemsInMarcBiblio' => sub { Link Here
764
};
764
};
765
765
766
766
767
subtest 'C4::Items::_build_default_values_for_mod_marc' => sub {
768
    plan tests => 4;
769
770
    $schema->storage->txn_begin();
771
772
    my $builder = t::lib::TestBuilder->new;
773
    my $framework = $builder->build({ source => 'BiblioFramework' });
774
775
    # Link biblio.biblionumber and biblioitems.biblioitemnumber to avoid _koha_marc_update_bib_ids to fail with 'no biblio[item]number tag for framework"
776
    Koha::MarcSubfieldStructures->search({ frameworkcode => '', tagfield => '999', tagsubfield => [ 'c', 'd' ] })->delete;
777
    Koha::MarcSubfieldStructure->new({ frameworkcode => '', tagfield => '999', tagsubfield => 'c', kohafield => 'biblio.biblionumber' })->store;
778
    Koha::MarcSubfieldStructure->new({ frameworkcode => '', tagfield => '999', tagsubfield => 'd', kohafield => 'biblioitems.biblioitemnumber' })->store;
779
780
    # Same for item fields: itemnumber, barcode, itype
781
    Koha::MarcSubfieldStructures->search({ frameworkcode => '', tagfield => '952', tagsubfield => [ '9', 'p', 'y' ] })->delete;
782
    Koha::MarcSubfieldStructure->new({ frameworkcode => '', tagfield => '952', tagsubfield => '9', kohafield => 'items.itemnumber' })->store;
783
    Koha::MarcSubfieldStructure->new({ frameworkcode => '', tagfield => '952', tagsubfield => 'p', kohafield => 'items.barcode' })->store;
784
    Koha::MarcSubfieldStructure->new({ frameworkcode => '', tagfield => '952', tagsubfield => 'y', kohafield => 'items.itype' })->store;
785
    Koha::Caches->get_instance->clear_from_cache( "MarcSubfieldStructure-" );
786
787
    my $itemtype = $builder->build({ source => 'Itemtype' })->{itemtype};
788
789
    # Create a record with a barcode
790
    my $biblio = $builder->build_sample_biblio({ frameworkcode => $framework->{frameworkcode} });
791
    my $item_record = new MARC::Record;
792
    my $a_barcode = 'a barcode';
793
    my $barcode_field = MARC::Field->new(
794
        '952', ' ', ' ',
795
        p => $a_barcode,
796
        y => $itemtype
797
    );
798
    my $itemtype_field = MARC::Field->new(
799
        '952', ' ', ' ',
800
        y => $itemtype
801
    );
802
    $item_record->append_fields( $barcode_field );
803
    my (undef, undef, $item_itemnumber) = AddItemFromMarc($item_record, $biblio->biblionumber);
804
805
    # Make sure everything has been set up
806
    my $item = Koha::Items->find($item_itemnumber);
807
    is( $item->barcode, $a_barcode, 'Everything has been set up correctly, the barcode is defined as expected' );
808
809
    # Delete the barcode field and save the record
810
    $item_record->delete_fields( $barcode_field );
811
    $item_record->append_fields( $itemtype_field ); # itemtype is mandatory
812
    ModItemFromMarc($item_record, $biblio->biblionumber, $item_itemnumber);
813
    $item = Koha::Items->find($item_itemnumber);
814
    is( $item->barcode, undef, 'The default value should have been set to the barcode, the field is mapped to a kohafield' );
815
816
    # Re-add the barcode field and save the record
817
    $item_record->append_fields( $barcode_field );
818
    ModItemFromMarc($item_record, $biblio->biblionumber, $item_itemnumber);
819
    $item = Koha::Items->find($item_itemnumber);
820
    is( $item->barcode, $a_barcode, 'Everything has been set up correctly, the barcode is defined as expected' );
821
822
    # Remove the mapping for barcode
823
    Koha::MarcSubfieldStructures->search({ frameworkcode => '', tagfield => '952', tagsubfield => 'p' })->delete;
824
825
    # And make sure the caches are cleared
826
    my $cache = Koha::Caches->get_instance();
827
    $cache->clear_from_cache("default_value_for_mod_marc-");
828
    $cache->clear_from_cache("MarcSubfieldStructure-");
829
830
    # Update the MARC field with another value
831
    $item_record->delete_fields( $barcode_field );
832
    my $another_barcode = 'another_barcode';
833
    my $another_barcode_field = MARC::Field->new(
834
        '952', ' ', ' ',
835
        p => $another_barcode,
836
    );
837
    $item_record->append_fields( $another_barcode_field );
838
    # The DB value should not have been updated
839
    ModItemFromMarc($item_record, $biblio->biblionumber, $item_itemnumber);
840
    $item = Koha::Items->find($item_itemnumber);
841
    is ( $item->barcode, $a_barcode, 'items.barcode is not mapped anymore, so the DB column has not been updated' );
842
843
    $cache->clear_from_cache("default_value_for_mod_marc-");
844
    $cache->clear_from_cache( "MarcSubfieldStructure-" );
845
    $schema->storage->txn_rollback;
846
};
847
848
subtest '_mod_item_dates' => sub {
849
    plan tests => 11;
850
851
    is( C4::Items::_mod_item_dates(), undef, 'Call without parameters' );
852
    is( C4::Items::_mod_item_dates(1), undef, 'Call without hashref' );
853
854
    my $orgitem;
855
    my $item = {
856
        itemcallnumber  => 'V II 149 1963',
857
        barcode         => '109304',
858
    };
859
    $orgitem = { %$item };
860
    C4::Items::_mod_item_dates($item);
861
    is_deeply( $item, $orgitem, 'No dates passed to _mod_item_dates' );
862
863
    # add two correct dates
864
    t::lib::Mocks::mock_preference('dateformat', 'us');
865
    $item->{dateaccessioned} = '01/31/2016';
866
    $item->{onloan} =  $item->{dateaccessioned};
867
    $orgitem = { %$item };
868
    C4::Items::_mod_item_dates($item);
869
    is( $item->{dateaccessioned}, '2016-01-31', 'dateaccessioned is fine' );
870
    is( $item->{onloan}, '2016-01-31', 'onloan is fine too' );
871
872
873
    # add some invalid dates
874
    $item->{notexistingcolumndate} = '13/1/2015'; # wrong format
875
    $item->{anotherdate} = 'tralala'; # even worse
876
    $item->{myzerodate} = '0000-00-00'; # wrong too
877
    C4::Items::_mod_item_dates($item);
878
    is( $item->{notexistingcolumndate}, undef, 'Invalid date became NULL' );
879
    is( $item->{anotherdate}, undef, 'Second invalid date became NULL too' );
880
    is( $item->{myzerodate}, undef, '0000-00-00 became NULL too' );
881
882
    # check if itemlost_on was not touched
883
    $item->{itemlost_on} = '12345678';
884
    $item->{withdrawn_on} = '12/31/2015 23:59:00';
885
    $item->{damaged_on} = '01/20/2017 09:00:00';
886
    $orgitem = { %$item };
887
    C4::Items::_mod_item_dates($item);
888
    is_deeply( $item, $orgitem, 'Colums with _on are not touched' );
889
890
    t::lib::Mocks::mock_preference('dateformat', 'metric');
891
    $item->{dateaccessioned} = '01/31/2016'; #wrong
892
    $item->{yetanotherdatetime} = '20/01/2016 13:58:00'; #okay
893
    C4::Items::_mod_item_dates($item);
894
    is( $item->{dateaccessioned}, undef, 'dateaccessioned wrong format' );
895
    is( $item->{yetanotherdatetime}, '2016-01-20 13:58:00',
896
        'yetanotherdatetime is ok' );
897
};
898
899
subtest 'get_hostitemnumbers_of' => sub {
767
subtest 'get_hostitemnumbers_of' => sub {
900
    plan tests => 3;
768
    plan tests => 3;
901
769
902
- 

Return to bug 23463