Bug 18736: Calculate tax depending on rounding
[koha.git] / C4 / Acquisition.pm
index 384b598..4bf8c17 100644 (file)
@@ -28,9 +28,11 @@ use C4::Contract;
 use C4::Debug;
 use C4::Templates qw(gettemplate);
 use Koha::DateUtils qw( dt_from_string output_pref );
+use Koha::Acquisition::Baskets;
 use Koha::Acquisition::Booksellers;
 use Koha::Acquisition::Orders;
 use Koha::Biblios;
+use Koha::Exceptions;
 use Koha::Items;
 use Koha::Number::Price;
 use Koha::Libraries;
@@ -83,8 +85,6 @@ BEGIN {
         &DelInvoice
         &MergeInvoices
 
-        &GetItemnumbersFromOrder
-
         &AddClaim
         &GetBiblioCountByBasketno
 
@@ -93,6 +93,9 @@ BEGIN {
         &NotifyOrderUsers
 
         &FillWithDefaultValues
+
+        &get_rounded_price
+        &get_rounding_sql
     );
 }
 
@@ -120,28 +123,6 @@ sub GetOrderFromItemnumber {
 
 }
 
-# Returns the itemnumber(s) associated with the ordernumber given in parameter
-sub GetItemnumbersFromOrder {
-    my ($ordernumber) = @_;
-    my $dbh          = C4::Context->dbh;
-    my $query        = "SELECT itemnumber FROM aqorders_items WHERE ordernumber=?";
-    my $sth = $dbh->prepare($query);
-    $sth->execute($ordernumber);
-    my @tab;
-
-    while (my $order = $sth->fetchrow_hashref) {
-    push @tab, $order->{'itemnumber'};
-    }
-
-    return @tab;
-
-}
-
-
-
-
-
-
 =head1 NAME
 
 C4::Acquisition - Koha functions for dealing with orders and acquisitions
@@ -293,7 +274,7 @@ sub GetBasketAsCSV {
     my @rows;
     if ($csv_profile_id) {
         my $csv_profile = Koha::CsvProfiles->find( $csv_profile_id );
-        die "There is no valid csv profile given" unless $csv_profile;
+        Koha::Exceptions::ObjectNotFound->throw( 'There is no valid csv profile given') unless $csv_profile;
 
         my $csv = Text::CSV_XS->new({'quote_char'=>'"','escape_char'=>'"','sep_char'=>$csv_profile->csv_separator,'binary'=>1});
         my $csv_profile_content = $csv_profile->content;
@@ -700,7 +681,7 @@ sub GetBasketsInfosByBookseller {
           COUNT(DISTINCT aqorders.biblionumber) AS total_biblios,
           SUM(
             IF(aqorders.datereceived IS NULL
-              AND aqorders.datecancellationprinted IS NULL
+              AND (aqorders.datecancellationprinted IS NULL OR aqorders.datecancellationprinted='0000-00-00')
             , aqorders.quantity
             , 0)
           ) AS expected_items
@@ -708,10 +689,18 @@ sub GetBasketsInfosByBookseller {
           LEFT JOIN aqorders ON aqorders.basketno = aqbasket.basketno
         WHERE booksellerid = ?};
 
+    $query.=" GROUP BY aqbasket.basketno, aqbasket.basketname, aqbasket.note, aqbasket.booksellernote, aqbasket.contractnumber, aqbasket.creationdate, aqbasket.closedate, aqbasket.booksellerid, aqbasket.authorisedby, aqbasket.booksellerinvoicenumber, aqbasket.basketgroupid, aqbasket.deliveryplace, aqbasket.billingplace, aqbasket.branch, aqbasket.is_standing, aqbasket.create_items";
+
     unless ( $allbaskets ) {
-        $query.=" AND (closedate IS NULL OR (aqorders.quantity > aqorders.quantityreceived AND datecancellationprinted IS NULL))";
+        # Don't show the basket if it's NOT CLOSED or is FULLY RECEIVED
+        $query.=" HAVING (closedate IS NULL OR (
+          SUM(
+            IF(aqorders.datereceived IS NULL
+              AND (aqorders.datecancellationprinted IS NULL OR aqorders.datecancellationprinted='0000-00-00')
+            , aqorders.quantity
+            , 0)
+            ) > 0))"
     }
-    $query.=" GROUP BY aqbasket.basketno, aqbasket.basketname, aqbasket.note, aqbasket.booksellernote, aqbasket.contractnumber, aqbasket.creationdate, aqbasket.closedate, aqbasket.booksellerid, aqbasket.authorisedby, aqbasket.booksellerinvoicenumber, aqbasket.basketgroupid, aqbasket.deliveryplace, aqbasket.billingplace, aqbasket.branch, aqbasket.is_standing, aqbasket.create_items";
 
     my $sth = $dbh->prepare($query);
     $sth->execute($supplierid);
@@ -1458,22 +1447,25 @@ sub ModReceiveOrder {
             UPDATE aqorders
             SET quantity = ?,
                 orderstatus = 'partial'|;
-        $query .= q|, order_internalnote = ?| if defined $order->{order_internalnote};
         $query .= q| WHERE ordernumber = ?|;
         my $sth = $dbh->prepare($query);
 
         $sth->execute(
             ( $is_standing ? 1 : ($order->{quantity} - $quantrec) ),
-            ( defined $order->{order_internalnote} ? $order->{order_internalnote} : () ),
             $order->{ordernumber}
         );
 
+        if ( not $order->{subscriptionid} && defined $order->{order_internalnote} ) {
+            $dbh->do(q|UPDATE aqorders
+                SET order_internalnote = ?|, {}, $order->{order_internalnote});
+        }
+
         # Recalculate tax_value
         $dbh->do(q|
             UPDATE aqorders
             SET
-                tax_value_on_ordering = quantity * ecost_tax_excluded * tax_rate_on_ordering,
-                tax_value_on_receiving = quantity * unitprice_tax_excluded * tax_rate_on_receiving
+                tax_value_on_ordering = quantity * | . get_rounding_sql(q|ecost_tax_excluded|) . q| * tax_rate_on_ordering,
+                tax_value_on_receiving = quantity * | . get_rounding_sql(q|unitprice_tax_excluded|) . q| * tax_rate_on_receiving
             WHERE ordernumber = ?
         |, undef, $order->{ordernumber});
 
@@ -1485,8 +1477,8 @@ sub ModReceiveOrder {
         $order->{tax_rate_on_ordering} //= 0;
         $order->{unitprice_tax_excluded} //= 0;
         $order->{tax_rate_on_receiving} //= 0;
-        $order->{tax_value_on_ordering} = $order->{quantity} * $order->{ecost_tax_excluded} * $order->{tax_rate_on_ordering};
-        $order->{tax_value_on_receiving} = $order->{quantity} * $order->{unitprice_tax_excluded} * $order->{tax_rate_on_receiving};
+        $order->{tax_value_on_ordering} = $order->{quantity} * get_rounded_price($order->{ecost_tax_excluded}) * $order->{tax_rate_on_ordering};
+        $order->{tax_value_on_receiving} = $order->{quantity} * get_rounded_price($order->{unitprice_tax_excluded}) * $order->{tax_rate_on_receiving};
         $order->{datereceived} = $datereceived;
         $order->{invoiceid} = $invoice->{invoiceid};
         $order->{orderstatus} = 'complete';
@@ -1599,8 +1591,8 @@ sub CancelReceipt {
 
     my $parent_ordernumber = $order->{'parent_ordernumber'};
 
-    my @itemnumbers = GetItemnumbersFromOrder( $ordernumber );
     my $order_obj = Koha::Acquisition::Orders->find( $ordernumber ); # FIXME rewrite all this subroutine using this object
+    my @itemnumbers = $order_obj->items->get_column('itemnumber');
 
     if($parent_ordernumber == $ordernumber || not $parent_ordernumber) {
         # The order line has no parent, just mark it as not received
@@ -1656,8 +1648,8 @@ sub CancelReceipt {
         $dbh->do(q|
             UPDATE aqorders
             SET
-                tax_value_on_ordering = quantity * ecost_tax_excluded * tax_rate_on_ordering,
-                tax_value_on_receiving = quantity * unitprice_tax_excluded * tax_rate_on_receiving
+                tax_value_on_ordering = quantity * | . get_rounding_sql(q|ecost_tax_excluded|) . q| * tax_rate_on_ordering,
+                tax_value_on_receiving = quantity * | . get_rounding_sql(q|unitprice_tax_excluded|) . q| * tax_rate_on_receiving
             WHERE ordernumber = ?
         |, undef, $parent_ordernumber);
 
@@ -1676,7 +1668,7 @@ sub CancelReceipt {
         my @affects = split q{\|}, C4::Context->preference("AcqItemSetSubfieldsWhenReceiptIsCancelled");
         if ( @affects ) {
             for my $in ( @itemnumbers ) {
-                my $item = Koha::Items->find( $in );
+                my $item = Koha::Items->find( $in ); # FIXME We do not need that, we already have Koha::Items from $order_obj->items
                 my $biblio = $item->biblio;
                 my ( $itemfield ) = GetMarcFromKohaField( 'items.itemnumber', $biblio->frameworkcode );
                 my $item_marc = C4::Items::GetMarcItem( $biblio->biblionumber, $in );
@@ -1698,7 +1690,7 @@ sub _cancel_items_receipt {
     my ( $order, $parent_ordernumber ) = @_;
     $parent_ordernumber ||= $order->ordernumber;
 
-    my @itemnumbers = GetItemnumbersFromOrder($order->ordernumber); # FIXME Must be $order->items
+    my $items = $order->items;
     if ( $order->basket->effective_create_items eq 'receiving' ) {
         # Remove items that were created at receipt
         my $query = qq{
@@ -1708,13 +1700,13 @@ sub _cancel_items_receipt {
         };
         my $dbh = C4::Context->dbh;
         my $sth = $dbh->prepare($query);
-        foreach my $itemnumber (@itemnumbers) {
-            $sth->execute($itemnumber, $itemnumber);
+        while ( my $item = $items->next ) {
+            $sth->execute($item->itemnumber, $item->itemnumber);
         }
     } else {
         # Update items
-        foreach my $itemnumber (@itemnumbers) {
-            ModItemOrder($itemnumber, $parent_ordernumber);
+        while ( my $item = $items->next ) {
+            ModItemOrder($item->itemnumber, $parent_ordernumber);
         }
     }
 }
@@ -1918,9 +1910,10 @@ sub DelOrder {
     }
     $sth->finish;
 
-    my @itemnumbers = GetItemnumbersFromOrder( $ordernumber );
-    foreach my $itemnumber (@itemnumbers){
-        my $delcheck = C4::Items::DelItemCheck( $bibnum, $itemnumber );
+    my $order = Koha::Acquisition::Orders->find($ordernumber);
+    my $items = $order->items;
+    while ( my $item = $items->next ) { # Should be moved to Koha::Acquisition::Order->delete
+        my $delcheck = C4::Items::DelItemCheck( $bibnum, $item->itemnumber );
 
         if($delcheck != 1) {
             $error->{'delitem'} = 1;
@@ -2008,6 +2001,41 @@ sub TransferOrder {
     return $newordernumber;
 }
 
+=head3 get_rounding_sql
+
+    $rounding_sql = get_rounding_sql($column_name);
+
+returns the correct SQL routine based on OrderPriceRounding system preference.
+
+=cut
+
+sub get_rounding_sql {
+    my ( $round_string ) = @_;
+    my $rounding_pref = C4::Context->preference('OrderPriceRounding') // q{};
+    if ( $rounding_pref eq "nearest_cent"  ) {
+        return "CAST($round_string*100 AS SIGNED)/100";
+    }
+    return $round_string;
+}
+
+=head3 get_rounded_price
+
+    $rounded_price = get_rounded_price( $price );
+
+returns a price rounded as specified in OrderPriceRounding system preference.
+
+=cut
+
+sub get_rounded_price {
+    my ( $price ) =  @_;
+    my $rounding_pref = C4::Context->preference('OrderPriceRounding') // q{};
+    if( $rounding_pref eq 'nearest_cent' ) {
+        return Koha::Number::Price->new( $price )->round();
+    }
+    return $price;
+}
+
+
 =head2 FUNCTIONS ABOUT PARCELS
 
 =head3 GetParcels
@@ -2290,6 +2318,8 @@ sub GetHistory {
     my $ordernumber = $params{ordernumber};
     my $search_children_too = $params{search_children_too} || 0;
     my $created_by = $params{created_by} || [];
+    my $ordernumbers = $params{ordernumbers} || [];
+    my $additional_fields = $params{additional_fields} // [];
 
     my @order_loop;
     my $total_qty         = 0;
@@ -2450,6 +2480,18 @@ sub GetHistory {
         push @query_params, @$created_by;
     }
 
+    if ( @$ordernumbers ) {
+        $query .= ' AND (aqorders.ordernumber IN ( ' . join (',', ('?') x @$ordernumbers ) . '))';
+        push @query_params, @$ordernumbers;
+    }
+    if ( @$additional_fields ) {
+        my @baskets = Koha::Acquisition::Baskets->filter_by_additional_fields($additional_fields);
+
+        return [] unless @baskets;
+
+        # No parameterization because record IDs come directly from DB
+        $query .= ' AND aqbasket.basketno IN ( ' . join( ',', map { $_->basketno } @baskets ) . ' )';
+    }
 
     if ( C4::Context->preference("IndependentBranches") ) {
         unless ( C4::Context->IsSuperLibrarian() ) {
@@ -2958,8 +3000,38 @@ sub GetBiblioCountByBasketno {
     return $sth->fetchrow;
 }
 
-# Note this subroutine should be moved to Koha::Acquisition::Order
-# Will do when a DBIC decision will be taken.
+=head3 populate_order_with_prices
+
+$order = populate_order_with_prices({
+    order        => $order #a hashref with the order values
+    booksellerid => $booksellerid #FIXME - should obtain from order basket
+    receiving    => 1 # boolean representing order stage, should pass only this or ordering
+    ordering     => 1 # boolean representing order stage
+});
+
+
+Sets calculated values for an order - all values are stored with pull precision regardless of rounding preference except fot
+tax value which is calculated on rounded values if requested
+
+For ordering the values set are:
+    rrp_tax_included
+    rrp_tax_excluded
+    ecost_tax_included
+    ecost_tax_excluded
+    tax_value_on_ordering
+For receiving the value set are:
+    unitprice_tax_included
+    unitprice_tax_excluded
+    tax_value_on_receiving
+
+Note: When receiving if the rounded value of the unitprice matches the rounded value of the ecost then then ecost (full precision) is used.
+
+Returns a hashref of the order
+
+FIXME: Move this to Koha::Acquisition::Order.pm
+
+=cut
+
 sub populate_order_with_prices {
     my ($params) = @_;
 
@@ -2983,11 +3055,15 @@ sub populate_order_with_prices {
             # rrp tax excluded = rrp tax included / ( 1 + tax rate )
             $order->{rrp_tax_excluded} = $order->{rrp_tax_included} / ( 1 + $order->{tax_rate_on_ordering} );
 
+            # ecost tax included = rrp tax included  ( 1 - discount )
+            $order->{ecost_tax_included} = $order->{rrp_tax_included} * ( 1 - $discount );
+
             # ecost tax excluded = rrp tax excluded * ( 1 - discount )
             $order->{ecost_tax_excluded} = $order->{rrp_tax_excluded} * ( 1 - $discount );
 
-            # ecost tax included = rrp tax included  ( 1 - discount )
-            $order->{ecost_tax_included} = $order->{rrp_tax_included} * ( 1 - $discount );
+            # tax value = quantity * ecost tax excluded * tax rate
+            $order->{tax_value_on_ordering} = ( get_rounded_price($order->{ecost_tax_included}) - get_rounded_price($order->{ecost_tax_excluded}) ) * $order->{quantity};
+
         }
         else {
             # The user entered the rrp tax excluded
@@ -2999,16 +3075,12 @@ sub populate_order_with_prices {
             # ecost tax excluded = rrp tax excluded * ( 1 - discount )
             $order->{ecost_tax_excluded} = $order->{rrp_tax_excluded} * ( 1 - $discount );
 
-            # ecost tax included = rrp tax excluded * ( 1 + tax rate ) * ( 1 - discount )
-            $order->{ecost_tax_included} =
-                $order->{rrp_tax_excluded} *
-                ( 1 + $order->{tax_rate_on_ordering} ) *
-                ( 1 - $discount );
-        }
+            # ecost tax included = rrp tax excluded * ( 1 + tax rate ) * ( 1 - discount ) = ecost tax excluded * ( 1 + tax rate )
+            $order->{ecost_tax_included} = $order->{ecost_tax_excluded} * ( 1 + $order->{tax_rate_on_ordering} );
 
-        # tax value = quantity * ecost tax excluded * tax rate
-        $order->{tax_value_on_ordering} =
-            $order->{quantity} * $order->{ecost_tax_excluded} * $order->{tax_rate_on_ordering};
+            # tax value = quantity * ecost tax included * tax rate
+            $order->{tax_value_on_ordering} = $order->{quantity} * get_rounded_price($order->{ecost_tax_excluded}) * $order->{tax_rate_on_ordering};
+        }
     }
 
     if ($receiving) {
@@ -3042,7 +3114,7 @@ sub populate_order_with_prices {
         }
 
         # tax value = quantity * unit price tax excluded * tax rate
-        $order->{tax_value_on_receiving} = $order->{quantity} * $order->{unitprice_tax_excluded} * $order->{tax_rate_on_receiving};
+        $order->{tax_value_on_receiving} = $order->{quantity} * get_rounded_price($order->{unitprice_tax_excluded}) * $order->{tax_rate_on_receiving};
     }
 
     return $order;