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; |