Bug 32030: Link external packages with agreements
[koha-ffzg.git] / Koha / EDI.pm
index a692da1..954d32e 100644 (file)
@@ -21,27 +21,45 @@ use strict;
 use warnings;
 use base qw(Exporter);
 use utf8;
 use warnings;
 use base qw(Exporter);
 use utf8;
-use Carp;
+use Carp qw( carp );
 use English qw{ -no_match_vars };
 use Business::ISBN;
 use DateTime;
 use C4::Context;
 use Koha::Database;
 use English qw{ -no_match_vars };
 use Business::ISBN;
 use DateTime;
 use C4::Context;
 use Koha::Database;
-use Koha::DateUtils;
-use C4::Acquisition qw( NewBasket CloseBasket ModOrder);
+use Koha::DateUtils qw( dt_from_string );
+use C4::Acquisition qw( ModOrder NewBasket );
 use C4::Suggestions qw( ModSuggestion );
 use C4::Suggestions qw( ModSuggestion );
-use C4::Biblio qw( AddBiblio TransformKohaToMarc GetMarcBiblio GetFrameworkCode GetMarcFromKohaField );
+use C4::Biblio qw(
+    AddBiblio
+    GetFrameworkCode
+    GetMarcFromKohaField
+    TransformKohaToMarc
+);
 use Koha::Edifact::Order;
 use Koha::Edifact;
 use Koha::Edifact::Order;
 use Koha::Edifact;
+use C4::Log qw( logaction );
 use Log::Log4perl;
 use Log::Log4perl;
-use Text::Unidecode;
+use Text::Unidecode qw( unidecode );
+use Koha::Plugins; # Adds plugin dirs to @INC
 use Koha::Plugins::Handler;
 use Koha::Acquisition::Baskets;
 use Koha::Acquisition::Booksellers;
 
 our $VERSION = 1.1;
 use Koha::Plugins::Handler;
 use Koha::Acquisition::Baskets;
 use Koha::Acquisition::Booksellers;
 
 our $VERSION = 1.1;
-our @EXPORT_OK =
-  qw( process_quote process_invoice process_ordrsp create_edi_order get_edifact_ean );
+
+our (@ISA, @EXPORT_OK);
+BEGIN {
+    require Exporter;
+    @ISA = qw(Exporter);
+    @EXPORT_OK = qw(
+      process_quote
+      process_invoice
+      process_ordrsp
+      create_edi_order
+      get_edifact_ean
+    );
+};
 
 sub create_edi_order {
     my $parameters = shift;
 
 sub create_edi_order {
     my $parameters = shift;
@@ -211,12 +229,31 @@ sub process_invoice {
     my $logger = Log::Log4perl->get_logger();
     my $vendor_acct;
 
     my $logger = Log::Log4perl->get_logger();
     my $vendor_acct;
 
-    my $plugin = $invoice_message->edi_acct()->plugin();
+    my $plugin_class = $invoice_message->edi_acct()->plugin();
+
+    # Plugin has its own invoice processor, only run it and not the standard invoice processor below
+    if ( $plugin_class ) {
+        eval "require $plugin_class"; # Import the class, eval is needed because requiring a string doesn't work like requiring a bareword
+        my $plugin = $plugin_class->new();
+        if ( $plugin->can('edifact_process_invoice') ) {
+            Koha::Plugins::Handler->run(
+                {
+                    class  => $plugin_class,
+                    method => 'edifact_process_invoice',
+                    params => {
+                        invoice => $invoice_message,
+                    }
+                }
+            );
+            return;
+        }
+    }
+
     my $edi_plugin;
     my $edi_plugin;
-    if ( $plugin ) {
+    if ( $plugin_class ) {
         $edi_plugin = Koha::Plugins::Handler->run(
             {
         $edi_plugin = Koha::Plugins::Handler->run(
             {
-                class  => $plugin,
+                class  => $plugin_class,
                 method => 'edifact',
                 params => {
                     invoice_message => $invoice_message,
                 method => 'edifact',
                 params => {
                     invoice_message => $invoice_message,
@@ -275,12 +312,22 @@ sub process_invoice {
 
             foreach my $line ( @{$lines} ) {
                 my $ordernumber = $line->ordernumber;
 
             foreach my $line ( @{$lines} ) {
                 my $ordernumber = $line->ordernumber;
+                if (!$ordernumber ) {
+                   $logger->trace( "Skipping invoice line, no associated ordernumber" );
+                   next;
+                }
+
                 $logger->trace( "Receipting order:$ordernumber Qty: ",
                     $line->quantity );
 
                 my $order = $schema->resultset('Aqorder')->find($ordernumber);
                 $logger->trace( "Receipting order:$ordernumber Qty: ",
                     $line->quantity );
 
                 my $order = $schema->resultset('Aqorder')->find($ordernumber);
+                if (my $bib = $order->biblionumber) {
+                    my $b = $bib->biblionumber;
+                    my $id = $line->item_number_id;
+                    $logger->trace("Updating bib:$b id:$id");
+                }
 
 
-      # ModReceiveOrder does not validate that $ordernumber exists validate here
+                # ModReceiveOrder does not validate that $ordernumber exists validate here
                 if ($order) {
 
                     # check suggestions
                 if ($order) {
 
                     # check suggestions
@@ -297,40 +344,57 @@ sub process_invoice {
                             }
                         );
                     }
                             }
                         );
                     }
+                    # If quantity_invoiced is present use it in preference
+                    my $quantity = $line->quantity_invoiced;
+                    if (!$quantity) {
+                        $quantity = $line->quantity;
+                    }
 
 
-                    my $price = _get_invoiced_price($line);
+                    my ( $price, $price_excl_tax ) = _get_invoiced_price($line, $quantity);
+                    my $tax_rate = $line->tax_rate;
+                    if ($tax_rate && $tax_rate->{rate} != 0) {
+                       $tax_rate->{rate} /= 100;
+                    }
 
 
-                    if ( $order->quantity > $line->quantity ) {
+                    if ( $order->quantity > $quantity ) {
                         my $ordered = $order->quantity;
 
                         # part receipt
                         $order->orderstatus('partial');
                         my $ordered = $order->quantity;
 
                         # part receipt
                         $order->orderstatus('partial');
-                        $order->quantity( $ordered - $line->quantity );
+                        $order->quantity( $ordered - $quantity );
                         $order->update;
                         my $received_order = $order->copy(
                             {
                         $order->update;
                         my $received_order = $order->copy(
                             {
-                                ordernumber      => undef,
-                                quantity         => $line->quantity,
-                                quantityreceived => $line->quantity,
-                                orderstatus      => 'complete',
-                                unitprice        => $price,
-                                invoiceid        => $invoiceid,
-                                datereceived     => $msg_date,
+                                ordernumber            => undef,
+                                quantity               => $quantity,
+                                quantityreceived       => $quantity,
+                                orderstatus            => 'complete',
+                                unitprice              => $price,
+                                unitprice_tax_included => $price,
+                                unitprice_tax_excluded => $price_excl_tax,
+                                invoiceid              => $invoiceid,
+                                datereceived           => $msg_date,
+                                tax_rate_on_receiving  => $tax_rate->{rate},
+                                tax_value_on_receiving => $quantity * $price_excl_tax * $tax_rate->{rate},
                             }
                         );
                         transfer_items( $schema, $line, $order,
                             }
                         );
                         transfer_items( $schema, $line, $order,
-                            $received_order );
+                            $received_order, $quantity );
                         receipt_items( $schema, $line,
                         receipt_items( $schema, $line,
-                            $received_order->ordernumber );
+                            $received_order->ordernumber, $quantity );
                     }
                     else {    # simple receipt all copies on order
                     }
                     else {    # simple receipt all copies on order
-                        $order->quantityreceived( $line->quantity );
+                        $order->quantityreceived( $quantity );
                         $order->datereceived($msg_date);
                         $order->invoiceid($invoiceid);
                         $order->unitprice($price);
                         $order->datereceived($msg_date);
                         $order->invoiceid($invoiceid);
                         $order->unitprice($price);
+                        $order->unitprice_tax_excluded($price_excl_tax);
+                        $order->unitprice_tax_included($price);
+                        $order->tax_rate_on_receiving($tax_rate->{rate});
+                        $order->tax_value_on_receiving( $quantity * $price_excl_tax * $tax_rate->{rate});
                         $order->orderstatus('complete');
                         $order->update;
                         $order->orderstatus('complete');
                         $order->update;
-                        receipt_items( $schema, $line, $ordernumber );
+                        receipt_items( $schema, $line, $ordernumber, $quantity );
                     }
                 }
                 else {
                     }
                 }
                 else {
@@ -351,21 +415,33 @@ sub process_invoice {
 }
 
 sub _get_invoiced_price {
 }
 
 sub _get_invoiced_price {
-    my $line  = shift;
-    my $price = $line->price_net;
-    if ( !defined $price ) {  # no net price so generate it from lineitem amount
-        $price = $line->amt_lineitem;
-        if ( $price and $line->quantity > 1 ) {
-            $price /= $line->quantity;    # div line cost by qty
+    my $line       = shift;
+    my $qty        = shift;
+    my $line_total = $line->amt_total;
+    my $excl_tax   = $line->amt_lineitem;
+
+    # If no tax some suppliers omit the total owed
+    # If no total given calculate from cost exclusive of tax
+    # + tax amount (if present, sometimes omitted if 0 )
+    if ( !defined $line_total ) {
+        my $x = $line->amt_taxoncharge;
+        if ( !defined $x ) {
+            $x = 0;
         }
         }
+        $line_total = $excl_tax + $x;
+    }
+
+    # invoices give amounts per orderline, Koha requires that we store
+    # them per item
+    if ( $qty != 1 ) {
+        return ( $line_total / $qty, $excl_tax / $qty );
     }
     }
-    return $price;
+    return ( $line_total, $excl_tax );    # return as is for most common case
 }
 
 sub receipt_items {
 }
 
 sub receipt_items {
-    my ( $schema, $inv_line, $ordernumber ) = @_;
+    my ( $schema, $inv_line, $ordernumber, $quantity ) = @_;
     my $logger   = Log::Log4perl->get_logger();
     my $logger   = Log::Log4perl->get_logger();
-    my $quantity = $inv_line->quantity;
 
     # itemnumber is not a foreign key ??? makes this a bit cumbersome
     my @item_links = $schema->resultset('AqordersItem')->search(
 
     # itemnumber is not a foreign key ??? makes this a bit cumbersome
     my @item_links = $schema->resultset('AqordersItem')->search(
@@ -382,7 +458,7 @@ sub receipt_items {
                 "Cannot find aqorder item for $i :Order:$ordernumber");
             next;
         }
                 "Cannot find aqorder item for $i :Order:$ordernumber");
             next;
         }
-        my $b = $item->homebranch->branchcode;
+        my $b = $item->get_column('homebranch');
         if ( !exists $branch_map{$b} ) {
             $branch_map{$b} = [];
         }
         if ( !exists $branch_map{$b} ) {
             $branch_map{$b} = [];
         }
@@ -451,10 +527,9 @@ sub receipt_items {
 }
 
 sub transfer_items {
 }
 
 sub transfer_items {
-    my ( $schema, $inv_line, $order_from, $order_to ) = @_;
+    my ( $schema, $inv_line, $order_from, $order_to, $quantity ) = @_;
 
     # Transfer x items from the orig order to a completed partial order
 
     # Transfer x items from the orig order to a completed partial order
-    my $quantity = $inv_line->quantity;
     my $gocc     = 0;
     my %mapped_by_branch;
     while ( $gocc < $quantity ) {
     my $gocc     = 0;
     my %mapped_by_branch;
     while ( $gocc < $quantity ) {
@@ -480,7 +555,7 @@ sub transfer_items {
     foreach my $ilink (@item_links) {
         my $ino      = $ilink->itemnumber;
         my $item     = $schema->resultset('Item')->find( $ilink->itemnumber );
     foreach my $ilink (@item_links) {
         my $ino      = $ilink->itemnumber;
         my $item     = $schema->resultset('Item')->find( $ilink->itemnumber );
-        my $i_branch = $item->homebranch;
+        my $i_branch = $item->get_column('homebranch');
         if ( exists $mapped_by_branch{$i_branch}
             && $mapped_by_branch{$i_branch} > 0 )
         {
         if ( exists $mapped_by_branch{$i_branch}
             && $mapped_by_branch{$i_branch} > 0 )
         {
@@ -574,10 +649,21 @@ sub process_quote {
                     basketno => $b,
                 }
             );
                     basketno => $b,
                 }
             );
-            CloseBasket($b);
+            Koha::Acquisition::Baskets->find($b)->close;
+            # Log the approval
+            if (C4::Context->preference("AcquisitionLog")) {
+                my $approved = Koha::Acquisition::Baskets->find( $b );
+                logaction(
+                    'ACQUISITIONS',
+                    'APPROVE_BASKET',
+                    $b,
+                    to_json($approved->unblessed)
+                );
+            }
         }
     }
 
         }
     }
 
+
     return;
 }
 
     return;
 }
 
@@ -620,26 +706,37 @@ sub quote_item {
         }
         $order_quantity = 1;    # attempts to create an orderline for each gir
     }
         }
         $order_quantity = 1;    # attempts to create an orderline for each gir
     }
+    my $price  = $item->price_info;
+    # Howells do not send an info price but do have a gross price
+    if (!$price) {
+        $price = $item->price_gross;
+    }
     my $vendor = Koha::Acquisition::Booksellers->find( $quote->vendor_id );
 
     my $vendor = Koha::Acquisition::Booksellers->find( $quote->vendor_id );
 
+    # NB quote will not include tax info it only contains the list price
+    my $ecost = _discounted_price( $vendor->discount, $price, $item->price_info_inclusive );
+
     # database definitions should set some of these defaults but dont
     my $order_hash = {
         biblionumber       => $bib->{biblionumber},
         entrydate          => dt_from_string()->ymd(),
         basketno           => $basketno,
     # database definitions should set some of these defaults but dont
     my $order_hash = {
         biblionumber       => $bib->{biblionumber},
         entrydate          => dt_from_string()->ymd(),
         basketno           => $basketno,
-        listprice          => $item->price,
+        listprice          => $price,
         quantity           => $order_quantity,
         quantityreceived   => 0,
         order_vendornote   => q{},
         quantity           => $order_quantity,
         quantityreceived   => 0,
         order_vendornote   => q{},
-        order_internalnote => $order_note,
-        replacementprice   => $item->price,
-        rrp_tax_included   => $item->price,
-        rrp_tax_excluded   => $item->price,
-        ecost => _discounted_price( $quote->vendor->discount, $item->price ),
-        uncertainprice => 0,
-        sort1          => q{},
-        sort2          => q{},
-        currency       => $vendor->listprice(),
+        order_internalnote => q{},
+        replacementprice   => $price,
+        rrp_tax_included   => $price,
+        rrp_tax_excluded   => $price,
+        rrp                => $price,
+        ecost              => $ecost,
+        ecost_tax_included => $ecost,
+        ecost_tax_excluded => $ecost,
+        uncertainprice     => 0,
+        sort1              => q{},
+        sort2              => q{},
+        currency           => $vendor->listprice(),
     };
 
     # suppliers references
     };
 
     # suppliers references
@@ -667,7 +764,9 @@ sub quote_item {
             $txt .= $si;
             ++$occ;
         }
             $txt .= $si;
             ++$occ;
         }
-        $order_hash->{order_vendornote} = $txt;
+    }
+    if ($order_note) {
+        $order_hash->{order_vendornote} = $order_note;
     }
 
     if ( $item->internal_notes() ) {
     }
 
     if ( $item->internal_notes() ) {
@@ -719,8 +818,9 @@ sub quote_item {
             my $created = 0;
             while ( $created < $order_quantity ) {
                 $item_hash->{biblionumber} = $bib->{biblionumber};
             my $created = 0;
             while ( $created < $order_quantity ) {
                 $item_hash->{biblionumber} = $bib->{biblionumber};
-                my $item = Koha::Item->new( $item_hash );
-                my $itemnumber = $item->itemnumber;
+                $item_hash->{biblioitemnumber} = $bib->{biblioitemnumber};
+                my $kitem = Koha::Item->new( $item_hash )->store;
+                my $itemnumber = $kitem->itemnumber;
                 $logger->trace("Added item:$itemnumber");
                 $schema->resultset('AqordersItem')->create(
                     {
                 $logger->trace("Added item:$itemnumber");
                 $schema->resultset('AqordersItem')->create(
                     {
@@ -781,8 +881,6 @@ sub quote_item {
                     my $new_item = {
                         itype =>
                           $item->girfield( 'stock_category', $occurrence ),
                     my $new_item = {
                         itype =>
                           $item->girfield( 'stock_category', $occurrence ),
-                        location =>
-                          $item->girfield( 'collection_code', $occurrence ),
                         itemcallnumber =>
                           $item->girfield( 'shelfmark', $occurrence )
                           || $item->girfield( 'classification', $occurrence )
                         itemcallnumber =>
                           $item->girfield( 'shelfmark', $occurrence )
                           || $item->girfield( 'classification', $occurrence )
@@ -791,11 +889,15 @@ sub quote_item {
                           $item->girfield( 'branch', $occurrence ),
                         homebranch => $item->girfield( 'branch', $occurrence ),
                     };
                           $item->girfield( 'branch', $occurrence ),
                         homebranch => $item->girfield( 'branch', $occurrence ),
                     };
+
+                    my $lsq_field = C4::Context->preference('EdifactLSQ');
+                    $new_item->{$lsq_field} = $item->girfield( 'sequence_code', $occurrence );
+
                     if ( $new_item->{itype} ) {
                         $item_hash->{itype} = $new_item->{itype};
                     }
                     if ( $new_item->{itype} ) {
                         $item_hash->{itype} = $new_item->{itype};
                     }
-                    if ( $new_item->{location} ) {
-                        $item_hash->{location} = $new_item->{location};
+                    if ( $new_item->{$lsq_field} ) {
+                        $item_hash->{$lsq_field} = $new_item->{$lsq_field};
                     }
                     if ( $new_item->{itemcallnumber} ) {
                         $item_hash->{itemcallnumber} =
                     }
                     if ( $new_item->{itemcallnumber} ) {
                         $item_hash->{itemcallnumber} =
@@ -810,8 +912,9 @@ sub quote_item {
                     }
 
                     $item_hash->{biblionumber} = $bib->{biblionumber};
                     }
 
                     $item_hash->{biblionumber} = $bib->{biblionumber};
-                    my $item = Koha::Item->new( $item_hash );
-                    my $itemnumber = $item->itemnumber;
+                    $item_hash->{biblioitemnumber} = $bib->{biblioitemnumber};
+                    my $kitem = Koha::Item->new( $item_hash )->store;
+                    my $itemnumber = $kitem->itemnumber;
                     $logger->trace("New item $itemnumber added");
                     $schema->resultset('AqordersItem')->create(
                         {
                     $logger->trace("New item $itemnumber added");
                     $schema->resultset('AqordersItem')->create(
                         {
@@ -864,12 +967,10 @@ sub quote_item {
                         notforloan       => -1,
                         cn_sort          => q{},
                         cn_source        => 'ddc',
                         notforloan       => -1,
                         cn_sort          => q{},
                         cn_source        => 'ddc',
-                        price            => $item->price,
-                        replacementprice => $item->price,
+                        price            => $price,
+                        replacementprice => $price,
                         itype =>
                           $item->girfield( 'stock_category', $occurrence ),
                         itype =>
                           $item->girfield( 'stock_category', $occurrence ),
-                        location =>
-                          $item->girfield( 'collection_code', $occurrence ),
                         itemcallnumber =>
                           $item->girfield( 'shelfmark', $occurrence )
                           || $item->girfield( 'classification', $occurrence )
                         itemcallnumber =>
                           $item->girfield( 'shelfmark', $occurrence )
                           || $item->girfield( 'classification', $occurrence )
@@ -878,9 +979,12 @@ sub quote_item {
                           $item->girfield( 'branch', $occurrence ),
                         homebranch => $item->girfield( 'branch', $occurrence ),
                     };
                           $item->girfield( 'branch', $occurrence ),
                         homebranch => $item->girfield( 'branch', $occurrence ),
                     };
+                    my $lsq_field = C4::Context->preference('EdifactLSQ');
+                    $new_item->{$lsq_field} = $item->girfield( 'sequence_code', $occurrence );
                     $new_item->{biblionumber} = $bib->{biblionumber};
                     $new_item->{biblionumber} = $bib->{biblionumber};
-                    my $item = Koha::Item->new( $new_item );
-                    my $itemnumber = $item->itemnumber;
+                    $new_item->{biblioitemnumber} = $bib->{biblioitemnumber};
+                    my $kitem = Koha::Item->new( $new_item )->store;
+                    my $itemnumber = $kitem->itemnumber;
                     $logger->trace("New item $itemnumber added");
                     $schema->resultset('AqordersItem')->create(
                         {
                     $logger->trace("New item $itemnumber added");
                     $schema->resultset('AqordersItem')->create(
                         {
@@ -925,7 +1029,13 @@ sub get_edifact_ean {
 
 # We should not need to have a routine to do this here
 sub _discounted_price {
 
 # We should not need to have a routine to do this here
 sub _discounted_price {
-    my ( $discount, $price ) = @_;
+    my ( $discount, $price, $discounted_price ) = @_;
+    if (defined $discounted_price) {
+        return $discounted_price;
+    }
+    if (!$price) {
+        return 0;
+    }
     return $price - ( ( $discount * $price ) / 100 );
 }
 
     return $price - ( ( $discount * $price ) / 100 );
 }
 
@@ -1077,7 +1187,8 @@ sub _create_item_from_quote {
     $item_hash->{booksellerid} = $quote->vendor_id;
     $item_hash->{price}        = $item_hash->{replacementprice} = $item->price;
     $item_hash->{itype}        = $item->girfield('stock_category');
     $item_hash->{booksellerid} = $quote->vendor_id;
     $item_hash->{price}        = $item_hash->{replacementprice} = $item->price;
     $item_hash->{itype}        = $item->girfield('stock_category');
-    $item_hash->{location}     = $item->girfield('collection_code');
+    my $lsq_field = C4::Context->preference('EdifactLSQ');
+    $item_hash->{$lsq_field}     = $item->girfield('sequence_code');
 
     my $note = {};
 
 
     my $note = {};
 
@@ -1159,7 +1270,7 @@ Koha::EDI
 
 =head2 receipt_items
 
 
 =head2 receipt_items
 
-    receipt_items( schema_obj, invoice_line, ordernumber)
+    receipt_items( schema_obj, invoice_line, ordernumber, $quantity)
 
     receipts the items recorded on this invoice line
 
 
     receipts the items recorded on this invoice line
 
@@ -1167,7 +1278,7 @@ Koha::EDI
 
 =head2 transfer_items
 
 
 =head2 transfer_items
 
-    transfer_items(schema, invoice_line, originating_order, receiving_order)
+    transfer_items(schema, invoice_line, originating_order, receiving_order, $quantity)
 
     Transfer the items covered by this invoice line from their original
     order to another order recording the partial fulfillment of the original
 
     Transfer the items covered by this invoice line from their original
     order to another order recording the partial fulfillment of the original
@@ -1220,16 +1331,19 @@ Koha::EDI
 
 =head2 _get_invoiced_price
 
 
 =head2 _get_invoiced_price
 
-      _get_invoiced_price(line_object)
+      (price, price_tax_excluded) = _get_invoiced_price(line_object, $quantity)
 
 
-      Returns the net price or an equivalent calculated from line cost / qty
+      Returns an array of unitprice and unitprice_tax_excluded derived from the lineitem
+      monetary fields
 
 =head2 _discounted_price
 
 
 =head2 _discounted_price
 
-      ecost = _discounted_price(discount, item_price)
+      ecost = _discounted_price(discount, item_price, discounted_price)
 
       utility subroutine to return a price calculated from the
       vendors discount and quoted price
 
       utility subroutine to return a price calculated from the
       vendors discount and quoted price
+      if invoice has a field containing discounted price that is returned
+      instead of recalculating
 
 =head2 _check_for_existing_bib
 
 
 =head2 _check_for_existing_bib