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::Biblio qw( AddBiblio TransformKohaToMarc GetMarcBiblio GetFrameworkCode GetMarcFromKohaField );
34 use Koha::Edifact::Order;
38 use Koha::Plugins::Handler;
39 use Koha::Acquisition::Baskets;
40 use Koha::Acquisition::Booksellers;
44 qw( process_quote process_invoice process_ordrsp create_edi_order get_edifact_ean );
46 sub create_edi_order {
47 my $parameters = shift;
48 my $basketno = $parameters->{basketno};
49 my $ean = $parameters->{ean};
50 my $branchcode = $parameters->{branchcode};
51 my $noingest = $parameters->{noingest};
52 if ( !$basketno || !$ean ) {
53 carp 'create_edi_order called with no basketno or ean';
57 my $schema = Koha::Database->new()->schema();
59 my @orderlines = $schema->resultset('Aqorder')->search(
61 basketno => $basketno,
67 carp "No orderlines for basket $basketno";
71 my $vendor = $schema->resultset('VendorEdiAccount')->search(
73 vendor_id => $orderlines[0]->basketno->booksellerid->id,
77 my $ean_search_keys = { ean => $ean, };
79 $ean_search_keys->{branchcode} = $branchcode;
82 $schema->resultset('EdifactEan')->search($ean_search_keys)->single;
84 # If no branch specific each can be found, look for a default ean
86 $ean_obj = $schema->resultset('EdifactEan')->search(
94 my $dbh = C4::Context->dbh;
95 my $arr_ref = $dbh->selectcol_arrayref(
96 'select id from edifact_messages where basketno = ? and message_type = \'QUOTE\'',
99 my $response = @{$arr_ref} ? 1 : 0;
101 my $edifact_order_params = {
102 orderlines => \@orderlines,
105 is_response => $response,
109 if ( $vendor->plugin ) {
110 $edifact = Koha::Plugins::Handler->run(
112 class => $vendor->plugin,
113 method => 'edifact_order',
115 params => $edifact_order_params,
121 $edifact = Koha::Edifact::Order->new($edifact_order_params);
124 return unless $edifact;
126 my $order_file = $edifact->encode();
130 my $m = unidecode($order_file); # remove diacritics and non-latin chars
131 if ($noingest) { # allows scripts to produce test files
135 message_type => 'ORDERS',
137 vendor_id => $vendor->vendor_id,
139 basketno => $basketno,
140 filename => $edifact->filename(),
141 transfer_date => $edifact->msg_date_string(),
142 edi_acct => $vendor->id,
145 $schema->resultset('EdifactMessage')->create($order);
153 my $response_message = shift;
154 $response_message->status('processing');
155 $response_message->update;
156 my $schema = Koha::Database->new()->schema();
157 my $logger = Log::Log4perl->get_logger();
160 Koha::Edifact->new( { transmission => $response_message->raw_msg, } );
161 my $messages = $edi->message_array();
163 if ( @{$messages} ) {
164 foreach my $msg ( @{$messages} ) {
165 my $lines = $msg->lineitems();
166 foreach my $line ( @{$lines} ) {
167 my $ordernumber = $line->ordernumber();
169 # action cancelled:change_requested:no_action:accepted:not_found:recorded
170 my $action = $line->action_notification();
171 if ( $action eq 'cancelled' ) {
172 my $reason = $line->coded_orderline_text();
175 ordernumber => $ordernumber,
176 cancellationreason => $reason,
177 orderstatus => 'cancelled',
178 datecancellationprinted => dt_from_string()->ymd(),
182 else { # record order as due with possible further info
184 my $report = $line->coded_orderline_text();
185 my $date_avail = $line->availability_date();
188 $report .= " Available: $date_avail";
192 ordernumber => $ordernumber,
193 suppliers_report => $report,
201 $response_message->status('received');
202 $response_message->update;
206 sub process_invoice {
207 my $invoice_message = shift;
208 $invoice_message->status('processing');
209 $invoice_message->update;
210 my $schema = Koha::Database->new()->schema();
211 my $logger = Log::Log4perl->get_logger();
214 my $plugin = $invoice_message->edi_acct()->plugin();
217 $edi_plugin = Koha::Plugins::Handler->run(
222 invoice_message => $invoice_message,
223 transmission => $invoice_message->raw_msg,
229 my $edi = $edi_plugin ||
230 Koha::Edifact->new( { transmission => $invoice_message->raw_msg, } );
232 my $messages = $edi->message_array();
234 if ( @{$messages} ) {
236 # BGM contains an invoice number
237 foreach my $msg ( @{$messages} ) {
238 my $invoicenumber = $msg->docmsg_number();
239 my $shipmentcharge = $msg->shipment_charge();
240 my $msg_date = $msg->message_date;
241 my $tax_date = $msg->tax_point_date;
242 if ( !defined $tax_date || $tax_date !~ m/^\d{8}/xms ) {
243 $tax_date = $msg_date;
246 my $vendor_ean = $msg->supplier_ean;
247 if ( !defined $vendor_acct || $vendor_ean ne $vendor_acct->san ) {
248 $vendor_acct = $schema->resultset('VendorEdiAccount')->search(
254 if ( !$vendor_acct ) {
256 "Cannot find vendor with ean $vendor_ean for invoice $invoicenumber in $invoice_message->filename";
259 $invoice_message->edi_acct( $vendor_acct->id );
260 $logger->trace("Adding invoice:$invoicenumber");
261 my $new_invoice = $schema->resultset('Aqinvoice')->create(
263 invoicenumber => $invoicenumber,
264 booksellerid => $invoice_message->vendor_id,
265 shipmentdate => $msg_date,
266 billingdate => $tax_date,
267 shipmentcost => $shipmentcharge,
268 shipmentcost_budgetid => $vendor_acct->shipment_budget,
269 message_id => $invoice_message->id,
272 my $invoiceid = $new_invoice->invoiceid;
273 $logger->trace("Added as invoiceno :$invoiceid");
274 my $lines = $msg->lineitems();
276 foreach my $line ( @{$lines} ) {
277 my $ordernumber = $line->ordernumber;
278 $logger->trace( "Receipting order:$ordernumber Qty: ",
281 my $order = $schema->resultset('Aqorder')->find($ordernumber);
283 # ModReceiveOrder does not validate that $ordernumber exists validate here
287 my $s = $schema->resultset('Suggestion')->search(
289 biblionumber => $order->biblionumber->biblionumber,
295 suggestionid => $s->suggestionid,
296 STATUS => 'AVAILABLE',
300 # If quantity_invoiced is present use it in preference
301 my $quantity = $line->quantity_invoiced;
303 $quantity = $line->quantity;
306 my ( $price, $price_excl_tax ) = _get_invoiced_price($line, $quantity);
307 my $tax_rate = $line->tax_rate;
308 if ($tax_rate && $tax_rate->{rate} != 0) {
309 $tax_rate->{rate} /= 100;
312 if ( $order->quantity > $quantity ) {
313 my $ordered = $order->quantity;
316 $order->orderstatus('partial');
317 $order->quantity( $ordered - $quantity );
319 my $received_order = $order->copy(
321 ordernumber => undef,
322 quantity => $quantity,
323 quantityreceived => $quantity,
324 orderstatus => 'complete',
326 unitprice_tax_included => $price,
327 unitprice_tax_excluded => $price_excl_tax,
328 invoiceid => $invoiceid,
329 datereceived => $msg_date,
330 tax_rate_on_receiving => $tax_rate->{rate},
331 tax_value_on_receiving => $quantity * $price_excl_tax * $tax_rate->{rate},
334 transfer_items( $schema, $line, $order,
335 $received_order, $quantity );
336 receipt_items( $schema, $line,
337 $received_order->ordernumber, $quantity );
339 else { # simple receipt all copies on order
340 $order->quantityreceived( $quantity );
341 $order->datereceived($msg_date);
342 $order->invoiceid($invoiceid);
343 $order->unitprice($price);
344 $order->unitprice_tax_excluded($price_excl_tax);
345 $order->unitprice_tax_included($price);
346 $order->tax_rate_on_receiving($tax_rate->{rate});
347 $order->tax_value_on_receiving( $quantity * $price_excl_tax * $tax_rate->{rate});
348 $order->orderstatus('complete');
350 receipt_items( $schema, $line, $ordernumber, $quantity );
355 "No order found for $ordernumber Invoice:$invoicenumber"
365 $invoice_message->status('received');
366 $invoice_message->update; # status and basketno link
370 sub _get_invoiced_price {
373 my $line_total = $line->amt_total;
374 my $excl_tax = $line->amt_lineitem;
376 # If no tax some suppliers omit the total owed
377 # If no total given calculate from cost exclusive of tax
378 # + tax amount (if present, sometimes omitted if 0 )
379 if ( !defined $line_total ) {
380 my $x = $line->amt_taxoncharge;
384 $line_total = $excl_tax + $x;
387 # invoices give amounts per orderline, Koha requires that we store
390 return ( $line_total / $qty, $excl_tax / $qty );
392 return ( $line_total, $excl_tax ); # return as is for most common case
396 my ( $schema, $inv_line, $ordernumber, $quantity ) = @_;
397 my $logger = Log::Log4perl->get_logger();
399 # itemnumber is not a foreign key ??? makes this a bit cumbersome
400 my @item_links = $schema->resultset('AqordersItem')->search(
402 ordernumber => $ordernumber,
406 foreach my $ilink (@item_links) {
407 my $item = $schema->resultset('Item')->find( $ilink->itemnumber );
409 my $i = $ilink->itemnumber;
411 "Cannot find aqorder item for $i :Order:$ordernumber");
414 my $b = $item->homebranch->branchcode;
415 if ( !exists $branch_map{$b} ) {
416 $branch_map{$b} = [];
418 push @{ $branch_map{$b} }, $item;
421 # Handling for 'AcqItemSetSubfieldsWhenReceived'
425 if ( C4::Context->preference('AcqCreateItem') eq 'ordering' ) {
426 @affects = split q{\|},
427 C4::Context->preference("AcqItemSetSubfieldsWhenReceived");
429 my $order = Koha::Acquisition::Orders->find($ordernumber);
430 $biblionumber = $order->biblionumber;
431 my $frameworkcode = GetFrameworkCode($biblionumber);
432 ($itemfield) = GetMarcFromKohaField( 'items.itemnumber',
437 my $gir_occurrence = 0;
438 while ( $gir_occurrence < $quantity ) {
439 my $branch = $inv_line->girfield( 'branch', $gir_occurrence );
440 my $item = shift @{ $branch_map{$branch} };
442 my $barcode = $inv_line->girfield( 'barcode', $gir_occurrence );
443 if ( $barcode && !$item->barcode ) {
444 my $rs = $schema->resultset('Item')->search(
449 if ( $rs->count > 0 ) {
450 $logger->warn("Barcode $barcode is a duplicate");
454 $logger->trace("Adding barcode $barcode");
455 $item->barcode($barcode);
459 # Handling for 'AcqItemSetSubfieldsWhenReceived'
461 my $item_marc = C4::Items::GetMarcItem( $biblionumber, $item->itemnumber );
462 for my $affect (@affects) {
463 my ( $sf, $v ) = split q{=}, $affect, 2;
464 foreach ( $item_marc->field($itemfield) ) {
465 $_->update( $sf => $v );
468 C4::Items::ModItemFromMarc( $item_marc, $biblionumber, $item->itemnumber );
474 $logger->warn("Unmatched item at branch:$branch");
483 my ( $schema, $inv_line, $order_from, $order_to, $quantity ) = @_;
485 # Transfer x items from the orig order to a completed partial order
487 my %mapped_by_branch;
488 while ( $gocc < $quantity ) {
489 my $branch = $inv_line->girfield( 'branch', $gocc );
490 if ( !exists $mapped_by_branch{$branch} ) {
491 $mapped_by_branch{$branch} = 1;
494 $mapped_by_branch{$branch}++;
498 my $logger = Log::Log4perl->get_logger();
499 my $o1 = $order_from->ordernumber;
500 my $o2 = $order_to->ordernumber;
501 $logger->warn("transferring $quantity copies from order $o1 to order $o2");
503 my @item_links = $schema->resultset('AqordersItem')->search(
505 ordernumber => $order_from->ordernumber,
508 foreach my $ilink (@item_links) {
509 my $ino = $ilink->itemnumber;
510 my $item = $schema->resultset('Item')->find( $ilink->itemnumber );
511 my $i_branch = $item->homebranch;
512 if ( exists $mapped_by_branch{$i_branch}
513 && $mapped_by_branch{$i_branch} > 0 )
515 $ilink->ordernumber( $order_to->ordernumber );
518 --$mapped_by_branch{$i_branch};
519 $logger->warn("Transferred item $item");
522 $logger->warn("Skipped item $item");
524 if ( $quantity < 1 ) {
535 $quote->status('processing');
538 my $edi = Koha::Edifact->new( { transmission => $quote->raw_msg, } );
540 my $messages = $edi->message_array();
541 my $process_errors = 0;
542 my $logger = Log::Log4perl->get_logger();
543 my $schema = Koha::Database->new()->schema();
544 my $message_count = 0;
545 my @added_baskets; # if auto & multiple baskets need to order all
547 if ( @{$messages} && $quote->vendor_id ) {
548 foreach my $msg ( @{$messages} ) {
551 NewBasket( $quote->vendor_id, 0, $quote->filename, q{},
553 push @added_baskets, $basketno;
554 if ( $message_count > 1 ) {
555 my $m_filename = $quote->filename;
556 $m_filename .= "_$message_count";
557 $schema->resultset('EdifactMessage')->create(
559 message_type => $quote->message_type,
560 transfer_date => $quote->transfer_date,
561 vendor_id => $quote->vendor_id,
562 edi_acct => $quote->edi_acct,
564 basketno => $basketno,
566 filename => $m_filename,
571 $quote->basketno($basketno);
573 $logger->trace("Created basket :$basketno");
574 my $items = $msg->lineitems();
575 my $refnum = $msg->message_refno;
577 for my $item ( @{$items} ) {
578 if ( !quote_item( $item, $quote, $basketno ) ) {
584 my $status = 'received';
585 if ($process_errors) {
589 $quote->status($status);
590 $quote->update; # status and basketno link
591 # Do we automatically generate orders for this vendor
592 my $v = $schema->resultset('VendorEdiAccount')->search(
594 vendor_id => $quote->vendor_id,
597 if ( $v->auto_orders ) {
598 for my $b (@added_baskets) {
601 ean => $messages->[0]->buyer_ean,
613 my ( $item, $quote, $basketno ) = @_;
615 my $schema = Koha::Database->new()->schema();
616 my $logger = Log::Log4perl->get_logger();
618 # $basketno is the return from AddBasket in the calling routine
619 # So this call should not fail unless that has
620 my $basket = Koha::Acquisition::Baskets->find( $basketno );
622 $logger->error('Skipping order creation no valid basketno');
625 $logger->trace( 'Checking db for matches with ', $item->item_number_id() );
626 my $bib = _check_for_existing_bib( $item->item_number_id() );
627 if ( !defined $bib ) {
629 my $bib_record = _create_bib_from_quote( $item, $quote );
630 ( $bib->{biblionumber}, $bib->{biblioitemnumber} ) =
631 AddBiblio( $bib_record, q{} );
632 $logger->trace("New biblio added $bib->{biblionumber}");
635 $logger->trace("Match found: $bib->{biblionumber}");
638 # Create an orderline
639 my $order_note = $item->{orderline_free_text};
641 my $order_quantity = $item->quantity();
642 my $gir_count = $item->number_of_girs();
643 $order_quantity ||= 1; # quantity not necessarily present
644 if ( $gir_count > 1 ) {
645 if ( $gir_count != $order_quantity ) {
647 "Order for $order_quantity items, $gir_count segments present");
649 $order_quantity = 1; # attempts to create an orderline for each gir
651 my $price = $item->price_info;
652 # Howells do not send an info price but do have a gross price
654 $price = $item->price_gross;
656 my $vendor = Koha::Acquisition::Booksellers->find( $quote->vendor_id );
658 # NB quote will not include tax info it only contains the list price
659 my $ecost = _discounted_price( $vendor->discount, $price, $item->price_info_inclusive );
661 # database definitions should set some of these defaults but dont
663 biblionumber => $bib->{biblionumber},
664 entrydate => dt_from_string()->ymd(),
665 basketno => $basketno,
667 quantity => $order_quantity,
668 quantityreceived => 0,
669 order_vendornote => q{},
670 order_internalnote => $order_note,
671 replacementprice => $price,
672 rrp_tax_included => $price,
673 rrp_tax_excluded => $price,
676 ecost_tax_included => $ecost,
677 ecost_tax_excluded => $ecost,
681 currency => $vendor->listprice(),
684 # suppliers references
685 if ( $item->reference() ) {
686 $order_hash->{suppliers_reference_number} = $item->reference;
687 $order_hash->{suppliers_reference_qualifier} = 'QLI';
689 elsif ( $item->orderline_reference_number() ) {
690 $order_hash->{suppliers_reference_number} =
691 $item->orderline_reference_number;
692 $order_hash->{suppliers_reference_qualifier} = 'SLI';
694 if ( $item->item_number_id ) { # suppliers ean
695 $order_hash->{line_item_id} = $item->item_number_id;
698 if ( $item->girfield('servicing_instruction') ) {
702 while ( $si = $item->girfield( 'servicing_instruction', $occ ) ) {
709 $order_hash->{order_vendornote} = $txt;
712 if ( $item->internal_notes() ) {
713 if ( $order_hash->{order_internalnote} ) { # more than ''
714 $order_hash->{order_internalnote} .= q{ };
716 $order_hash->{order_internalnote} .= $item->internal_notes;
719 my $budget = _get_budget( $schema, $item->girfield('fund_allocation') );
723 if ( $item->quantity > 1 ) {
724 carp 'Skipping line with no budget info';
725 $logger->trace('girfield skipped for invalid budget');
729 carp 'Skipping line with no budget info';
730 $logger->trace('orderline skipped for invalid budget');
740 $order_hash->{budget_id} = $budget->budget_id;
741 my $first_order = $schema->resultset('Aqorder')->create($order_hash);
742 my $o = $first_order->ordernumber();
743 $logger->trace("Order created :$o");
745 # should be done by database settings
746 $first_order->parent_ordernumber( $first_order->ordernumber() );
747 $first_order->update();
749 # add to $budgets to prevent duplicate orderlines
750 $budgets{ $budget->budget_id } = '1';
752 # record ordernumber against budget
753 $ordernumber{ $budget->budget_id } = $o;
755 if ( C4::Context->preference('AcqCreateItem') eq 'ordering' ) {
756 $item_hash = _create_item_from_quote( $item, $quote );
759 while ( $created < $order_quantity ) {
760 $item_hash->{biblionumber} = $bib->{biblionumber};
761 $item_hash->{biblioitemnumber} = $bib->{biblioitemnumber};
762 my $kitem = Koha::Item->new( $item_hash )->store;
763 my $itemnumber = $kitem->itemnumber;
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};
852 $item_hash->{biblionumber} = $bib->{biblionumber};
853 $item_hash->{biblioitemnumber} = $bib->{biblioitemnumber};
854 my $kitem = Koha::Item->new( $item_hash )->store;
855 my $itemnumber = $kitem->itemnumber;
856 $logger->trace("New item $itemnumber added");
857 $schema->resultset('AqordersItem')->create(
859 ordernumber => $new_order->ordernumber,
860 itemnumber => $itemnumber,
865 $item->girfield( 'library_rotation_plan', $occurrence );
868 Koha::StockRotationRotas->find( { title => $lrp },
869 { key => 'stockrotationrotas_title' } );
871 $rota->add_item($itemnumber);
872 $logger->trace("Item added to rota $rota->id");
876 "No rota found matching $lrp in orderline");
884 # increment quantity in orderline for EXISTING budget in $budgets
886 my $row = $schema->resultset('Aqorder')->find(
888 ordernumber => $ordernumber{ $budget->budget_id }
892 my $qty = $row->quantity;
901 # Do not use the basket level value as it is always NULL
902 # See calling subs call to AddBasket
903 if ( C4::Context->preference('AcqCreateItem') eq 'ordering' ) {
909 replacementprice => $price,
911 $item->girfield( 'stock_category', $occurrence ),
913 $item->girfield( 'collection_code', $occurrence ),
915 $item->girfield( 'shelfmark', $occurrence )
916 || $item->girfield( 'classification', $occurrence )
917 || $item_hash->{itemcallnumber},
919 $item->girfield( 'branch', $occurrence ),
920 homebranch => $item->girfield( 'branch', $occurrence ),
922 $new_item->{biblionumber} = $bib->{biblionumber};
923 $new_item->{biblioitemnumber} = $bib->{biblioitemnumber};
924 my $kitem = Koha::Item->new( $new_item )->store;
925 my $itemnumber = $kitem->itemnumber;
926 $logger->trace("New item $itemnumber added");
927 $schema->resultset('AqordersItem')->create(
929 ordernumber => $ordernumber{ $budget->budget_id },
930 itemnumber => $itemnumber,
935 $item->girfield( 'library_rotation_plan', $occurrence );
938 Koha::StockRotationRotas->find( { title => $lrp },
939 { key => 'stockrotationrotas_title' } );
941 $rota->add_item($itemnumber);
942 $logger->trace("Item added to rota $rota->id");
946 "No rota found matching $lrp in orderline");
959 sub get_edifact_ean {
961 my $dbh = C4::Context->dbh;
963 my $eans = $dbh->selectcol_arrayref('select ean from edifact_ean');
968 # We should not need to have a routine to do this here
969 sub _discounted_price {
970 my ( $discount, $price, $discounted_price ) = @_;
971 if (defined $discounted_price) {
972 return $discounted_price;
977 return $price - ( ( $discount * $price ) / 100 );
980 sub _check_for_existing_bib {
983 my $search_isbn = $isbn;
984 $search_isbn =~ s/^\s*/%/xms;
985 $search_isbn =~ s/\s*$/%/xms;
986 my $dbh = C4::Context->dbh;
987 my $sth = $dbh->prepare(
988 'select biblionumber, biblioitemnumber from biblioitems where isbn like ?',
991 $dbh->selectall_arrayref( $sth, { Slice => {} }, $search_isbn );
992 if ( @{$tuple_arr} ) {
993 return $tuple_arr->[0];
995 elsif ( length($isbn) == 13 && $isbn !~ /^97[89]/ ) {
996 my $tarr = $dbh->selectall_arrayref(
997 'select biblionumber, biblioitemnumber from biblioitems where ean = ?',
1007 $isbn =~ s/\-//xmsg;
1008 if ( $isbn =~ m/(\d{13})/xms ) {
1009 my $b_isbn = Business::ISBN->new($1);
1010 if ( $b_isbn && $b_isbn->is_valid && $b_isbn->as_isbn10 ) {
1011 $search_isbn = $b_isbn->as_isbn10->as_string( [] );
1015 elsif ( $isbn =~ m/(\d{9}[xX]|\d{10})/xms ) {
1016 my $b_isbn = Business::ISBN->new($1);
1017 if ( $b_isbn && $b_isbn->is_valid ) {
1018 $search_isbn = $b_isbn->as_isbn13->as_string( [] );
1023 $search_isbn = "%$search_isbn%";
1025 $dbh->selectall_arrayref( $sth, { Slice => {} }, $search_isbn );
1026 if ( @{$tuple_arr} ) {
1027 return $tuple_arr->[0];
1034 # returns a budget obj or undef
1035 # fact we need this shows what a mess Acq API is
1037 my ( $schema, $budget_code ) = @_;
1038 my $period_rs = $schema->resultset('Aqbudgetperiod')->search(
1040 budget_period_active => 1,
1044 # db does not ensure budget code is unque
1045 return $schema->resultset('Aqbudget')->single(
1047 budget_code => $budget_code,
1049 { -in => $period_rs->get_column('budget_period_id')->as_query },
1054 # try to get title level classification from incoming quote
1055 sub title_level_class {
1058 my $default_scheme = C4::Context->preference('DefaultClassificationSource');
1059 if ( $default_scheme eq 'ddc' ) {
1060 $class = $item->dewey_class();
1062 elsif ( $default_scheme eq 'lcc' ) {
1063 $class = $item->lc_class();
1067 $item->girfield('shelfmark')
1068 || $item->girfield('classification')
1074 sub _create_bib_from_quote {
1076 #TBD we should flag this for updating from an external source
1077 #As biblio (&biblioitems) has no candidates flag in order
1078 my ( $item, $quote ) = @_;
1079 my $itemid = $item->item_number_id;
1080 my $defalt_classification_source =
1081 C4::Context->preference('DefaultClassificationSource');
1083 'biblioitems.cn_source' => $defalt_classification_source,
1084 'items.cn_source' => $defalt_classification_source,
1085 'items.notforloan' => -1,
1086 'items.cn_sort' => q{},
1088 $bib_hash->{'biblio.seriestitle'} = $item->series;
1090 $bib_hash->{'biblioitems.publishercode'} = $item->publisher;
1091 $bib_hash->{'biblioitems.publicationyear'} =
1092 $bib_hash->{'biblio.copyrightdate'} = $item->publication_date;
1094 $bib_hash->{'biblio.title'} = $item->title;
1095 $bib_hash->{'biblio.author'} = $item->author;
1096 $bib_hash->{'biblioitems.isbn'} = $item->item_number_id;
1097 $bib_hash->{'biblioitems.itemtype'} = $item->girfield('stock_category');
1099 # If we have a 13 digit id we are assuming its an ean
1100 # (it may also be an isbn or issn)
1101 if ( $itemid =~ /^\d{13}$/ ) {
1102 $bib_hash->{'biblioitems.ean'} = $itemid;
1103 if ( $itemid =~ /^977/ ) {
1104 $bib_hash->{'biblioitems.issn'} = $itemid;
1107 for my $key ( keys %{$bib_hash} ) {
1108 if ( !defined $bib_hash->{$key} ) {
1109 delete $bib_hash->{$key};
1112 return TransformKohaToMarc($bib_hash);
1116 sub _create_item_from_quote {
1117 my ( $item, $quote ) = @_;
1118 my $defalt_classification_source =
1119 C4::Context->preference('DefaultClassificationSource');
1121 cn_source => $defalt_classification_source,
1125 $item_hash->{booksellerid} = $quote->vendor_id;
1126 $item_hash->{price} = $item_hash->{replacementprice} = $item->price;
1127 $item_hash->{itype} = $item->girfield('stock_category');
1128 $item_hash->{location} = $item->girfield('collection_code');
1132 $item_hash->{itemcallnumber} =
1133 $item->girfield('shelfmark')
1134 || $item->girfield('classification')
1135 || title_level_class($item);
1137 my $branch = $item->girfield('branch');
1138 $item_hash->{holdingbranch} = $item_hash->{homebranch} = $branch;
1151 Module exporting subroutines used in EDI processing for Koha
1155 Subroutines called by batch processing to handle Edifact
1156 messages of various types and related utilities
1160 These routines should really be methods of some object.
1161 get_edifact_ean is a stopgap which should be replaced
1165 =head2 process_quote
1167 process_quote(quote_message);
1169 passed a message object for a quote, parses it creating an order basket
1170 and orderlines in the database
1171 updates the message's status to received in the database and adds the
1174 =head2 process_invoice
1176 process_invoice(invoice_message)
1178 passed a message object for an invoice, add the contained invoices
1179 and update the orderlines referred to in the invoice
1180 As an Edifact invoice is in effect a despatch note this receipts the
1181 appropriate quantities in the orders
1183 no meaningful return value
1185 =head2 process_ordrsp
1187 process_ordrsp(ordrsp_message)
1189 passed a message object for a supplier response, process the contents
1190 If an orderline is cancelled cancel the corresponding orderline in koha
1191 otherwise record the supplier message against it
1193 no meaningful return value
1195 =head2 create_edi_order
1197 create_edi_order( { parameter_hashref } )
1199 parameters must include basketno and ean
1201 branchcode can optionally be passed
1203 returns 1 on success undef otherwise
1205 if the parameter noingest is set the formatted order is returned
1206 and not saved in the database. This functionality is intended for debugging only
1208 =head2 receipt_items
1210 receipt_items( schema_obj, invoice_line, ordernumber, $quantity)
1212 receipts the items recorded on this invoice line
1214 no meaningful return
1216 =head2 transfer_items
1218 transfer_items(schema, invoice_line, originating_order, receiving_order, $quantity)
1220 Transfer the items covered by this invoice line from their original
1221 order to another order recording the partial fulfillment of the original
1224 no meaningful return
1226 =head2 get_edifact_ean
1228 $ean = get_edifact_ean();
1230 routine to return the ean.
1234 quote_item(lineitem, quote_message);
1236 Called by process_quote to handle an individual lineitem
1237 Generate the biblios and items if required and orderline linking to them
1239 Returns 1 on success undef on error
1241 Most usual cause of error is a line with no or incorrect budget codes
1242 which woild cause order creation to abort
1243 If other correct lines exist these are processed and the erroneous line os logged
1245 =head2 title_level_class
1247 classmark = title_level_class(edi_item)
1249 Trys to return a title level classmark from a quote message line
1250 Will return a dewey or lcc classmark if one exists according to the
1251 value in DefaultClassificationSource syspref
1253 If unable to returns the shelfmark or classification from the GIR segment
1255 If all else fails returns empty string
1257 =head2 _create_bib_from_quote
1259 marc_record_obj = _create_bib_from_quote(lineitem, quote)
1261 Returns a MARC::Record object based on the info in the quote's lineitem
1263 =head2 _create_item_from_quote
1265 item_hashref = _create_item_from_quote( lineitem, quote)
1267 returns a hashref representing the item fields specified in the quote
1269 =head2 _get_invoiced_price
1271 (price, price_tax_excluded) = _get_invoiced_price(line_object, $quantity)
1273 Returns an array of unitprice and unitprice_tax_excluded derived from the lineitem
1276 =head2 _discounted_price
1278 ecost = _discounted_price(discount, item_price, discounted_price)
1280 utility subroutine to return a price calculated from the
1281 vendors discount and quoted price
1282 if invoice has a field containing discounted price that is returned
1283 instead of recalculating
1285 =head2 _check_for_existing_bib
1287 (biblionumber, biblioitemnumber) = _check_for_existing_bib(isbn_or_ean)
1289 passed an isbn or ean attempts to locate a match bib
1290 On success returns biblionumber and biblioitemnumber
1291 On failure returns undefined/an empty list
1295 b = _get_budget(schema_obj, budget_code)
1297 Returns the Aqbudget object for the active budget given the passed budget_code
1298 or undefined if one does not exist
1302 Colin Campbell <colin.campbell@ptfs-europe.com>
1307 Copyright 2014,2015 PTFS-Europe Ltd
1308 This program is free software, You may redistribute it under
1309 under the terms of the GNU General Public License