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',
301 # If quantity_invoiced is present use it in preference
302 my $quantity = $line->quantity_invoiced;
304 $quantity = $line->quantity;
307 my ( $price, $price_excl_tax ) = _get_invoiced_price($line, $quantity);
308 my $tax_rate = $line->tax_rate;
309 if ($tax_rate && $tax_rate->{rate} != 0) {
310 $tax_rate->{rate} /= 100;
313 if ( $order->quantity > $quantity ) {
314 my $ordered = $order->quantity;
317 $order->orderstatus('partial');
318 $order->quantity( $ordered - $quantity );
320 my $received_order = $order->copy(
322 ordernumber => undef,
323 quantity => $quantity,
324 quantityreceived => $quantity,
325 orderstatus => 'complete',
327 unitprice_tax_included => $price,
328 unitprice_tax_excluded => $price_excl_tax,
329 invoiceid => $invoiceid,
330 datereceived => $msg_date,
331 tax_rate_on_receiving => $tax_rate->{rate},
332 tax_value_on_receiving => $quantity * $price_excl_tax * $tax_rate->{rate},
335 transfer_items( $schema, $line, $order,
336 $received_order, $quantity );
337 receipt_items( $schema, $line,
338 $received_order->ordernumber, $quantity );
340 else { # simple receipt all copies on order
341 $order->quantityreceived( $quantity );
342 $order->datereceived($msg_date);
343 $order->invoiceid($invoiceid);
344 $order->unitprice($price);
345 $order->unitprice_tax_excluded($price_excl_tax);
346 $order->unitprice_tax_included($price);
347 $order->tax_rate_on_receiving($tax_rate->{rate});
348 $order->tax_value_on_receiving( $quantity * $price_excl_tax * $tax_rate->{rate});
349 $order->orderstatus('complete');
351 receipt_items( $schema, $line, $ordernumber, $quantity );
356 "No order found for $ordernumber Invoice:$invoicenumber"
366 $invoice_message->status('received');
367 $invoice_message->update; # status and basketno link
371 sub _get_invoiced_price {
374 my $line_total = $line->amt_total;
375 my $excl_tax = $line->amt_lineitem;
377 # If no tax some suppliers omit the total owed
378 # If no total given calculate from cost exclusive of tax
379 # + tax amount (if present, sometimes omitted if 0 )
380 if ( !defined $line_total ) {
381 my $x = $line->amt_taxoncharge;
385 $line_total = $excl_tax + $x;
388 # invoices give amounts per orderline, Koha requires that we store
391 return ( $line_total / $qty, $excl_tax / $qty );
393 return ( $line_total, $excl_tax ); # return as is for most common case
397 my ( $schema, $inv_line, $ordernumber, $quantity ) = @_;
398 my $logger = Log::Log4perl->get_logger();
400 # itemnumber is not a foreign key ??? makes this a bit cumbersome
401 my @item_links = $schema->resultset('AqordersItem')->search(
403 ordernumber => $ordernumber,
407 foreach my $ilink (@item_links) {
408 my $item = $schema->resultset('Item')->find( $ilink->itemnumber );
410 my $i = $ilink->itemnumber;
412 "Cannot find aqorder item for $i :Order:$ordernumber");
415 my $b = $item->homebranch->branchcode;
416 if ( !exists $branch_map{$b} ) {
417 $branch_map{$b} = [];
419 push @{ $branch_map{$b} }, $item;
422 # Handling for 'AcqItemSetSubfieldsWhenReceived'
426 if ( C4::Context->preference('AcqCreateItem') eq 'ordering' ) {
427 @affects = split q{\|},
428 C4::Context->preference("AcqItemSetSubfieldsWhenReceived");
430 my $order = Koha::Acquisition::Orders->find($ordernumber);
431 $biblionumber = $order->biblionumber;
432 my $frameworkcode = GetFrameworkCode($biblionumber);
433 ($itemfield) = GetMarcFromKohaField( 'items.itemnumber',
438 my $gir_occurrence = 0;
439 while ( $gir_occurrence < $quantity ) {
440 my $branch = $inv_line->girfield( 'branch', $gir_occurrence );
441 my $item = shift @{ $branch_map{$branch} };
443 my $barcode = $inv_line->girfield( 'barcode', $gir_occurrence );
444 if ( $barcode && !$item->barcode ) {
445 my $rs = $schema->resultset('Item')->search(
450 if ( $rs->count > 0 ) {
451 $logger->warn("Barcode $barcode is a duplicate");
455 $logger->trace("Adding barcode $barcode");
456 $item->barcode($barcode);
460 # Handling for 'AcqItemSetSubfieldsWhenReceived'
462 my $item_marc = C4::Items::GetMarcItem( $biblionumber, $item->itemnumber );
463 for my $affect (@affects) {
464 my ( $sf, $v ) = split q{=}, $affect, 2;
465 foreach ( $item_marc->field($itemfield) ) {
466 $_->update( $sf => $v );
469 C4::Items::ModItemFromMarc( $item_marc, $biblionumber, $item->itemnumber );
475 $logger->warn("Unmatched item at branch:$branch");
484 my ( $schema, $inv_line, $order_from, $order_to, $quantity ) = @_;
486 # Transfer x items from the orig order to a completed partial order
488 my %mapped_by_branch;
489 while ( $gocc < $quantity ) {
490 my $branch = $inv_line->girfield( 'branch', $gocc );
491 if ( !exists $mapped_by_branch{$branch} ) {
492 $mapped_by_branch{$branch} = 1;
495 $mapped_by_branch{$branch}++;
499 my $logger = Log::Log4perl->get_logger();
500 my $o1 = $order_from->ordernumber;
501 my $o2 = $order_to->ordernumber;
502 $logger->warn("transferring $quantity copies from order $o1 to order $o2");
504 my @item_links = $schema->resultset('AqordersItem')->search(
506 ordernumber => $order_from->ordernumber,
509 foreach my $ilink (@item_links) {
510 my $ino = $ilink->itemnumber;
511 my $item = $schema->resultset('Item')->find( $ilink->itemnumber );
512 my $i_branch = $item->homebranch;
513 if ( exists $mapped_by_branch{$i_branch}
514 && $mapped_by_branch{$i_branch} > 0 )
516 $ilink->ordernumber( $order_to->ordernumber );
519 --$mapped_by_branch{$i_branch};
520 $logger->warn("Transferred item $item");
523 $logger->warn("Skipped item $item");
525 if ( $quantity < 1 ) {
536 $quote->status('processing');
539 my $edi = Koha::Edifact->new( { transmission => $quote->raw_msg, } );
541 my $messages = $edi->message_array();
542 my $process_errors = 0;
543 my $logger = Log::Log4perl->get_logger();
544 my $schema = Koha::Database->new()->schema();
545 my $message_count = 0;
546 my @added_baskets; # if auto & multiple baskets need to order all
548 if ( @{$messages} && $quote->vendor_id ) {
549 foreach my $msg ( @{$messages} ) {
552 NewBasket( $quote->vendor_id, 0, $quote->filename, q{},
554 push @added_baskets, $basketno;
555 if ( $message_count > 1 ) {
556 my $m_filename = $quote->filename;
557 $m_filename .= "_$message_count";
558 $schema->resultset('EdifactMessage')->create(
560 message_type => $quote->message_type,
561 transfer_date => $quote->transfer_date,
562 vendor_id => $quote->vendor_id,
563 edi_acct => $quote->edi_acct,
565 basketno => $basketno,
567 filename => $m_filename,
572 $quote->basketno($basketno);
574 $logger->trace("Created basket :$basketno");
575 my $items = $msg->lineitems();
576 my $refnum = $msg->message_refno;
578 for my $item ( @{$items} ) {
579 if ( !quote_item( $item, $quote, $basketno ) ) {
585 my $status = 'received';
586 if ($process_errors) {
590 $quote->status($status);
591 $quote->update; # status and basketno link
592 # Do we automatically generate orders for this vendor
593 my $v = $schema->resultset('VendorEdiAccount')->search(
595 vendor_id => $quote->vendor_id,
598 if ( $v->auto_orders ) {
599 for my $b (@added_baskets) {
602 ean => $messages->[0]->buyer_ean,
614 my ( $item, $quote, $basketno ) = @_;
616 my $schema = Koha::Database->new()->schema();
617 my $logger = Log::Log4perl->get_logger();
619 # $basketno is the return from AddBasket in the calling routine
620 # So this call should not fail unless that has
621 my $basket = Koha::Acquisition::Baskets->find( $basketno );
623 $logger->error('Skipping order creation no valid basketno');
626 $logger->trace( 'Checking db for matches with ', $item->item_number_id() );
627 my $bib = _check_for_existing_bib( $item->item_number_id() );
628 if ( !defined $bib ) {
630 my $bib_record = _create_bib_from_quote( $item, $quote );
631 ( $bib->{biblionumber}, $bib->{biblioitemnumber} ) =
632 AddBiblio( $bib_record, q{} );
633 $logger->trace("New biblio added $bib->{biblionumber}");
636 $logger->trace("Match found: $bib->{biblionumber}");
639 # Create an orderline
640 my $order_note = $item->{orderline_free_text};
642 my $order_quantity = $item->quantity();
643 my $gir_count = $item->number_of_girs();
644 $order_quantity ||= 1; # quantity not necessarily present
645 if ( $gir_count > 1 ) {
646 if ( $gir_count != $order_quantity ) {
648 "Order for $order_quantity items, $gir_count segments present");
650 $order_quantity = 1; # attempts to create an orderline for each gir
652 my $price = $item->price_info;
653 # Howells do not send an info price but do have a gross price
655 $price = $item->price_gross;
657 my $vendor = Koha::Acquisition::Booksellers->find( $quote->vendor_id );
659 # NB quote will not include tax info it only contains the list price
660 my $ecost = _discounted_price( $vendor->discount, $price, $item->price_info_inclusive );
662 # database definitions should set some of these defaults but dont
664 biblionumber => $bib->{biblionumber},
665 entrydate => dt_from_string()->ymd(),
666 basketno => $basketno,
668 quantity => $order_quantity,
669 quantityreceived => 0,
670 order_vendornote => q{},
671 order_internalnote => $order_note,
672 replacementprice => $price,
673 rrp_tax_included => $price,
674 rrp_tax_excluded => $price,
677 ecost_tax_included => $ecost,
678 ecost_tax_excluded => $ecost,
682 currency => $vendor->listprice(),
685 # suppliers references
686 if ( $item->reference() ) {
687 $order_hash->{suppliers_reference_number} = $item->reference;
688 $order_hash->{suppliers_reference_qualifier} = 'QLI';
690 elsif ( $item->orderline_reference_number() ) {
691 $order_hash->{suppliers_reference_number} =
692 $item->orderline_reference_number;
693 $order_hash->{suppliers_reference_qualifier} = 'SLI';
695 if ( $item->item_number_id ) { # suppliers ean
696 $order_hash->{line_item_id} = $item->item_number_id;
699 if ( $item->girfield('servicing_instruction') ) {
703 while ( $si = $item->girfield( 'servicing_instruction', $occ ) ) {
710 $order_hash->{order_vendornote} = $txt;
713 if ( $item->internal_notes() ) {
714 if ( $order_hash->{order_internalnote} ) { # more than ''
715 $order_hash->{order_internalnote} .= q{ };
717 $order_hash->{order_internalnote} .= $item->internal_notes;
720 my $budget = _get_budget( $schema, $item->girfield('fund_allocation') );
724 if ( $item->quantity > 1 ) {
725 carp 'Skipping line with no budget info';
726 $logger->trace('girfield skipped for invalid budget');
730 carp 'Skipping line with no budget info';
731 $logger->trace('orderline skipped for invalid budget');
741 $order_hash->{budget_id} = $budget->budget_id;
742 my $first_order = $schema->resultset('Aqorder')->create($order_hash);
743 my $o = $first_order->ordernumber();
744 $logger->trace("Order created :$o");
746 # should be done by database settings
747 $first_order->parent_ordernumber( $first_order->ordernumber() );
748 $first_order->update();
750 # add to $budgets to prevent duplicate orderlines
751 $budgets{ $budget->budget_id } = '1';
753 # record ordernumber against budget
754 $ordernumber{ $budget->budget_id } = $o;
756 if ( C4::Context->preference('AcqCreateItem') eq 'ordering' ) {
757 $item_hash = _create_item_from_quote( $item, $quote );
760 while ( $created < $order_quantity ) {
762 ( $bib->{biblionumber}, $bib->{biblioitemnumber}, $itemnumber )
763 = AddItem( $item_hash, $bib->{biblionumber} );
764 $logger->trace("Added item:$itemnumber");
765 $schema->resultset('AqordersItem')->create(
767 ordernumber => $first_order->ordernumber,
768 itemnumber => $itemnumber,
776 if ( $order_quantity == 1 && $item->quantity > 1 ) {
777 my $occurrence = 1; # occ zero already added
778 while ( $occurrence < $item->quantity ) {
781 $budget = _get_budget( $schema,
782 $item->girfield( 'fund_allocation', $occurrence ) );
786 $item->girfield( 'fund_allocation', $occurrence );
787 carp 'Skipping line with no budget info';
789 "girfield skipped for invalid budget:$bad_budget");
790 ++$occurrence; ## lets look at the next one not this one again
794 # add orderline for NEW budget in $budgets
795 if ( !exists $budgets{ $budget->budget_id } ) {
797 # $order_hash->{quantity} = 1; by default above
798 # we should handle both 1:1 GIR & 1:n GIR (with LQT values) here
800 $order_hash->{budget_id} = $budget->budget_id;
803 $schema->resultset('Aqorder')->create($order_hash);
804 my $o = $new_order->ordernumber();
805 $logger->trace("Order created :$o");
807 # should be done by database settings
808 $new_order->parent_ordernumber( $new_order->ordernumber() );
809 $new_order->update();
811 # add to $budgets to prevent duplicate orderlines
812 $budgets{ $budget->budget_id } = '1';
814 # record ordernumber against budget
815 $ordernumber{ $budget->budget_id } = $o;
817 if ( C4::Context->preference('AcqCreateItem') eq 'ordering' ) {
818 if ( !defined $item_hash ) {
819 $item_hash = _create_item_from_quote( $item, $quote );
823 $item->girfield( 'stock_category', $occurrence ),
825 $item->girfield( 'collection_code', $occurrence ),
827 $item->girfield( 'shelfmark', $occurrence )
828 || $item->girfield( 'classification', $occurrence )
829 || title_level_class($item),
831 $item->girfield( 'branch', $occurrence ),
832 homebranch => $item->girfield( 'branch', $occurrence ),
834 if ( $new_item->{itype} ) {
835 $item_hash->{itype} = $new_item->{itype};
837 if ( $new_item->{location} ) {
838 $item_hash->{location} = $new_item->{location};
840 if ( $new_item->{itemcallnumber} ) {
841 $item_hash->{itemcallnumber} =
842 $new_item->{itemcallnumber};
844 if ( $new_item->{holdingbranch} ) {
845 $item_hash->{holdingbranch} =
846 $new_item->{holdingbranch};
848 if ( $new_item->{homebranch} ) {
849 $item_hash->{homebranch} = $new_item->{homebranch};
853 ( undef, undef, $itemnumber ) =
854 AddItem( $item_hash, $bib->{biblionumber} );
855 $logger->trace("New item $itemnumber added");
856 $schema->resultset('AqordersItem')->create(
858 ordernumber => $new_order->ordernumber,
859 itemnumber => $itemnumber,
864 $item->girfield( 'library_rotation_plan', $occurrence );
867 Koha::StockRotationRotas->find( { title => $lrp },
868 { key => 'stockrotationrotas_title' } );
870 $rota->add_item($itemnumber);
871 $logger->trace("Item added to rota $rota->id");
875 "No rota found matching $lrp in orderline");
883 # increment quantity in orderline for EXISTING budget in $budgets
885 my $row = $schema->resultset('Aqorder')->find(
887 ordernumber => $ordernumber{ $budget->budget_id }
891 my $qty = $row->quantity;
900 # Do not use the basket level value as it is always NULL
901 # See calling subs call to AddBasket
902 if ( C4::Context->preference('AcqCreateItem') eq 'ordering' ) {
908 replacementprice => $price,
910 $item->girfield( 'stock_category', $occurrence ),
912 $item->girfield( 'collection_code', $occurrence ),
914 $item->girfield( 'shelfmark', $occurrence )
915 || $item->girfield( 'classification', $occurrence )
916 || $item_hash->{itemcallnumber},
918 $item->girfield( 'branch', $occurrence ),
919 homebranch => $item->girfield( 'branch', $occurrence ),
922 ( undef, undef, $itemnumber ) =
923 AddItem( $new_item, $bib->{biblionumber} );
924 $logger->trace("New item $itemnumber added");
925 $schema->resultset('AqordersItem')->create(
927 ordernumber => $ordernumber{ $budget->budget_id },
928 itemnumber => $itemnumber,
933 $item->girfield( 'library_rotation_plan', $occurrence );
936 Koha::StockRotationRotas->find( { title => $lrp },
937 { key => 'stockrotationrotas_title' } );
939 $rota->add_item($itemnumber);
940 $logger->trace("Item added to rota $rota->id");
944 "No rota found matching $lrp in orderline");
957 sub get_edifact_ean {
959 my $dbh = C4::Context->dbh;
961 my $eans = $dbh->selectcol_arrayref('select ean from edifact_ean');
966 # We should not need to have a routine to do this here
967 sub _discounted_price {
968 my ( $discount, $price, $discounted_price ) = @_;
969 if (defined $discounted_price) {
970 return $discounted_price;
975 return $price - ( ( $discount * $price ) / 100 );
978 sub _check_for_existing_bib {
981 my $search_isbn = $isbn;
982 $search_isbn =~ s/^\s*/%/xms;
983 $search_isbn =~ s/\s*$/%/xms;
984 my $dbh = C4::Context->dbh;
985 my $sth = $dbh->prepare(
986 'select biblionumber, biblioitemnumber from biblioitems where isbn like ?',
989 $dbh->selectall_arrayref( $sth, { Slice => {} }, $search_isbn );
990 if ( @{$tuple_arr} ) {
991 return $tuple_arr->[0];
993 elsif ( length($isbn) == 13 && $isbn !~ /^97[89]/ ) {
994 my $tarr = $dbh->selectall_arrayref(
995 'select biblionumber, biblioitemnumber from biblioitems where ean = ?',
1005 $isbn =~ s/\-//xmsg;
1006 if ( $isbn =~ m/(\d{13})/xms ) {
1007 my $b_isbn = Business::ISBN->new($1);
1008 if ( $b_isbn && $b_isbn->is_valid && $b_isbn->as_isbn10 ) {
1009 $search_isbn = $b_isbn->as_isbn10->as_string( [] );
1013 elsif ( $isbn =~ m/(\d{9}[xX]|\d{10})/xms ) {
1014 my $b_isbn = Business::ISBN->new($1);
1015 if ( $b_isbn && $b_isbn->is_valid ) {
1016 $search_isbn = $b_isbn->as_isbn13->as_string( [] );
1021 $search_isbn = "%$search_isbn%";
1023 $dbh->selectall_arrayref( $sth, { Slice => {} }, $search_isbn );
1024 if ( @{$tuple_arr} ) {
1025 return $tuple_arr->[0];
1032 # returns a budget obj or undef
1033 # fact we need this shows what a mess Acq API is
1035 my ( $schema, $budget_code ) = @_;
1036 my $period_rs = $schema->resultset('Aqbudgetperiod')->search(
1038 budget_period_active => 1,
1042 # db does not ensure budget code is unque
1043 return $schema->resultset('Aqbudget')->single(
1045 budget_code => $budget_code,
1047 { -in => $period_rs->get_column('budget_period_id')->as_query },
1052 # try to get title level classification from incoming quote
1053 sub title_level_class {
1056 my $default_scheme = C4::Context->preference('DefaultClassificationSource');
1057 if ( $default_scheme eq 'ddc' ) {
1058 $class = $item->dewey_class();
1060 elsif ( $default_scheme eq 'lcc' ) {
1061 $class = $item->lc_class();
1065 $item->girfield('shelfmark')
1066 || $item->girfield('classification')
1072 sub _create_bib_from_quote {
1074 #TBD we should flag this for updating from an external source
1075 #As biblio (&biblioitems) has no candidates flag in order
1076 my ( $item, $quote ) = @_;
1077 my $itemid = $item->item_number_id;
1078 my $defalt_classification_source =
1079 C4::Context->preference('DefaultClassificationSource');
1081 'biblioitems.cn_source' => $defalt_classification_source,
1082 'items.cn_source' => $defalt_classification_source,
1083 'items.notforloan' => -1,
1084 'items.cn_sort' => q{},
1086 $bib_hash->{'biblio.seriestitle'} = $item->series;
1088 $bib_hash->{'biblioitems.publishercode'} = $item->publisher;
1089 $bib_hash->{'biblioitems.publicationyear'} =
1090 $bib_hash->{'biblio.copyrightdate'} = $item->publication_date;
1092 $bib_hash->{'biblio.title'} = $item->title;
1093 $bib_hash->{'biblio.author'} = $item->author;
1094 $bib_hash->{'biblioitems.isbn'} = $item->item_number_id;
1095 $bib_hash->{'biblioitems.itemtype'} = $item->girfield('stock_category');
1097 # If we have a 13 digit id we are assuming its an ean
1098 # (it may also be an isbn or issn)
1099 if ( $itemid =~ /^\d{13}$/ ) {
1100 $bib_hash->{'biblioitems.ean'} = $itemid;
1101 if ( $itemid =~ /^977/ ) {
1102 $bib_hash->{'biblioitems.issn'} = $itemid;
1105 for my $key ( keys %{$bib_hash} ) {
1106 if ( !defined $bib_hash->{$key} ) {
1107 delete $bib_hash->{$key};
1110 return TransformKohaToMarc($bib_hash);
1114 sub _create_item_from_quote {
1115 my ( $item, $quote ) = @_;
1116 my $defalt_classification_source =
1117 C4::Context->preference('DefaultClassificationSource');
1119 cn_source => $defalt_classification_source,
1123 $item_hash->{booksellerid} = $quote->vendor_id;
1124 $item_hash->{price} = $item_hash->{replacementprice} = $item->price;
1125 $item_hash->{itype} = $item->girfield('stock_category');
1126 $item_hash->{location} = $item->girfield('collection_code');
1130 $item_hash->{itemcallnumber} =
1131 $item->girfield('shelfmark')
1132 || $item->girfield('classification')
1133 || title_level_class($item);
1135 my $branch = $item->girfield('branch');
1136 $item_hash->{holdingbranch} = $item_hash->{homebranch} = $branch;
1149 Module exporting subroutines used in EDI processing for Koha
1153 Subroutines called by batch processing to handle Edifact
1154 messages of various types and related utilities
1158 These routines should really be methods of some object.
1159 get_edifact_ean is a stopgap which should be replaced
1163 =head2 process_quote
1165 process_quote(quote_message);
1167 passed a message object for a quote, parses it creating an order basket
1168 and orderlines in the database
1169 updates the message's status to received in the database and adds the
1172 =head2 process_invoice
1174 process_invoice(invoice_message)
1176 passed a message object for an invoice, add the contained invoices
1177 and update the orderlines referred to in the invoice
1178 As an Edifact invoice is in effect a despatch note this receipts the
1179 appropriate quantities in the orders
1181 no meaningful return value
1183 =head2 process_ordrsp
1185 process_ordrsp(ordrsp_message)
1187 passed a message object for a supplier response, process the contents
1188 If an orderline is cancelled cancel the corresponding orderline in koha
1189 otherwise record the supplier message against it
1191 no meaningful return value
1193 =head2 create_edi_order
1195 create_edi_order( { parameter_hashref } )
1197 parameters must include basketno and ean
1199 branchcode can optionally be passed
1201 returns 1 on success undef otherwise
1203 if the parameter noingest is set the formatted order is returned
1204 and not saved in the database. This functionality is intended for debugging only
1206 =head2 receipt_items
1208 receipt_items( schema_obj, invoice_line, ordernumber, $quantity)
1210 receipts the items recorded on this invoice line
1212 no meaningful return
1214 =head2 transfer_items
1216 transfer_items(schema, invoice_line, originating_order, receiving_order, $quantity)
1218 Transfer the items covered by this invoice line from their original
1219 order to another order recording the partial fulfillment of the original
1222 no meaningful return
1224 =head2 get_edifact_ean
1226 $ean = get_edifact_ean();
1228 routine to return the ean.
1232 quote_item(lineitem, quote_message);
1234 Called by process_quote to handle an individual lineitem
1235 Generate the biblios and items if required and orderline linking to them
1237 Returns 1 on success undef on error
1239 Most usual cause of error is a line with no or incorrect budget codes
1240 which woild cause order creation to abort
1241 If other correct lines exist these are processed and the erroneous line os logged
1243 =head2 title_level_class
1245 classmark = title_level_class(edi_item)
1247 Trys to return a title level classmark from a quote message line
1248 Will return a dewey or lcc classmark if one exists according to the
1249 value in DefaultClassificationSource syspref
1251 If unable to returns the shelfmark or classification from the GIR segment
1253 If all else fails returns empty string
1255 =head2 _create_bib_from_quote
1257 marc_record_obj = _create_bib_from_quote(lineitem, quote)
1259 Returns a MARC::Record object based on the info in the quote's lineitem
1261 =head2 _create_item_from_quote
1263 item_hashref = _create_item_from_quote( lineitem, quote)
1265 returns a hashref representing the item fields specified in the quote
1267 =head2 _get_invoiced_price
1269 (price, price_tax_excluded) = _get_invoiced_price(line_object, $quantity)
1271 Returns an array of unitprice and unitprice_tax_excluded derived from the lineitem
1274 =head2 _discounted_price
1276 ecost = _discounted_price(discount, item_price, discounted_price)
1278 utility subroutine to return a price calculated from the
1279 vendors discount and quoted price
1280 if invoice has a field containing discounted price that is returned
1281 instead of recalculating
1283 =head2 _check_for_existing_bib
1285 (biblionumber, biblioitemnumber) = _check_for_existing_bib(isbn_or_ean)
1287 passed an isbn or ean attempts to locate a match bib
1288 On success returns biblionumber and biblioitemnumber
1289 On failure returns undefined/an empty list
1293 b = _get_budget(schema_obj, budget_code)
1295 Returns the Aqbudget object for the active budget given the passed budget_code
1296 or undefined if one does not exist
1300 Colin Campbell <colin.campbell@ptfs-europe.com>
1305 Copyright 2014,2015 PTFS-Europe Ltd
1306 This program is free software, You may redistribute it under
1307 under the terms of the GNU General Public License