Index: uc_vat.module
=========================================================
--- uc_vat.module	(revision 1.13)
+++ uc_vat.module	Mon Jul 27 17:51:23 EDT 2009
@@ -14,7 +14,7 @@
     'access arguments' => array('configure taxes'),
     'type' => MENU_LOCAL_TASK,
   );
-  
+
   return $items;
 }
 
@@ -37,7 +37,7 @@
     '#title' => t('Add "excluding shipping costs" to displayed prices where appropriate.'),
     '#default_value' => variable_get('uc_vat_suffix_shipping', FALSE),
   );
-  
+
   return system_settings_form($form);
 }
 
@@ -182,41 +182,38 @@
  * VAT price alterer callback function.
  */
 function uc_vat_price_handler_alter(&$price, &$context, &$options) {
-  switch ($context['location']) {
-    case 'product-view':            // Standard product node view
-    case 'product-kit-view':        // Product kit node view
-    case 'product-tapir-table':     // Catalog table view
-    case 'catalog-grid-product':    // Catalog grid view
-    case 'product-upsell':          // Prices displayed by uc_upsell
+  switch ($context['type']) {
+    case 'product':
+      /* if (isset($context['field']) && $context['field'] != 'sell_price') {
+        // Don't modify list_price or cost.
+        return;
+      } */
+    case 'cart_item':
       $node = $context['subject']['node'];
-      break;
 
-    case 'cart-item':               // Cart item
-    case 'cart-review-item':        // Cart checkout review page
-    //case 'cart-checkout-item': // Enabling this causes incorrect prices in the cart block!
-      $node = $context['extras']['node'];
-      break;
-
-    case 'views-price-handler':     // Prices displayed by Views module
-      $node = node_load($context['subject']['node']->nid);
+      // Ensure that all the parts are there when the data comes from Views.
+      if (!isset($node->type) || !isset($node->sell_price) || !isset($node->shippable)) {
+        $node = node_load($node->nid);
+      }
       break;
-
-    case 'product-attribute-form-element': // Product node option dropdown
-    case 'attribute-form-default-option':  // Product attributes page
+    case 'attribute_option':
       $node = node_load($context['subject']['option']->nid);
       break;
-
+    case 'line_item':
+      uc_vat_line_item_price_alter($price, $context, $options);
+      return;
     default:
-      // Don't modify prices in other locations.
+      // Don't modify other types of prices.
       return;
   }
 
   $suffixes = array();
 
+  $taxes = uc_taxes_rate_load();
   if ($node->type == 'product_kit') {
     // Special case for product kits; calculate VAT per-product including any kit discount.
     foreach ($node->products as $product) {
-      foreach (uc_taxes_rate_load() as $tax) {
+      foreach ($taxes as $tax) {
         if (in_array($product->type, $tax->taxed_product_types) && ($tax->shippable == 0 || $product->shippable == 1)) {
           // This uses the original sell_price, which will not necessarily be correct if another price alterer is enabled.
           // Instead, should we try to proportionally back-calculate the individual product prices from the price we were passed?
@@ -227,9 +224,10 @@
     }
   }
   else {
-    foreach (uc_taxes_rate_load() as $tax) {
+    $taxed_price = $price['price'];
+    foreach ($taxes as $tax) {
       if (in_array($node->type, $tax->taxed_product_types) && ($tax->shippable == 0 || $node->shippable == 1)) {
-        $price['price'] *= 1 + $tax->rate;
+        $price['price'] += $taxed_price * $tax->rate;
         $suffixes[$tax->id] = $tax->name;
       }
     }
@@ -244,6 +242,47 @@
 }
 
 /**
+ * Handle taxes on line items separately from single-item prices.
+ */
+function uc_vat_line_item_price_alter(&$price, &$context, &$options) {
+  $order = $context['subject']['order'];
+  $tax_rates = uc_taxes_rate_load();
+  $taxes = uc_taxes_calculate($order);
+
+  $line_item = (array)$context['subject']['line_item'];
+  foreach ($taxes as $tax) {
+    if (isset($tax_rates[$tax->id])) {
+      $tax_rate = $tax_rates[$tax->id];
+      $taxed_line_items = $tax_rate->taxed_line_items;
+      if (is_array($taxed_line_items) && in_array($line_item['type'], $taxed_line_items)) {
+        if ($line_item['type'] == 'tax' && $line_item['weight'] >= $tax->weight) {
+          continue;
+        }
+        $price['price'] += $line_item['amount'] * $tax_rate->rate;
+      }
+    }
+  }
+}
+
+/**
+ * Implementation of hook_uc_tax_alter().
+ */
+function uc_vat_uc_taxes_alter(&$taxes, $order) {
+  foreach ((array)$taxes as $id => $tax) {
+    $tax->summed = 0;
+
+    // Taxes are included already, so the subtotal without taxes doesn't
+    // make sense.
+    if ($tax->id == 'subtotal') {
+      unset($taxes[$id]);
+    }
+    else {
+      $taxes[$id] = $tax;
+    }
+  }
+}
+
+/**
  * Format the cart contents table including VAT information on the checkout page.
  *
  * @param $show_subtotal
@@ -265,31 +304,32 @@
     array('data' => t('Price'), 'class' => 'price'),
   );
 
-  $context = array(
-    'location' => 'cart-checkout-item',
-  );
-
   // Set up table rows.
-  foreach (uc_cart_get_contents() as $item) {
+  $contents = uc_cart_get_contents();
+  foreach ($contents as $item) {
     $node = node_load($item->nid);
     $price_info = array(
       'price' => $item->price,
       'qty' => $item->qty,
     );
-    $context['subject'] = array(
+    $context = array(
+      'type' => 'cart_item',
+      'revision' => 'altered',
+      'subject' => array(
+        'cart' => $contents,
-      'cart_item' => $item,
-      'node' => $node,
+        'cart_item' => $item,
+        'node' => $node,
+      ),
     );
-    $context['revision'] = 'altered';
 
-    $price = uc_price($price_info, $context);
+    $total = uc_price($price_info, $context);
     $tax_total = 0;
     foreach (uc_taxes_rate_load() as $tax) {
       if (in_array($node->type, $tax->taxed_product_types) && ($tax->shippable == 0 || $node->shippable == 1)) {
-        $tax_total += $price * $tax->rate;
+        $tax_total += $total - $total / (1 + $tax->rate);
       }
     }
-    $total = $price + $tax_total;
+    $price = $total - $tax_total;
     $subtotal += $total;
 
     $description = check_plain($item->title);
@@ -299,6 +339,7 @@
 
     // Remove node from context to prevent the price from being altered.
     unset($context['subject']);
+    $context['type'] = 'amount';
     $context['revision'] = 'themed-original';
     $rows[] = array(
       array('data' => t('@qty&times;', array('@qty' => $item->qty)), 'class' => 'qty'),
@@ -312,8 +353,8 @@
   // Add the subtotal as the final row.
   if ($show_subtotal) {
     $context = array(
+      'type' => 'amount',
       'revision' => 'themed-original',
-      'location' => 'cart-checkout-subtotal',
     );
     $rows[] = array(
       'data' => array(array('data' => '<span id="subtotal-title">' . t('Subtotal:') . '</span> ' . uc_price($subtotal, $context), 'colspan' => 6, 'class' => 'subtotal')),
