3 # Copyright 2014,2015 PTFS-Europe Ltd
5 # This file is part of Koha.
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.
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.
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>.
22 use base qw(Exporter);
25 use English qw{ -no_match_vars };
31 use C4::Acquisition qw( NewBasket CloseBasket ModOrder);
32 use C4::Suggestions qw( ModSuggestion );
33 use C4::Items qw(AddItem);
34 use C4::Biblio qw( AddBiblio TransformKohaToMarc GetMarcBiblio GetFrameworkCode GetMarcFromKohaField );
35 use Koha::Edifact::Order;
39 use Koha::Plugins::Handler;
40 use Koha::Acquisition::Baskets;
41 use Koha::Acquisition::Booksellers;
45 qw( process_quote process_invoice process_ordrsp create_edi_order get_edifact_ean );
47 sub create_edi_order {
48 my $parameters = shift;
49 my $basketno = $parameters->{basketno};
50 my $ean = $parameters->{ean};
51 my $branchcode = $parameters->{branchcode};
52 my $noingest = $parameters->{noingest};
53 if ( !$basketno || !$ean ) {
54 carp 'create_edi_order called with no basketno or ean';
58 my $schema = Koha::Database->new()->schema();
60 my @orderlines = $schema->resultset('Aqorder')->search(
62 basketno => $basketno,
68 carp "No orderlines for basket $basketno";
72 my $vendor = $schema->resultset('VendorEdiAccount')->search(
74 vendor_id => $orderlines[0]->basketno->booksellerid->id,
78 my $ean_search_keys = { ean => $ean, };
80 $ean_search_keys->{branchcode} = $branchcode;
83 $schema->resultset('EdifactEan')->search($ean_search_keys)->single;
85 # If no branch specific each can be found, look for a default ean
87 $ean_obj = $schema->resultset('EdifactEan')->search(
95 my $dbh = C4::Context->dbh;
96 my $arr_ref = $dbh->selectcol_arrayref(
97 'select id from edifact_messages where basketno = ? and message_type = \'QUOTE\'',
100 my $response = @{$arr_ref} ? 1 : 0;
102 my $edifact_order_params = {
103 orderlines => \@orderlines,
106 is_response => $response,
110 if ( $vendor->plugin ) {
111 $edifact = Koha::Plugins::Handler->run(
113 class => $vendor->plugin,
114 method => 'edifact_order',
116 params => $edifact_order_params,
122 $edifact = Koha::Edifact::Order->new($edifact_order_params);
125 return unless $edifact;
127 my $order_file = $edifact->encode();
131 my $m = unidecode($order_file); # remove diacritics and non-latin chars
132 if ($noingest) { # allows scripts to produce test files
136 message_type => 'ORDERS',
138 vendor_id => $vendor->vendor_id,
140 basketno => $basketno,
141 filename => $edifact->filename(),
142 transfer_date => $edifact->msg_date_string(),
143 edi_acct => $vendor->id,
146 $schema->resultset('EdifactMessage')->create($order);
154 my $response_message = shift;
155 $response_message->status('processing');
156 $response_message->update;
157 my $schema = Koha::Database->new()->schema();
158 my $logger = Log::Log4perl->get_logger();
161 Koha::Edifact->new( { transmission => $response_message->raw_msg, } );
162 my $messages = $edi->message_array();
164 if ( @{$messages} ) {
165 foreach my $msg ( @{$messages} ) {
166 my $lines = $msg->lineitems();
167 foreach my $line ( @{$lines} ) {
168 my $ordernumber = $line->ordernumber();
170 # action cancelled:change_requested:no_action:accepted:not_found:recorded
171 my $action = $line->action_notification();
172 if ( $action eq 'cancelled' ) {
173 my $reason = $line->coded_orderline_text();
176 ordernumber => $ordernumber,
177 cancellationreason => $reason,
178 orderstatus => 'cancelled',
179 datecancellationprinted => dt_from_string()->ymd(),
183 else { # record order as due with possible further info
185 my $report = $line->coded_orderline_text();
186 my $date_avail = $line->availability_date();
189 $report .= " Available: $date_avail";
193 ordernumber => $ordernumber,
194 suppliers_report => $report,
202 $response_message->status('received');
203 $response_message->update;
207 sub process_invoice {
208 my $invoice_message = shift;
209 $invoice_message->status('processing');
210 $invoice_message->update;
211 my $schema = Koha::Database->new()->schema();
212 my $logger = Log::Log4perl->get_logger();
215 my $plugin = $invoice_message->edi_acct()->plugin();
218 $edi_plugin = Koha::Plugins::Handler->run(
223 invoice_message => $invoice_message,
224 transmission => $invoice_message->raw_msg,
230 my $edi = $edi_plugin ||
231 Koha::Edifact->new( { transmission => $invoice_message->raw_msg, } );
233 my $messages = $edi->message_array();
235 if ( @{$messages} ) {
237 # BGM contains an invoice number
238 foreach my $msg ( @{$messages} ) {
239 my $invoicenumber = $msg->docmsg_number();
240 my $shipmentcharge = $msg->shipment_charge();
241 my $msg_date = $msg->message_date;
242 my $tax_date = $msg->tax_point_date;
243 if ( !defined $tax_date || $tax_date !~ m/^\d{8}/xms ) {
244 $tax_date = $msg_date;
247 my $vendor_ean = $msg->supplier_ean;
248 if ( !defined $vendor_acct || $vendor_ean ne $vendor_acct->san ) {
249 $vendor_acct = $schema->resultset('VendorEdiAccount')->search(
255 if ( !$vendor_acct ) {
257 "Cannot find vendor with ean $vendor_ean for invoice $invoicenumber in $invoice_message->filename";
260 $invoice_message->edi_acct( $vendor_acct->id );
261 $logger->trace("Adding invoice:$invoicenumber");
262 my $new_invoice = $schema->resultset('Aqinvoice')->create(
264 invoicenumber => $invoicenumber,
265 booksellerid => $invoice_message->vendor_id,
266 shipmentdate => $msg_date,
267 billingdate => $tax_date,
268 shipmentcost => $shipmentcharge,
269 shipmentcost_budgetid => $vendor_acct->shipment_budget,
270 message_id => $invoice_message->id,
273 my $invoiceid = $new_invoice->invoiceid;
274 $logger->trace("Added as invoiceno :$invoiceid");
275 my $lines = $msg->lineitems();
277 foreach my $line ( @{$lines} ) {
278 my $ordernumber = $line->ordernumber;
279 $logger->trace( "Receipting order:$ordernumber Qty: ",
282 my $order = $schema->resultset('Aqorder')->find($ordernumber);
284 # ModReceiveOrder does not validate that $ordernumber exists validate here
288 my $s = $schema->resultset('Suggestion')->search(
290 biblionumber => $order->biblionumber->biblionumber,
296 suggestionid => $s->suggestionid,
297 STATUS => 'AVAILABLE',
302 my $price = _get_invoiced_price($line);
304 if ( $order->quantity > $line->quantity ) {
305 my $ordered = $order->quantity;
308 $order->orderstatus('partial');
309 $order->quantity( $ordered - $line->quantity );
311 my $received_order = $order->copy(
313 ordernumber => undef,
314 quantity => $line->quantity,
315 quantityreceived => $line->quantity,
316 orderstatus => 'complete',
318 invoiceid => $invoiceid,
319 datereceived => $msg_date,
322 transfer_items( $schema, $line, $order,
324 receipt_items( $schema, $line,
325 $received_order->ordernumber );
327 else { # simple receipt all copies on order
328 $order->quantityreceived( $line->quantity );
329 $order->datereceived($msg_date);
330 $order->invoiceid($invoiceid);
331 $order->unitprice($price);
332 $order->orderstatus('complete');
334 receipt_items( $schema, $line, $ordernumber );
339 "No order found for $ordernumber Invoice:$invoicenumber"
349 $invoice_message->status('received');
350 $invoice_message->update; # status and basketno link
354 sub _get_invoiced_price {
356 my $price = $line->price_net;
357 if ( !defined $price ) { # no net price so generate it from lineitem amount
358 $price = $line->amt_lineitem;
359 if ( $price and $line->quantity > 1 ) {
360 $price /= $line->quantity; # div line cost by qty
367 my ( $schema, $inv_line, $ordernumber ) = @_;
368 my $logger = Log::Log4perl->get_logger();
369 my $quantity = $inv_line->quantity;
371 # itemnumber is not a foreign key ??? makes this a bit cumbersome
372 my @item_links = $schema->resultset('AqordersItem')->search(
374 ordernumber => $ordernumber,
378 foreach my $ilink (@item_links) {
379 my $item = $schema->resultset('Item')->find( $ilink->itemnumber );
381 my $i = $ilink->itemnumber;
383 "Cannot find aqorder item for $i :Order:$ordernumber");
386 my $b = $item->homebranch->branchcode;
387 if ( !exists $branch_map{$b} ) {
388 $branch_map{$b} = [];
390 push @{ $branch_map{$b} }, $item;
393 # Handling for 'AcqItemSetSubfieldsWhenReceived'
397 if ( C4::Context->preference('AcqCreateItem') eq 'ordering' ) {
398 @affects = split q{\|},
399 C4::Context->preference("AcqItemSetSubfieldsWhenReceived");
401 my $order = Koha::Acquisition::Orders->find($ordernumber);
402 $biblionumber = $order->biblionumber;
403 my $frameworkcode = GetFrameworkCode($biblionumber);
404 ($itemfield) = GetMarcFromKohaField( 'items.itemnumber',
409 my $gir_occurrence = 0;
410 while ( $gir_occurrence < $quantity ) {
411 my $branch = $inv_line->girfield( 'branch', $gir_occurrence );
412 my $item = shift @{ $branch_map{$branch} };
414 my $barcode = $inv_line->girfield( 'barcode', $gir_occurrence );
415 if ( $barcode && !$item->barcode ) {
416 my $rs = $schema->resultset('Item')->search(
421 if ( $rs->count > 0 ) {
422 $logger->warn("Barcode $barcode is a duplicate");
426 $logger->trace("Adding barcode $barcode");
427 $item->barcode($barcode);
431 # Handling for 'AcqItemSetSubfieldsWhenReceived'
433 my $item_marc = C4::Items::GetMarcItem( $biblionumber, $item->itemnumber );
434 for my $affect (@affects) {
435 my ( $sf, $v ) = split q{=}, $affect, 2;
436 foreach ( $item_marc->field($itemfield) ) {
437 $_->update( $sf => $v );
440 C4::Items::ModItemFromMarc( $item_marc, $biblionumber, $item->itemnumber );
446 $logger->warn("Unmatched item at branch:$branch");
455 my ( $schema, $inv_line, $order_from, $order_to ) = @_;
457 # Transfer x items from the orig order to a completed partial order
458 my $quantity = $inv_line->quantity;
460 my %mapped_by_branch;
461 while ( $gocc < $quantity ) {
462 my $branch = $inv_line->girfield( 'branch', $gocc );
463 if ( !exists $mapped_by_branch{$branch} ) {
464 $mapped_by_branch{$branch} = 1;
467 $mapped_by_branch{$branch}++;
471 my $logger = Log::Log4perl->get_logger();
472 my $o1 = $order_from->ordernumber;
473 my $o2 = $order_to->ordernumber;
474 $logger->warn("transferring $quantity copies from order $o1 to order $o2");
476 my @item_links = $schema->resultset('AqordersItem')->search(
478 ordernumber => $order_from->ordernumber,
481 foreach my $ilink (@item_links) {
482 my $ino = $ilink->itemnumber;
483 my $item = $schema->resultset('Item')->find( $ilink->itemnumber );
484 my $i_branch = $item->homebranch;
485 if ( exists $mapped_by_branch{$i_branch}
486 && $mapped_by_branch{$i_branch} > 0 )
488 $ilink->ordernumber( $order_to->ordernumber );
491 --$mapped_by_branch{$i_branch};
492 $logger->warn("Transferred item $item");
495 $logger->warn("Skipped item $item");
497 if ( $quantity < 1 ) {
508 $quote->status('processing');
511 my $edi = Koha::Edifact->new( { transmission => $quote->raw_msg, } );
513 my $messages = $edi->message_array();
514 my $process_errors = 0;
515 my $logger = Log::Log4perl->get_logger();
516 my $schema = Koha::Database->new()->schema();
517 my $message_count = 0;
518 my @added_baskets; # if auto & multiple baskets need to order all
520 if ( @{$messages} && $quote->vendor_id ) {
521 foreach my $msg ( @{$messages} ) {
524 NewBasket( $quote->vendor_id, 0, $quote->filename, q{},
526 push @added_baskets, $basketno;
527 if ( $message_count > 1 ) {
528 my $m_filename = $quote->filename;
529 $m_filename .= "_$message_count";
530 $schema->resultset('EdifactMessage')->create(
532 message_type => $quote->message_type,
533 transfer_date => $quote->transfer_date,
534 vendor_id => $quote->vendor_id,
535 edi_acct => $quote->edi_acct,
537 basketno => $basketno,
539 filename => $m_filename,
544 $quote->basketno($basketno);
546 $logger->trace("Created basket :$basketno");
547 my $items = $msg->lineitems();
548 my $refnum = $msg->message_refno;
550 for my $item ( @{$items} ) {
551 if ( !quote_item( $item, $quote, $basketno ) ) {
557 my $status = 'received';
558 if ($process_errors) {
562 $quote->status($status);
563 $quote->update; # status and basketno link
564 # Do we automatically generate orders for this vendor
565 my $v = $schema->resultset('VendorEdiAccount')->search(
567 vendor_id => $quote->vendor_id,
570 if ( $v->auto_orders ) {
571 for my $b (@added_baskets) {
574 ean => $messages->[0]->buyer_ean,
586 my ( $item, $quote, $basketno ) = @_;
588 my $schema = Koha::Database->new()->schema();
589 my $logger = Log::Log4perl->get_logger();
591 # $basketno is the return from AddBasket in the calling routine
592 # So this call should not fail unless that has
593 my $basket = Koha::Acquisition::Baskets->find( $basketno );
595 $logger->error('Skipping order creation no valid basketno');
598 $logger->trace( 'Checking db for matches with ', $item->item_number_id() );
599 my $bib = _check_for_existing_bib( $item->item_number_id() );
600 if ( !defined $bib ) {
602 my $bib_record = _create_bib_from_quote( $item, $quote );
603 ( $bib->{biblionumber}, $bib->{biblioitemnumber} ) =
604 AddBiblio( $bib_record, q{} );
605 $logger->trace("New biblio added $bib->{biblionumber}");
608 $logger->trace("Match found: $bib->{biblionumber}");
611 # Create an orderline
612 my $order_note = $item->{orderline_free_text};
614 my $order_quantity = $item->quantity();
615 my $gir_count = $item->number_of_girs();
616 $order_quantity ||= 1; # quantity not necessarily present
617 if ( $gir_count > 1 ) {
618 if ( $gir_count != $order_quantity ) {
620 "Order for $order_quantity items, $gir_count segments present");
622 $order_quantity = 1; # attempts to create an orderline for each gir
624 my $vendor = Koha::Acquisition::Booksellers->find( $quote->vendor_id );
626 # database definitions should set some of these defaults but dont
628 biblionumber => $bib->{biblionumber},
629 entrydate => dt_from_string()->ymd(),
630 basketno => $basketno,
631 listprice => $item->price,
632 quantity => $order_quantity,
633 quantityreceived => 0,
634 order_vendornote => q{},
635 order_internalnote => $order_note,
636 replacementprice => $item->price,
637 rrp_tax_included => $item->price,
638 rrp_tax_excluded => $item->price,
639 ecost => _discounted_price( $quote->vendor->discount, $item->price ),
643 currency => $vendor->listprice(),
646 # suppliers references
647 if ( $item->reference() ) {
648 $order_hash->{suppliers_reference_number} = $item->reference;
649 $order_hash->{suppliers_reference_qualifier} = 'QLI';
651 elsif ( $item->orderline_reference_number() ) {
652 $order_hash->{suppliers_reference_number} =
653 $item->orderline_reference_number;
654 $order_hash->{suppliers_reference_qualifier} = 'SLI';
656 if ( $item->item_number_id ) { # suppliers ean
657 $order_hash->{line_item_id} = $item->item_number_id;
660 if ( $item->girfield('servicing_instruction') ) {
664 while ( $si = $item->girfield( 'servicing_instruction', $occ ) ) {
671 $order_hash->{order_vendornote} = $txt;
674 if ( $item->internal_notes() ) {
675 if ( $order_hash->{order_internalnote} ) { # more than ''
676 $order_hash->{order_internalnote} .= q{ };
678 $order_hash->{order_internalnote} .= $item->internal_notes;
681 my $budget = _get_budget( $schema, $item->girfield('fund_allocation') );
685 if ( $item->quantity > 1 ) {
686 carp 'Skipping line with no budget info';
687 $logger->trace('girfield skipped for invalid budget');
691 carp 'Skipping line with no budget info';
692 $logger->trace('orderline skipped for invalid budget');
702 $order_hash->{budget_id} = $budget->budget_id;
703 my $first_order = $schema->resultset('Aqorder')->create($order_hash);
704 my $o = $first_order->ordernumber();
705 $logger->trace("Order created :$o");
707 # should be done by database settings
708 $first_order->parent_ordernumber( $first_order->ordernumber() );
709 $first_order->update();
711 # add to $budgets to prevent duplicate orderlines
712 $budgets{ $budget->budget_id } = '1';
714 # record ordernumber against budget
715 $ordernumber{ $budget->budget_id } = $o;
717 if ( C4::Context->preference('AcqCreateItem') eq 'ordering' ) {
718 $item_hash = _create_item_from_quote( $item, $quote );
721 while ( $created < $order_quantity ) {
723 ( $bib->{biblionumber}, $bib->{biblioitemnumber}, $itemnumber )
724 = AddItem( $item_hash, $bib->{biblionumber} );
725 $logger->trace("Added item:$itemnumber");
726 $schema->resultset('AqordersItem')->create(
728 ordernumber => $first_order->ordernumber,
729 itemnumber => $itemnumber,
737 if ( $order_quantity == 1 && $item->quantity > 1 ) {
738 my $occurrence = 1; # occ zero already added
739 while ( $occurrence < $item->quantity ) {
742 $budget = _get_budget( $schema,
743 $item->girfield( 'fund_allocation', $occurrence ) );
747 $item->girfield( 'fund_allocation', $occurrence );
748 carp 'Skipping line with no budget info';
750 "girfield skipped for invalid budget:$bad_budget");
751 ++$occurrence; ## lets look at the next one not this one again
755 # add orderline for NEW budget in $budgets
756 if ( !exists $budgets{ $budget->budget_id } ) {
758 # $order_hash->{quantity} = 1; by default above
759 # we should handle both 1:1 GIR & 1:n GIR (with LQT values) here
761 $order_hash->{budget_id} = $budget->budget_id;
764 $schema->resultset('Aqorder')->create($order_hash);
765 my $o = $new_order->ordernumber();
766 $logger->trace("Order created :$o");
768 # should be done by database settings
769 $new_order->parent_ordernumber( $new_order->ordernumber() );
770 $new_order->update();
772 # add to $budgets to prevent duplicate orderlines
773 $budgets{ $budget->budget_id } = '1';
775 # record ordernumber against budget
776 $ordernumber{ $budget->budget_id } = $o;
778 if ( C4::Context->preference('AcqCreateItem') eq 'ordering' ) {
779 if ( !defined $item_hash ) {
780 $item_hash = _create_item_from_quote( $item, $quote );
784 $item->girfield( 'stock_category', $occurrence ),
786 $item->girfield( 'collection_code', $occurrence ),
788 $item->girfield( 'shelfmark', $occurrence )
789 || $item->girfield( 'classification', $occurrence )
790 || title_level_class($item),
792 $item->girfield( 'branch', $occurrence ),
793 homebranch => $item->girfield( 'branch', $occurrence ),
795 if ( $new_item->{itype} ) {
796 $item_hash->{itype} = $new_item->{itype};
798 if ( $new_item->{location} ) {
799 $item_hash->{location} = $new_item->{location};
801 if ( $new_item->{itemcallnumber} ) {
802 $item_hash->{itemcallnumber} =
803 $new_item->{itemcallnumber};
805 if ( $new_item->{holdingbranch} ) {
806 $item_hash->{holdingbranch} =
807 $new_item->{holdingbranch};
809 if ( $new_item->{homebranch} ) {
810 $item_hash->{homebranch} = $new_item->{homebranch};
814 ( undef, undef, $itemnumber ) =
815 AddItem( $item_hash, $bib->{biblionumber} );
816 $logger->trace("New item $itemnumber added");
817 $schema->resultset('AqordersItem')->create(
819 ordernumber => $new_order->ordernumber,
820 itemnumber => $itemnumber,
825 $item->girfield( 'library_rotation_plan', $occurrence );
828 Koha::StockRotationRotas->find( { title => $lrp },
829 { key => 'stockrotationrotas_title' } );
831 $rota->add_item($itemnumber);
832 $logger->trace("Item added to rota $rota->id");
836 "No rota found matching $lrp in orderline");
844 # increment quantity in orderline for EXISTING budget in $budgets
846 my $row = $schema->resultset('Aqorder')->find(
848 ordernumber => $ordernumber{ $budget->budget_id }
852 my $qty = $row->quantity;
861 # Do not use the basket level value as it is always NULL
862 # See calling subs call to AddBasket
863 if ( C4::Context->preference('AcqCreateItem') eq 'ordering' ) {
868 price => $item->price,
869 replacementprice => $item->price,
871 $item->girfield( 'stock_category', $occurrence ),
873 $item->girfield( 'collection_code', $occurrence ),
875 $item->girfield( 'shelfmark', $occurrence )
876 || $item->girfield( 'classification', $occurrence )
877 || $item_hash->{itemcallnumber},
879 $item->girfield( 'branch', $occurrence ),
880 homebranch => $item->girfield( 'branch', $occurrence ),
883 ( undef, undef, $itemnumber ) =
884 AddItem( $new_item, $bib->{biblionumber} );
885 $logger->trace("New item $itemnumber added");
886 $schema->resultset('AqordersItem')->create(
888 ordernumber => $ordernumber{ $budget->budget_id },
889 itemnumber => $itemnumber,
894 $item->girfield( 'library_rotation_plan', $occurrence );
897 Koha::StockRotationRotas->find( { title => $lrp },
898 { key => 'stockrotationrotas_title' } );
900 $rota->add_item($itemnumber);
901 $logger->trace("Item added to rota $rota->id");
905 "No rota found matching $lrp in orderline");
918 sub get_edifact_ean {
920 my $dbh = C4::Context->dbh;
922 my $eans = $dbh->selectcol_arrayref('select ean from edifact_ean');
927 # We should not need to have a routine to do this here
928 sub _discounted_price {
929 my ( $discount, $price ) = @_;
930 return $price - ( ( $discount * $price ) / 100 );
933 sub _check_for_existing_bib {
936 my $search_isbn = $isbn;
937 $search_isbn =~ s/^\s*/%/xms;
938 $search_isbn =~ s/\s*$/%/xms;
939 my $dbh = C4::Context->dbh;
940 my $sth = $dbh->prepare(
941 'select biblionumber, biblioitemnumber from biblioitems where isbn like ?',
944 $dbh->selectall_arrayref( $sth, { Slice => {} }, $search_isbn );
945 if ( @{$tuple_arr} ) {
946 return $tuple_arr->[0];
948 elsif ( length($isbn) == 13 && $isbn !~ /^97[89]/ ) {
949 my $tarr = $dbh->selectall_arrayref(
950 'select biblionumber, biblioitemnumber from biblioitems where ean = ?',
961 if ( $isbn =~ m/(\d{13})/xms ) {
962 my $b_isbn = Business::ISBN->new($1);
963 if ( $b_isbn && $b_isbn->is_valid && $b_isbn->as_isbn10 ) {
964 $search_isbn = $b_isbn->as_isbn10->as_string( [] );
968 elsif ( $isbn =~ m/(\d{9}[xX]|\d{10})/xms ) {
969 my $b_isbn = Business::ISBN->new($1);
970 if ( $b_isbn && $b_isbn->is_valid ) {
971 $search_isbn = $b_isbn->as_isbn13->as_string( [] );
976 $search_isbn = "%$search_isbn%";
978 $dbh->selectall_arrayref( $sth, { Slice => {} }, $search_isbn );
979 if ( @{$tuple_arr} ) {
980 return $tuple_arr->[0];
987 # returns a budget obj or undef
988 # fact we need this shows what a mess Acq API is
990 my ( $schema, $budget_code ) = @_;
991 my $period_rs = $schema->resultset('Aqbudgetperiod')->search(
993 budget_period_active => 1,
997 # db does not ensure budget code is unque
998 return $schema->resultset('Aqbudget')->single(
1000 budget_code => $budget_code,
1002 { -in => $period_rs->get_column('budget_period_id')->as_query },
1007 # try to get title level classification from incoming quote
1008 sub title_level_class {
1011 my $default_scheme = C4::Context->preference('DefaultClassificationSource');
1012 if ( $default_scheme eq 'ddc' ) {
1013 $class = $item->dewey_class();
1015 elsif ( $default_scheme eq 'lcc' ) {
1016 $class = $item->lc_class();
1020 $item->girfield('shelfmark')
1021 || $item->girfield('classification')
1027 sub _create_bib_from_quote {
1029 #TBD we should flag this for updating from an external source
1030 #As biblio (&biblioitems) has no candidates flag in order
1031 my ( $item, $quote ) = @_;
1032 my $itemid = $item->item_number_id;
1033 my $defalt_classification_source =
1034 C4::Context->preference('DefaultClassificationSource');
1036 'biblioitems.cn_source' => $defalt_classification_source,
1037 'items.cn_source' => $defalt_classification_source,
1038 'items.notforloan' => -1,
1039 'items.cn_sort' => q{},
1041 $bib_hash->{'biblio.seriestitle'} = $item->series;
1043 $bib_hash->{'biblioitems.publishercode'} = $item->publisher;
1044 $bib_hash->{'biblioitems.publicationyear'} =
1045 $bib_hash->{'biblio.copyrightdate'} = $item->publication_date;
1047 $bib_hash->{'biblio.title'} = $item->title;
1048 $bib_hash->{'biblio.author'} = $item->author;
1049 $bib_hash->{'biblioitems.isbn'} = $item->item_number_id;
1050 $bib_hash->{'biblioitems.itemtype'} = $item->girfield('stock_category');
1052 # If we have a 13 digit id we are assuming its an ean
1053 # (it may also be an isbn or issn)
1054 if ( $itemid =~ /^\d{13}$/ ) {
1055 $bib_hash->{'biblioitems.ean'} = $itemid;
1056 if ( $itemid =~ /^977/ ) {
1057 $bib_hash->{'biblioitems.issn'} = $itemid;
1060 for my $key ( keys %{$bib_hash} ) {
1061 if ( !defined $bib_hash->{$key} ) {
1062 delete $bib_hash->{$key};
1065 return TransformKohaToMarc($bib_hash);
1069 sub _create_item_from_quote {
1070 my ( $item, $quote ) = @_;
1071 my $defalt_classification_source =
1072 C4::Context->preference('DefaultClassificationSource');
1074 cn_source => $defalt_classification_source,
1078 $item_hash->{booksellerid} = $quote->vendor_id;
1079 $item_hash->{price} = $item_hash->{replacementprice} = $item->price;
1080 $item_hash->{itype} = $item->girfield('stock_category');
1081 $item_hash->{location} = $item->girfield('collection_code');
1085 $item_hash->{itemcallnumber} =
1086 $item->girfield('shelfmark')
1087 || $item->girfield('classification')
1088 || title_level_class($item);
1090 my $branch = $item->girfield('branch');
1091 $item_hash->{holdingbranch} = $item_hash->{homebranch} = $branch;
1104 Module exporting subroutines used in EDI processing for Koha
1108 Subroutines called by batch processing to handle Edifact
1109 messages of various types and related utilities
1113 These routines should really be methods of some object.
1114 get_edifact_ean is a stopgap which should be replaced
1118 =head2 process_quote
1120 process_quote(quote_message);
1122 passed a message object for a quote, parses it creating an order basket
1123 and orderlines in the database
1124 updates the message's status to received in the database and adds the
1127 =head2 process_invoice
1129 process_invoice(invoice_message)
1131 passed a message object for an invoice, add the contained invoices
1132 and update the orderlines referred to in the invoice
1133 As an Edifact invoice is in effect a despatch note this receipts the
1134 appropriate quantities in the orders
1136 no meaningful return value
1138 =head2 process_ordrsp
1140 process_ordrsp(ordrsp_message)
1142 passed a message object for a supplier response, process the contents
1143 If an orderline is cancelled cancel the corresponding orderline in koha
1144 otherwise record the supplier message against it
1146 no meaningful return value
1148 =head2 create_edi_order
1150 create_edi_order( { parameter_hashref } )
1152 parameters must include basketno and ean
1154 branchcode can optionally be passed
1156 returns 1 on success undef otherwise
1158 if the parameter noingest is set the formatted order is returned
1159 and not saved in the database. This functionality is intended for debugging only
1161 =head2 receipt_items
1163 receipt_items( schema_obj, invoice_line, ordernumber)
1165 receipts the items recorded on this invoice line
1167 no meaningful return
1169 =head2 transfer_items
1171 transfer_items(schema, invoice_line, originating_order, receiving_order)
1173 Transfer the items covered by this invoice line from their original
1174 order to another order recording the partial fulfillment of the original
1177 no meaningful return
1179 =head2 get_edifact_ean
1181 $ean = get_edifact_ean();
1183 routine to return the ean.
1187 quote_item(lineitem, quote_message);
1189 Called by process_quote to handle an individual lineitem
1190 Generate the biblios and items if required and orderline linking to them
1192 Returns 1 on success undef on error
1194 Most usual cause of error is a line with no or incorrect budget codes
1195 which woild cause order creation to abort
1196 If other correct lines exist these are processed and the erroneous line os logged
1198 =head2 title_level_class
1200 classmark = title_level_class(edi_item)
1202 Trys to return a title level classmark from a quote message line
1203 Will return a dewey or lcc classmark if one exists according to the
1204 value in DefaultClassificationSource syspref
1206 If unable to returns the shelfmark or classification from the GIR segment
1208 If all else fails returns empty string
1210 =head2 _create_bib_from_quote
1212 marc_record_obj = _create_bib_from_quote(lineitem, quote)
1214 Returns a MARC::Record object based on the info in the quote's lineitem
1216 =head2 _create_item_from_quote
1218 item_hashref = _create_item_from_quote( lineitem, quote)
1220 returns a hashref representing the item fields specified in the quote
1222 =head2 _get_invoiced_price
1224 _get_invoiced_price(line_object)
1226 Returns the net price or an equivalent calculated from line cost / qty
1228 =head2 _discounted_price
1230 ecost = _discounted_price(discount, item_price)
1232 utility subroutine to return a price calculated from the
1233 vendors discount and quoted price
1235 =head2 _check_for_existing_bib
1237 (biblionumber, biblioitemnumber) = _check_for_existing_bib(isbn_or_ean)
1239 passed an isbn or ean attempts to locate a match bib
1240 On success returns biblionumber and biblioitemnumber
1241 On failure returns undefined/an empty list
1245 b = _get_budget(schema_obj, budget_code)
1247 Returns the Aqbudget object for the active budget given the passed budget_code
1248 or undefined if one does not exist
1252 Colin Campbell <colin.campbell@ptfs-europe.com>
1257 Copyright 2014,2015 PTFS-Europe Ltd
1258 This program is free software, You may redistribute it under
1259 under the terms of the GNU General Public License