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

(-)a/Koha/MarcOrder.pm (-1 / +944 lines)
Line 0 Link Here
0
- 
1
package Koha::MarcOrder;
2
3
# Copyright 2023, PTFS-Europe Ltd
4
#
5
# This file is part of Koha.
6
#
7
# Koha is free software; you can redistribute it and/or modify it
8
# under the terms of the GNU General Public License as published by
9
# the Free Software Foundation; either version 3 of the License, or
10
# (at your option) any later version.
11
#
12
# Koha is distributed in the hope that it will be useful, but
13
# WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU General Public License for more details.
16
#
17
# You should have received a copy of the GNU General Public License
18
# along with Koha; if not, see <http://www.gnu.org/licenses>.
19
20
use Modern::Perl;
21
use Try::Tiny qw( catch try );
22
23
use base qw(Koha::Object);
24
25
use C4::Matcher;
26
use C4::ImportBatch qw(
27
    RecordsFromMARCXMLFile
28
    RecordsFromISO2709File
29
    RecordsFromMarcPlugin
30
    BatchStageMarcRecords
31
    BatchFindDuplicates
32
    SetImportBatchMatcher
33
    SetImportBatchOverlayAction
34
    SetImportBatchNoMatchAction
35
    SetImportBatchItemAction
36
    SetImportBatchStatus
37
    GetImportBatchOverlayAction
38
    GetImportBatchNoMatchAction
39
    GetImportBatchItemAction
40
    GetImportBatch
41
);
42
use C4::Search      qw( FindDuplicate );
43
use C4::Acquisition qw( NewBasket );
44
use C4::Biblio      qw(
45
    AddBiblio
46
    GetMarcFromKohaField
47
    TransformHtmlToXml
48
    GetMarcQuantity
49
);
50
use C4::Items   qw( AddItemFromMarc );
51
use C4::Budgets qw( GetBudgetByCode );
52
53
use Koha::Database;
54
use Koha::ImportBatchProfiles;
55
use Koha::ImportBatches;
56
use Koha::Import::Records;
57
use Koha::Acquisition::Currencies;
58
use Koha::Acquisition::Booksellers;
59
use Koha::Acquisition::Baskets;
60
61
=head1 NAME
62
63
Koha::MarcOrder - Koha Marc Order Object class
64
65
=head1 API
66
67
=head2 Class methods
68
69
=cut
70
71
=head3 import_record_and_create_order_lines
72
73
    my $result = Koha::MarcOrder->import_record_and_create_order_lines($args);
74
75
    Controller for record import and order line creation when using the interface in addorderiso2709.pl
76
77
=cut
78
79
sub import_record_and_create_order_lines {
80
    my ( $self, $args ) = @_;
81
82
    my $import_batch_id           = $args->{import_batch_id};
83
    my $import_record_id_selected = $args->{import_record_id_selected} || ();
84
    my $matcher_id                = $args->{matcher_id};
85
    my $overlay_action            = $args->{overlay_action};
86
    my $import_record             = $args->{import_record};
87
    my $client_item_fields        = $args->{client_item_fields};
88
    my $agent                     = $args->{agent};
89
    my $basket_id                 = $args->{basket_id};
90
    my $budget_id                 = $args->{budget_id};
91
    my $vendor                    = $args->{vendor};
92
93
    my $result = add_biblios_from_import_record(
94
        {
95
            import_batch_id           => $import_batch_id,
96
            import_record             => $import_record,
97
            matcher_id                => $matcher_id,
98
            overlay_action            => $overlay_action,
99
            agent                     => $agent,
100
            import_record_id_selected => $import_record_id_selected,
101
        }
102
    );
103
104
    return {
105
        duplicates_in_batch => $result->{duplicates_in_batch},
106
        skip                => $result->{skip}
107
    } if $result->{skip};
108
109
    my $order_line_details = add_items_from_import_record(
110
        {
111
            record_result      => $result->{record_result},
112
            basket_id          => $basket_id,
113
            vendor             => $vendor,
114
            budget_id          => $budget_id,
115
            agent              => $agent,
116
            client_item_fields => $client_item_fields
117
        }
118
    );
119
120
    my $order_lines = create_order_lines( { order_line_details => $order_line_details } );
121
122
    return {
123
        duplicates_in_batch => 0,
124
        skip                => 0
125
    };
126
}
127
128
=head3 _get_MarcFieldsToOrder_syspref_data
129
130
    my $marc_fields_to_order = _get_MarcFieldsToOrder_syspref_data('MarcFieldsToOrder', $marcrecord, $fields);
131
132
    Fetches data from a marc record based on the mappings in the syspref MarcFieldsToOrder using the fields selected in $fields (array).
133
134
=cut
135
136
sub _get_MarcFieldsToOrder_syspref_data {
137
    my ( $syspref_name, $record, $field_list ) = @_;
138
    my $syspref = C4::Context->preference($syspref_name);
139
    $syspref = "$syspref\n\n";
140
    my $yaml = eval { YAML::XS::Load( Encode::encode_utf8($syspref) ); };
141
    if ($@) {
142
        warn "Unable to parse $syspref syspref : $@";
143
        return ();
144
    }
145
    my $r;
146
    for my $field_name (@$field_list) {
147
        next unless exists $yaml->{$field_name};
148
        my @fields = split /\|/, $yaml->{$field_name};
149
        for my $field (@fields) {
150
            my ( $f, $sf ) = split /\$/, $field;
151
            next unless $f and $sf;
152
            if ( my $v = $record->subfield( $f, $sf ) ) {
153
                $r->{$field_name} = $v;
154
            }
155
            last if $yaml->{$field};
156
        }
157
    }
158
    return $r;
159
}
160
161
=head3 _get_MarcItemFieldsToOrder_syspref_data
162
163
    my $marc_item_fields_to_order = _get_MarcItemFieldsToOrder_syspref_data('MarcItemFieldsToOrder', $marcrecord, $fields);
164
165
    Fetches data from a marc record based on the mappings in the syspref MarcItemFieldsToOrder using the fields selected in $fields (array).
166
167
=cut
168
169
sub _get_MarcItemFieldsToOrder_syspref_data {
170
    my ( $syspref_name, $record, $field_list ) = @_;
171
    my $syspref = C4::Context->preference($syspref_name);
172
    $syspref = "$syspref\n\n";
173
    my $yaml = eval { YAML::XS::Load( Encode::encode_utf8($syspref) ); };
174
    if ($@) {
175
        warn "Unable to parse $syspref syspref : $@";
176
        return ();
177
    }
178
    my @result;
179
    my @tags_list;
180
181
    # Check tags in syspref definition
182
    for my $field_name (@$field_list) {
183
        next unless exists $yaml->{$field_name};
184
        my @fields = split /\|/, $yaml->{$field_name};
185
        for my $field (@fields) {
186
            my ( $f, $sf ) = split /\$/, $field;
187
            next unless $f and $sf;
188
            push @tags_list, $f;
189
        }
190
    }
191
    @tags_list = List::MoreUtils::uniq(@tags_list);
192
193
    my $tags_count = _verify_number_of_fields( \@tags_list, $record );
194
195
    # Return if the number of these fields in the record is not the same.
196
    die "Invalid number of fields detected on field $tags_count->{key}, please check this file" if $tags_count->{error};
197
198
    # Gather the fields
199
    my $fields_hash;
200
    foreach my $tag (@tags_list) {
201
        my @tmp_fields;
202
        foreach my $field ( $record->field($tag) ) {
203
            push @tmp_fields, $field;
204
        }
205
        $fields_hash->{$tag} = \@tmp_fields;
206
    }
207
208
    if ( $tags_count->{count} ) {
209
        for ( my $i = 0 ; $i < $tags_count->{count} ; $i++ ) {
210
            my $r;
211
            for my $field_name (@$field_list) {
212
                next unless exists $yaml->{$field_name};
213
                my @fields = split /\|/, $yaml->{$field_name};
214
                for my $field (@fields) {
215
                    my ( $f, $sf ) = split /\$/, $field;
216
                    next unless $f and $sf;
217
                    my $v = $fields_hash->{$f}[$i] ? $fields_hash->{$f}[$i]->subfield($sf) : undef;
218
                    $r->{$field_name} = $v if ( defined $v );
219
                    last if $yaml->{$field};
220
                }
221
            }
222
            push @result, $r;
223
        }
224
    }
225
226
    return $result[0];
227
}
228
229
=head3 _verify_number_of_fields
230
231
    my $tags_count = _verify_number_of_fields(\@tags_list, $record);
232
233
    Verifies that the number of fields in the record is consistent for each field
234
235
=cut
236
237
sub _verify_number_of_fields {
238
    my ( $tags_list, $record ) = @_;
239
    my $tag_fields_count;
240
    for my $tag (@$tags_list) {
241
        my @fields = $record->field($tag);
242
        $tag_fields_count->{$tag} = scalar @fields;
243
    }
244
245
    my $tags_count;
246
    foreach my $key ( keys %$tag_fields_count ) {
247
        if ( $tag_fields_count->{$key} > 0 ) {    # Having 0 of a field is ok
248
            $tags_count //= $tag_fields_count->{$key};    # Start with the count from the first occurrence
249
            return { error => 1, key => $key }
250
                if $tag_fields_count->{$key} !=
251
                $tags_count;                              # All counts of various fields should be equal if they exist
252
        }
253
    }
254
    return { error => 0, count => $tags_count };
255
}
256
257
=head3 add_biblios_from_import_record
258
259
    my ($record_results, $duplicates_in_batch) = add_biblios_from_import_record({
260
        import_record             => $import_record,
261
        matcher_id                => $matcher_id,
262
        overlay_action            => $overlay_action,
263
        import_record_id_selected => $import_record_id_selected,
264
        agent                     => $agent,
265
        import_batch_id           => $import_batch_id
266
    });
267
268
    Takes a set of import records and adds biblio records based on the file content.
269
    Params matcher_id and overlay_action are taken from the marc ordering account.
270
    Returns the new or matched biblionumber and the marc record for each import record.
271
272
=cut
273
274
sub add_biblios_from_import_record {
275
    my ($args) = @_;
276
277
    my $import_batch_id           = $args->{import_batch_id};
278
    my $import_record_id_selected = $args->{import_record_id_selected} || ();
279
    my $matcher_id                = $args->{matcher_id};
280
    my $overlay_action            = $args->{overlay_action};
281
    my $import_record             = $args->{import_record};
282
    my $agent                     = $args->{agent} || "";
283
    my $duplicates_in_batch;
284
285
    my $duplicates_found = 0;
286
    if ( $agent eq 'client' ) {
287
        return {
288
            record_result       => 0,
289
            duplicates_in_batch => 0,
290
            skip                => 1
291
        } if not grep { $_ eq $import_record->import_record_id } @{$import_record_id_selected};
292
    }
293
294
    my $marcrecord   = $import_record->get_marc_record || die "Couldn't translate marc information";
295
    my $matches      = $import_record->get_import_record_matches( { chosen => 1 } );
296
    my $match        = $matches->count ? $matches->next             : undef;
297
    my $biblionumber = $match          ? $match->candidate_match_id : 0;
298
299
    if ($biblionumber) {
300
        $import_record->status('imported')->store;
301
        if ( $overlay_action eq 'replace' ) {
302
            my $biblio = Koha::Biblios->find($biblionumber);
303
            $import_record->replace( { biblio => $biblio } );
304
        }
305
    } else {
306
        if ($matcher_id) {
307
            if ( $matcher_id eq '_TITLE_AUTHOR_' ) {
308
                my @matches = FindDuplicate($marcrecord);
309
                $duplicates_found = 1 if @matches;
310
            } else {
311
                my $matcher = C4::Matcher->fetch($matcher_id);
312
                my @matches = $matcher->get_matches( $marcrecord, my $max_matches = 1 );
313
                $duplicates_found = 1 if @matches;
314
            }
315
            return {
316
                record_result       => 0,
317
                duplicates_in_batch => $import_batch_id,
318
                skip                => 1
319
            } if $duplicates_found;
320
        }
321
322
        # add the biblio if no matches were found
323
        if ( !$duplicates_found ) {
324
            ( $biblionumber, undef ) = AddBiblio( $marcrecord, '' );
325
            $import_record->status('imported')->store;
326
        }
327
    }
328
    $import_record->import_biblio->matched_biblionumber($biblionumber)->store;
329
330
    my $record_result = {
331
        biblionumber     => $biblionumber,
332
        marcrecord       => $marcrecord,
333
        import_record_id => $import_record->import_record_id,
334
    };
335
336
    return {
337
        record_result       => $record_result,
338
        duplicates_in_batch => $duplicates_in_batch,
339
        skip                => 0
340
    };
341
}
342
343
=head3 add_items_from_import_record
344
345
    my $order_line_details = add_items_from_import_record({
346
        record_result      => $record_result,
347
        basket_id          => $basket_id,
348
        vendor             => $vendor,
349
        budget_id          => $budget_id,
350
        agent              => $agent,
351
        client_item_fields => $client_item_fields
352
    });
353
354
    Adds items to biblio records based on mappings in MarcItemFieldsToOrder.
355
    Returns an array of order line details based on newly added items.
356
    If being called from addorderiso2709.pl then client_item_fields is a hash of all the UI form inputs needed by the script.
357
358
=cut
359
360
sub add_items_from_import_record {
361
    my ($args) = @_;
362
363
    my $record_result      = $args->{record_result};
364
    my $basket_id          = $args->{basket_id};
365
    my $budget_id          = $args->{budget_id};
366
    my $vendor             = $args->{vendor};
367
    my $agent              = $args->{agent};
368
    my $client_item_fields = $args->{client_item_fields} || undef;
369
    my $active_currency    = Koha::Acquisition::Currencies->get_active;
370
    my $biblionumber       = $record_result->{biblionumber};
371
    my $marcrecord         = $record_result->{marcrecord};
372
    my @order_line_details;
373
374
    if ( $agent eq 'cron' ) {
375
        my $marc_fields_to_order = _get_MarcFieldsToOrder_syspref_data(
376
            'MarcFieldsToOrder', $marcrecord,
377
            [ 'price', 'quantity', 'budget_code', 'discount', 'sort1', 'sort2' ]
378
        );
379
        my $quantity = $marc_fields_to_order->{quantity};
380
        my $budget_code =
381
            $marc_fields_to_order->{budget_code} || $budget_id;    # Use fallback from ordering profile if not mapped
382
        my $price    = $marc_fields_to_order->{price};
383
        my $discount = $marc_fields_to_order->{discount};
384
        my $sort1    = $marc_fields_to_order->{sort1};
385
        my $sort2    = $marc_fields_to_order->{sort2};
386
        my $mapped_budget;
387
388
        if ($budget_code) {
389
            my $biblio_budget = GetBudgetByCode($budget_code);
390
            if ($biblio_budget) {
391
                $mapped_budget = $biblio_budget->{budget_id};
392
            } else {
393
                $mapped_budget = $budget_id;
394
            }
395
        }
396
397
        my $marc_item_fields_to_order = _get_MarcItemFieldsToOrder_syspref_data(
398
            'MarcItemFieldsToOrder',
399
            $marcrecord,
400
            [
401
                'homebranch', 'holdingbranch', 'itype', 'nonpublic_note',   'public_note', 'loc', 'ccode', 'notforloan',
402
                'uri',        'copyno',        'price', 'replacementprice', 'itemcallnumber', 'quantity', 'budget_code'
403
            ]
404
        );
405
        my $item_homebranch     = $marc_item_fields_to_order->{homebranch};
406
        my $item_holdingbranch  = $marc_item_fields_to_order->{holdingbranch};
407
        my $item_itype          = $marc_item_fields_to_order->{itype};
408
        my $item_nonpublic_note = $marc_item_fields_to_order->{nonpublic_note};
409
        my $item_public_note    = $marc_item_fields_to_order->{public_note};
410
        my $item_loc            = $marc_item_fields_to_order->{loc};
411
        my $item_ccode          = $marc_item_fields_to_order->{ccode};
412
        my $item_notforloan     = $marc_item_fields_to_order->{notforloan};
413
        my $item_uri            = $marc_item_fields_to_order->{uri};
414
        my $item_copyno         = $marc_item_fields_to_order->{copyno};
415
        my $item_quantity       = $marc_item_fields_to_order->{quantity} || 0;
416
        my $item_budget_code    = $marc_item_fields_to_order->{budget_code};
417
        my $item_budget_id;
418
419
        if ( $marc_item_fields_to_order->{budget_code} ) {
420
            my $item_budget = GetBudgetByCode( $marc_item_fields_to_order->{budget_code} );
421
            if ($item_budget) {
422
                $item_budget_id = $item_budget->{budget_id};
423
            } else {
424
                $item_budget_id = $budget_id;
425
            }
426
        } else {
427
            $item_budget_id = $budget_id;
428
        }
429
        my $item_price             = $marc_item_fields_to_order->{price};
430
        my $item_replacement_price = $marc_item_fields_to_order->{replacementprice};
431
        my $item_callnumber        = $marc_item_fields_to_order->{itemcallnumber};
432
        my $itemcreation           = 0;
433
434
        for ( my $i = 0 ; $i < $item_quantity ; $i++ ) {
435
            $itemcreation = 1;
436
            my $item = Koha::Item->new(
437
                {
438
                    biblionumber        => $biblionumber,
439
                    homebranch          => $item_homebranch,
440
                    holdingbranch       => $item_holdingbranch,
441
                    itype               => $item_itype,
442
                    itemnotes_nonpublic => $item_nonpublic_note,
443
                    itemnotes           => $item_public_note,
444
                    location            => $item_loc,
445
                    ccode               => $item_ccode,
446
                    notforloan          => $item_notforloan,
447
                    uri                 => $item_uri,
448
                    copynumber          => $item_copyno,
449
                    price               => $item_price,
450
                    replacementprice    => $item_replacement_price,
451
                    itemcallnumber      => $item_callnumber,
452
                }
453
            )->store;
454
455
            my %order_detail_hash = (
456
                biblionumber => $biblionumber,
457
                basketno     => $basket_id,
458
                itemnumbers  => ( $item->itemnumber ),
459
                quantity     => 1,
460
                budget_id    => $item_budget_id,
461
                currency     => $vendor->listprice,
462
            );
463
464
            if ($item_price) {
465
                $order_detail_hash{tax_rate_on_ordering}  = $vendor->tax_rate;
466
                $order_detail_hash{tax_rate_on_receiving} = $vendor->tax_rate;
467
                $order_detail_hash{discount}              = $vendor->discount;
468
                $order_detail_hash{rrp}                   = $item_price;
469
                $order_detail_hash{ecost} =
470
                    $vendor->discount ? $item_price * ( 1 - $vendor->discount / 100 ) : $item_price;
471
                $order_detail_hash{listprice} = $order_detail_hash{rrp} / $active_currency->rate;
472
                $order_detail_hash{unitprice} = $order_detail_hash{ecost};
473
            } else {
474
                $order_detail_hash{listprice} = 0;
475
            }
476
            $order_detail_hash{replacementprice} = $item_replacement_price || 0;
477
            $order_detail_hash{uncertainprice}   = 0 if $order_detail_hash{listprice};
478
479
            push @order_line_details, \%order_detail_hash;
480
        }
481
482
        if ( !$itemcreation ) {
483
            my %order_detail_hash = (
484
                biblionumber   => $biblionumber,
485
                basketno       => $basket_id,
486
                quantity       => $quantity,
487
                budget_id      => $mapped_budget,
488
                uncertainprice => 1,
489
                sort1          => $sort1,
490
                sort2          => $sort2,
491
            );
492
493
            if ($price) {
494
                $order_detail_hash{tax_rate_on_ordering}  = $vendor->tax_rate;
495
                $order_detail_hash{tax_rate_on_receiving} = $vendor->tax_rate;
496
                my $order_discount = $discount ? $discount : $vendor->discount;
497
                $order_detail_hash{discount}  = $order_discount;
498
                $order_detail_hash{rrp}       = $price;
499
                $order_detail_hash{ecost}     = $order_discount ? $price * ( 1 - $order_discount / 100 ) : $price;
500
                $order_detail_hash{listprice} = $order_detail_hash{rrp} / $active_currency->rate;
501
                $order_detail_hash{unitprice} = $order_detail_hash{ecost};
502
            } else {
503
                $order_detail_hash{listprice} = 0;
504
            }
505
506
            $order_detail_hash{uncertainprice} = 0 if $order_detail_hash{listprice};
507
            push @order_line_details, \%order_detail_hash;
508
        }
509
    }
510
511
    if ( $agent eq 'client' ) {
512
        my $homebranches      = $client_item_fields->{homebranches};
513
        my $count             = scalar @$homebranches;
514
        my $holdingbranches   = $client_item_fields->{holdingbranches};
515
        my $itypes            = $client_item_fields->{itypes};
516
        my $nonpublic_notes   = $client_item_fields->{nonpublic_notes};
517
        my $public_notes      = $client_item_fields->{public_notes};
518
        my $locs              = $client_item_fields->{locs};
519
        my $ccodes            = $client_item_fields->{ccodes};
520
        my $notforloans       = $client_item_fields->{notforloans};
521
        my $uris              = $client_item_fields->{uris};
522
        my $copynos           = $client_item_fields->{copynos};
523
        my $budget_codes      = $client_item_fields->{budget_codes};
524
        my $itemprices        = $client_item_fields->{itemprices};
525
        my $replacementprices = $client_item_fields->{replacementprices};
526
        my $itemcallnumbers   = $client_item_fields->{itemcallnumbers};
527
528
        my $itemcreation;
529
        for ( my $i = 0 ; $i < $count ; $i++ ) {
530
            $itemcreation = 1;
531
            my $item = Koha::Item->new(
532
                {
533
                    biblionumber        => $biblionumber,
534
                    homebranch          => @$homebranches[$i],
535
                    holdingbranch       => @$holdingbranches[$i],
536
                    itemnotes_nonpublic => @$nonpublic_notes[$i],
537
                    itemnotes           => @$public_notes[$i],
538
                    location            => @$locs[$i],
539
                    ccode               => @$ccodes[$i],
540
                    itype               => @$itypes[$i],
541
                    notforloan          => @$notforloans[$i],
542
                    uri                 => @$uris[$i],
543
                    copynumber          => @$copynos[$i],
544
                    price               => @$itemprices[$i],
545
                    replacementprice    => @$replacementprices[$i],
546
                    itemcallnumber      => @$itemcallnumbers[$i],
547
                }
548
            )->store;
549
550
            my %order_detail_hash = (
551
                biblionumber => $biblionumber,
552
                itemnumbers  => ( $item->itemnumber ),
553
                basketno     => $basket_id,
554
                quantity     => 1,
555
                budget_id    => @$budget_codes[$i]
556
                    || $budget_id,    # If no budget selected in the UI, default to the budget on the ordering account
557
                currency => $vendor->listprice,
558
            );
559
560
            if ( @$itemprices[$i] ) {
561
                $order_detail_hash{tax_rate_on_ordering}  = $vendor->tax_rate;
562
                $order_detail_hash{tax_rate_on_receiving} = $vendor->tax_rate;
563
                my $order_discount =
564
                    $client_item_fields->{c_discount} ? $client_item_fields->{c_discount} : $vendor->discount;
565
                $order_detail_hash{discount} = $order_discount;
566
                $order_detail_hash{rrp}      = @$itemprices[$i];
567
                $order_detail_hash{ecost} =
568
                    $order_discount ? @$itemprices[$i] * ( 1 - $order_discount / 100 ) : @$itemprices[$i];
569
                $order_detail_hash{listprice} = $order_detail_hash{rrp} / $active_currency->rate;
570
                $order_detail_hash{unitprice} = $order_detail_hash{ecost};
571
            } else {
572
                $order_detail_hash{listprice} = 0;
573
            }
574
            $order_detail_hash{replacementprice} = @$replacementprices[$i] || 0;
575
            $order_detail_hash{uncertainprice}   = 0 if $order_detail_hash{listprice};
576
577
            push @order_line_details, \%order_detail_hash;
578
        }
579
580
        if ( !$itemcreation ) {
581
            my $quantity          = GetMarcQuantity( $marcrecord, C4::Context->preference('marcflavour') ) || 1;
582
            my %order_detail_hash = (
583
                biblionumber       => $biblionumber,
584
                basketno           => $basket_id,
585
                quantity           => $client_item_fields->{c_quantity},
586
                budget_id          => $client_item_fields->{c_budget_id},
587
                uncertainprice     => 1,
588
                sort1              => $client_item_fields->{c_sort1},
589
                sort2              => $client_item_fields->{c_sort2},
590
                order_internalnote => $client_item_fields->{all_order_internalnote},
591
                order_vendornote   => $client_item_fields->{all_order_vendornote},
592
                currency           => $client_item_fields->{all_currency},
593
                replacementprice   => $client_item_fields->{c_replacement_price},
594
            );
595
            if ( $client_item_fields->{c_price} ) {
596
                $order_detail_hash{tax_rate_on_ordering}  = $vendor->tax_rate;
597
                $order_detail_hash{tax_rate_on_receiving} = $vendor->tax_rate;
598
                my $order_discount =
599
                    $client_item_fields->{c_discount} ? $client_item_fields->{c_discount} : $vendor->discount;
600
                $order_detail_hash{discount} = $order_discount;
601
                $order_detail_hash{rrp}      = $client_item_fields->{c_price};
602
                $order_detail_hash{ecost} =
603
                      $order_discount
604
                    ? $client_item_fields->{c_price} * ( 1 - $order_discount / 100 )
605
                    : $client_item_fields->{c_price};
606
                $order_detail_hash{listprice} = $order_detail_hash{rrp} / $active_currency->rate;
607
                $order_detail_hash{unitprice} = $order_detail_hash{ecost};
608
            } else {
609
                $order_detail_hash{listprice} = 0;
610
            }
611
612
            $order_detail_hash{uncertainprice} = 0 if $order_detail_hash{listprice};
613
614
            # Add items if applicable parsing the item sent by the form, and create an item just for the import_record_id we are dealing with
615
            my $basket = Koha::Acquisition::Baskets->find($basket_id);
616
            $order_detail_hash{itemnumbers} = ();
617
            if ( $basket->effective_create_items eq 'ordering' && !$basket->is_standing ) {
618
                my @tags         = $client_item_fields->{tag};
619
                my @subfields    = $client_item_fields->{subfield};
620
                my @field_values = $client_item_fields->{field_value};
621
                my @serials      = $client_item_fields->{serial};
622
                my $xml          = TransformHtmlToXml( \@tags, \@subfields, \@field_values );
623
                my $record       = MARC::Record::new_from_xml( $xml, 'UTF-8' );
624
                for ( my $qtyloop = 1 ; $qtyloop <= $client_item_fields->{c_quantity} ; $qtyloop++ ) {
625
                    my ( $biblionumber, undef, $itemnumber ) = AddItemFromMarc( $record, $biblionumber );
626
                    push @{ $order_detail_hash{itemnumbers} }, $itemnumber;
627
                }
628
            }
629
            push @order_line_details, \%order_detail_hash;
630
        }
631
    }
632
    return \@order_line_details;
633
}
634
635
=head3 create_order_lines
636
637
    my $order_lines = create_order_lines({
638
        order_line_details => $order_line_details
639
    });
640
641
    Creates order lines based on an array of order line details
642
643
=cut
644
645
sub create_order_lines {
646
    my ($args) = @_;
647
648
    my $order_line_details = $args->{order_line_details};
649
650
    foreach my $order_detail ( @{$order_line_details} ) {
651
        my @itemnumbers = $order_detail->{itemnumbers} || ();
652
        delete( $order_detail->{itemnumbers} );
653
        my $order = Koha::Acquisition::Order->new( \%{$order_detail} );
654
        $order->populate_with_prices_for_ordering();
655
        $order->populate_with_prices_for_receiving();
656
        $order->store;
657
        foreach my $itemnumber (@itemnumbers) {
658
            $order->add_item($itemnumber);
659
        }
660
    }
661
    return;
662
}
663
664
sub import_batches_list {
665
    my ( $self, $template ) = @_;
666
    my $batches = GetImportBatchRangeDesc();
667
668
    my @list = ();
669
    foreach my $batch (@$batches) {
670
        if ( $batch->{'import_status'} =~ /^staged$|^reverted$/ && $batch->{'record_type'} eq 'biblio' ) {
671
672
            # check if there is at least 1 line still staged
673
            my $import_records_count = Koha::Import::Records->search(
674
                {
675
                    import_batch_id => $batch->{'import_batch_id'},
676
                    status          => $batch->{import_status}
677
                }
678
            )->count;
679
            if ($import_records_count) {
680
                push @list, {
681
                    import_batch_id => $batch->{'import_batch_id'},
682
                    num_records     => $batch->{'num_records'},
683
                    num_items       => $batch->{'num_items'},
684
                    staged_date     => $batch->{'upload_timestamp'},
685
                    import_status   => $batch->{'import_status'},
686
                    file_name       => $batch->{'file_name'},
687
                    comments        => $batch->{'comments'},
688
                };
689
            } else {
690
691
                # if there are no more line to includes, set the status to imported
692
                # FIXME This should be removed in the future.
693
                SetImportBatchStatus( $batch->{'import_batch_id'}, 'imported' );
694
            }
695
        }
696
    }
697
    $template->param( batch_list => \@list );
698
    my $num_batches = GetNumberOfNonZ3950ImportBatches();
699
    $template->param( num_results => $num_batches );
700
}
701
702
sub import_biblios_list {
703
    my ( $self, $template, $import_batch_id ) = @_;
704
    my $batch = GetImportBatch( $import_batch_id, 'staged' );
705
    return () unless $batch and $batch->{import_status} =~ /^staged$|^reverted$/;
706
    my $import_records = Koha::Import::Records->search(
707
        {
708
            import_batch_id => $import_batch_id,
709
            status          => $batch->{import_status}
710
        }
711
    );
712
    my @list       = ();
713
    my $item_error = 0;
714
715
    my $ccodes = {
716
        map { $_->{authorised_value} => $_->{opac_description} }
717
            Koha::AuthorisedValues->get_descriptions_by_koha_field(
718
            { frameworkcode => '', kohafield => 'items.ccode' }
719
            )
720
    };
721
    my $locations = {
722
        map { $_->{authorised_value} => $_->{opac_description} }
723
            Koha::AuthorisedValues->get_descriptions_by_koha_field(
724
            { frameworkcode => '', kohafield => 'items.location' }
725
            )
726
    };
727
    my $notforloans = {
728
        map { $_->{authorised_value} => $_->{lib} } Koha::AuthorisedValues->get_descriptions_by_koha_field(
729
            { frameworkcode => '', kohafield => 'items.notforloan' }
730
        )
731
    };
732
733
    # location list
734
    my @locations;
735
    foreach ( sort keys %$locations ) {
736
        push @locations, { code => $_, description => "$_ - " . $locations->{$_} };
737
    }
738
    my @ccodes;
739
    foreach ( sort { $ccodes->{$a} cmp $ccodes->{$b} } keys %$ccodes ) {
740
        push @ccodes, { code => $_, description => $ccodes->{$_} };
741
    }
742
    my @notforloans;
743
    foreach ( sort { $notforloans->{$a} cmp $notforloans->{$b} } keys %$notforloans ) {
744
        push @notforloans, { code => $_, description => $notforloans->{$_} };
745
    }
746
747
    my $biblio_count = 0;
748
    while ( my $import_record = $import_records->next ) {
749
        my $item_id = 1;
750
        $biblio_count++;
751
        my $matches      = $import_record->get_import_record_matches( { chosen => 1 } );
752
        my $match        = $matches->count ? $matches->next                                               : undef;
753
        my $match_biblio = $match ? Koha::Biblios->find( { biblionumber => $match->candidate_match_id } ) : undef;
754
        my %cellrecord   = (
755
            import_record_id   => $import_record->import_record_id,
756
            import_biblio      => $import_record->import_biblio,
757
            import             => 1,
758
            status             => $import_record->status,
759
            record_sequence    => $import_record->record_sequence,
760
            overlay_status     => $import_record->overlay_status,
761
            match_biblionumber => $match ? $match->candidate_match_id : 0,
762
            match_citation     => $match_biblio
763
            ? ( $match_biblio->title || '' ) . ' ' . ( $match_biblio->author || '' )
764
            : '',
765
            match_score => $match ? $match->score : 0,
766
        );
767
        my $marcrecord = $import_record->get_marc_record || die "couldn't translate marc information";
768
769
        my $infos = _get_MarcFieldsToOrder_syspref_data(
770
            'MarcFieldsToOrder', $marcrecord,
771
            [ 'price', 'quantity', 'budget_code', 'discount', 'sort1', 'sort2', 'replacementprice' ]
772
        );
773
        my $price            = $infos->{price};
774
        my $replacementprice = $infos->{replacementprice};
775
        my $quantity         = $infos->{quantity};
776
        my $budget_code      = $infos->{budget_code};
777
        my $discount         = $infos->{discount};
778
        my $sort1            = $infos->{sort1};
779
        my $sort2            = $infos->{sort2};
780
        my $budget_id;
781
782
        if ($budget_code) {
783
            my $biblio_budget = GetBudgetByCode($budget_code);
784
            if ($biblio_budget) {
785
                $budget_id = $biblio_budget->{budget_id};
786
            }
787
        }
788
789
        # Items
790
        my @itemlist           = ();
791
        my $all_items_quantity = 0;
792
        my $alliteminfos       = _get_MarcItemFieldsToOrder_syspref_data(
793
            'MarcItemFieldsToOrder',
794
            $marcrecord,
795
            [
796
                'homebranch', 'holdingbranch', 'itype', 'nonpublic_note',   'public_note', 'loc', 'ccode', 'notforloan',
797
                'uri',        'copyno',        'price', 'replacementprice', 'itemcallnumber', 'quantity', 'budget_code'
798
            ]
799
        );
800
        if ( %$alliteminfos != -1 ) {
801
            my $item_homebranch     = $alliteminfos->{homebranch};
802
            my $item_holdingbranch  = $alliteminfos->{holdingbranch};
803
            my $item_itype          = $alliteminfos->{itype};
804
            my $item_nonpublic_note = $alliteminfos->{nonpublic_note};
805
            my $item_public_note    = $alliteminfos->{public_note};
806
            my $item_loc            = $alliteminfos->{loc};
807
            my $item_ccode          = $alliteminfos->{ccode};
808
            my $item_notforloan     = $alliteminfos->{notforloan};
809
            my $item_uri            = $alliteminfos->{uri};
810
            my $item_copyno         = $alliteminfos->{copyno};
811
            my $item_quantity       = $alliteminfos->{quantity} || 1;
812
            my $item_budget_code    = $alliteminfos->{budget_code};
813
            my $item_budget_id;
814
815
            if ( $alliteminfos->{budget_code} ) {
816
                my $item_budget = GetBudgetByCode( $alliteminfos->{budget_code} );
817
                if ($item_budget) {
818
                    $item_budget_id = $item_budget->{budget_id};
819
                }
820
            }
821
            my $item_price             = $alliteminfos->{price};
822
            my $item_replacement_price = $alliteminfos->{replacementprice};
823
            my $item_callnumber        = $alliteminfos->{itemcallnumber};
824
825
            for ( my $i = 0 ; $i < $item_quantity ; $i++ ) {
826
827
                my %itemrecord = (
828
                    'item_id'          => $item_id++,
829
                    'biblio_count'     => $biblio_count,
830
                    'homebranch'       => $item_homebranch,
831
                    'holdingbranch'    => $item_holdingbranch,
832
                    'itype'            => $item_itype,
833
                    'nonpublic_note'   => $item_nonpublic_note,
834
                    'public_note'      => $item_public_note,
835
                    'loc'              => $item_loc,
836
                    'ccode'            => $item_ccode,
837
                    'notforloan'       => $item_notforloan,
838
                    'uri'              => $item_uri,
839
                    'copyno'           => $item_copyno,
840
                    'quantity'         => $item_quantity,
841
                    'budget_id'        => $item_budget_id         || $budget_id,
842
                    'itemprice'        => $item_price             || $price,
843
                    'replacementprice' => $item_replacement_price || $replacementprice,
844
                    'itemcallnumber'   => $item_callnumber,
845
                );
846
                $all_items_quantity++;
847
                push @itemlist, \%itemrecord;
848
849
            }
850
851
            $cellrecord{'iteminfos'} = \@itemlist;
852
        } else {
853
            $cellrecord{'item_error'} = 1;
854
        }
855
        push @list, \%cellrecord;
856
857
        # If MarcItemFieldsToOrder is not set, we use MarcFieldsToOrder to populate the order form.
858
        if ( %$alliteminfos == -1 ) {
859
            $cellrecord{price}            = $price            || '';
860
            $cellrecord{replacementprice} = $replacementprice || '';
861
            $cellrecord{quantity}         = $quantity         || '';
862
            $cellrecord{budget_id}        = $budget_id        || '';
863
        } else {
864
865
            # When using MarcItemFields to order we want the order to have the same quantity as total items
866
            $cellrecord{quantity} = $all_items_quantity;
867
        }
868
869
        # The fields discount, sort1, and sort2 only exist at the order level, so always use MarcItemFieldsToOrder
870
        $cellrecord{discount} = $discount || '';
871
        $cellrecord{sort1}    = $sort1    || '';
872
        $cellrecord{sort2}    = $sort2    || '';
873
874
    }
875
    my $num_records    = $batch->{'num_records'};
876
    my $overlay_action = GetImportBatchOverlayAction($import_batch_id);
877
    my $nomatch_action = GetImportBatchNoMatchAction($import_batch_id);
878
    my $item_action    = GetImportBatchItemAction($import_batch_id);
879
    $template->param(
880
        import_biblio_list                 => \@list,
881
        num_results                        => $num_records,
882
        import_batch_id                    => $import_batch_id,
883
        "overlay_action_${overlay_action}" => 1,
884
        overlay_action                     => $overlay_action,
885
        "nomatch_action_${nomatch_action}" => 1,
886
        nomatch_action                     => $nomatch_action,
887
        "item_action_${item_action}"       => 1,
888
        item_action                        => $item_action,
889
        item_error                         => $item_error,
890
        libraries                          => Koha::Libraries->search,
891
        locationloop                       => \@locations,
892
        itemtypes                          => Koha::ItemTypes->search,
893
        ccodeloop                          => \@ccodes,
894
        notforloanloop                     => \@notforloans,
895
    );
896
    _batch_info( $template, $batch );
897
}
898
899
sub _batch_info {
900
    my ( $template, $batch ) = @_;
901
    $template->param(
902
        batch_info       => 1,
903
        file_name        => $batch->{'file_name'},
904
        comments         => $batch->{'comments'},
905
        import_status    => $batch->{'import_status'},
906
        upload_timestamp => $batch->{'upload_timestamp'},
907
        num_records      => $batch->{'num_records'},
908
        num_items        => $batch->{'num_items'}
909
    );
910
    if ( $batch->{'num_records'} > 0 ) {
911
        if ( $batch->{'import_status'} eq 'staged' or $batch->{'import_status'} eq 'reverted' ) {
912
            $template->param( can_commit => 1 );
913
        }
914
        if ( $batch->{'import_status'} eq 'imported' ) {
915
            $template->param( can_revert => 1 );
916
        }
917
    }
918
    if ( defined $batch->{'matcher_id'} ) {
919
        my $matcher = C4::Matcher->fetch( $batch->{'matcher_id'} );
920
        if ( defined $matcher ) {
921
            $template->param(
922
                'current_matcher_id'          => $batch->{'matcher_id'},
923
                'current_matcher_code'        => $matcher->code(),
924
                'current_matcher_description' => $matcher->description()
925
            );
926
        }
927
    }
928
    _add_matcher_list( $batch->{'matcher_id'}, $template );
929
}
930
931
sub _add_matcher_list {
932
    my ( $current_matcher_id, $template ) = @_;
933
    my @matchers = C4::Matcher::GetMatcherList();
934
    if ( defined $current_matcher_id ) {
935
        for ( my $i = 0 ; $i <= $#matchers ; $i++ ) {
936
            if ( $matchers[$i]->{'matcher_id'} == $current_matcher_id ) {
937
                $matchers[$i]->{'selected'} = 1;
938
            }
939
        }
940
    }
941
    $template->param( available_matchers => \@matchers );
942
}
943
944
1;

Return to bug 35026