Currently we have no way to control the order that taxes are displayed.

Attached patch adds an additional field to the tax rate that holds weight information, and then passes this to hook_commerce_price_component_type_info() so that taxes are displayed according to their weight.

Review appreciated, not really much of a drupal developer (but its nice to help out when I can).

Cheers,
Sam

CommentFileSizeAuthor
tax_rate_display_weight.patch3.3 KBessbee
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

essbee’s picture

Status: Active » Needs review
pcambra’s picture

Status: Needs review » Active

I'm not sure about this, I've asked fago in rules issue queue #1095784: Control component execution order with a separate controller rule set & weight field if we should add this at commerce level or rules level

pcambra’s picture

Status: Active » Needs review

As fago says, rules stores a weight for the component but is not exposed to the UI, here is a solution for reusing the weight to get the tax display, we may use this to apply the pricing rules as well.

Repo: git.drupal.org/sandbox/pcambra/1081200.git
Branch: 1095576
Diff: http://drupalcode.org/sandbox/pcambra/1081200.git/commitdiff/1bdcaf13a6f...

rszrama’s picture

Ahh, I was wondering about whether or not components had weight. However, I don't see any solution in your patch, Pedro, to allow for actually adjusting the component weight via the UI. Was that intentional?

pcambra’s picture

I exposed it in the tax rate edit form commerce_tax_ui_tax_rate_form

$form['tax_rate']['weight'] = array(
    '#type' => 'weight',
    '#title' => t(' Weight'),
    '#description' => t('The weight of this tax. Used in ordering multiple taxes when displayed'),
    '#default_value' => isset($tax_rate['weight']) ? $tax_rate['weight'] : 0,
);
rszrama’s picture

Indeed... not sure how I missed that. : P

rszrama’s picture

Status: Needs review » Needs work

This doesn't appear to work for new tax rates. I've tried adding several to no avail... I keep getting a PHP error:

Call to undefined method stdClass::save() in /Users/rszrama/src/git/drupal/commerce/modules/tax/commerce_tax_ui.module on line 408

I think the weight saving may need to be moved after the cache clearing or something.

essbee’s picture

Thinking further on this, and given the trouble being run into. Wouldnt having the display weight uncoupled from the processing order provide greater flexibility.

What if i want Tax A processed before Tax B, but on display want Tax B to show above Tax A. I personally can't think of a scenario like that for Australian taxes, but when it comes to the crazy complicated world of taxes the more flexibility the btter.

pcambra’s picture

Ok, after rethinking this, we are not able to do this in the way I proposed as the rule gets created after the function of saving the tax rate, so unless we store a weight field on our own (this was the original patch way), we are not able to do this "ourselves", however, storing that weight per tax rate is redundat because rules already stores a weight for every component, which is always 0 as rules doesn't expose it in the UI.

I think maybe the best way to do this is to expose that weight field for components in rules, see http://drupal.org/node/1095784#comment-4222582

essbee’s picture

But doesnt that still fail to allow for displaying taxes in a different order to the order they are processed in?

roball’s picture

Title: Add weight field to allow control of display order of taxes in muti tax setup » Allow control of display order of taxes in muti tax setup

There is a report in #1 of #1300966: Ability to order the display of sales taxes that there should be a workaround to set the display order of tax price components.

For each tax rate, set up an own tax type, which will automatically create an associated tax rule for each type. These rules appear at "Administration -> Store -> Configuration -> Product pricing rules" (admin/commerce/config/product-pricing).

If there are no conditions defined in tax rates, the tax price components are displayed in the REVERSE order of their rule weights. So giving each rule an according weight number like

Rule "10% VAT": Weight= 2
Rule "20% VAT": Weight= 1

will result in displaying the rate "10% VAT" first, followed by "20% VAT".

However, that does not work if tax rates have conditions. No matter which weights the rules have, the rates are always displayed in a fixed order.

The only way I was able to specify a certain order is via overriding theme_commerce_price_formatted_components().

bisonbleu’s picture

Issue summary: View changes

Workaround in #11 works, thanks - although not very intuitive especially the reversed-weight order which goes against a well established convention.

I don't mean this in a rude way, but I'm surprised this isn't fixed 3 years after the initial report. Or perhaps it is in a separate issue...

roball’s picture

Even if the (sure, dirty) workaround would work, I would recommend to override theme_commerce_price_formatted_components() in your theme's template.php.

bisonbleu’s picture

Thanks for the suggestion @roball.

Can you expand a little? Why / for which use-cases is the template.php override a better approach than creating 2 separate Tax Types?

Also, can you provide pointers or code snippet for the override? That would be great!

roball’s picture

Creating your custom tax types via "Administration -> Store -> Configuration -> Taxes -> Tax types" and tax rates via "Administration -> Store -> Configuration -> Taxes" is still necessary.

Overriding theme_commerce_price_formatted_components() is required to ensure your tax price components are always displayed in a specified order. For example, I have two tax rates for Austrian VAT: "10% VAT" (Machine name: vat_10) and "20% VAT" (Machine name: vat_20). Of course, I want them displayed in increasing order, so if there are both 10 and 20% VAT rates on an order, I want to ensure "10% VAT" is displayed first.

For this purpose, search for function theme_commerce_price_formatted_components($variables) in modules/commerce/modules/price/commerce_price.module and copy it to your template.php with the function name changed accordingly matching your theme's name. For example, if you are using the at-commerce theme, paste this code:

/**
 * Themes a price components table.
 * Overrides theme_commerce_price_formatted_components() from "modules/commerce/modules/price/commerce_price.module"
 */
function at_commerce_commerce_price_formatted_components($variables) {
  // Add the CSS styling to the table.
  drupal_add_css(drupal_get_path('module', 'commerce_price') . '/theme/commerce_price.theme.css');

  // Build table rows out of the components.
  $rows = array();

  foreach ($variables['components'] as $name => $component) {
    $rows[] = array(
      'data' => array(
        array(
          'data' => $component['title'],
          'class' => array('component-title'),
        ),
        array(
          'data' => $component['formatted_price'],
          'class' => array('component-total'),
        ),
      ),
      'class' => array(drupal_html_class('component-type-' . $name)),
    );
  }

  return theme('table', array('rows' => $rows, 'attributes' => array('class' => array('commerce-price-formatted-components'))));

Before the return line, add your own code to manipulate $rows.

Hope that helps.

bisonbleu’s picture

Great! So then, overriding my_theme_commerce_price_formatted_components() will never fail. As for using the dirty workaround, there is no guarantee that it will never fail.

In the override function, manipulating the rows before they are returned is where I need more insight to determine the best strategy, one that will remain viable even in special cases such as when a discount is applied to the order.

In my use-case, 2 taxes (that do not display inclusive with product prices) are applied when a product is purchased. They are federal_tax (5%) and provincial_tax (10%). By default, provincial_tax appears above federal_tax. That's the opposite of what I want.

How can I change/swap the order of the two taxes?

When I print_r($rows);, I get the following output.

Array ( 
	[0] => Array ( 
		[data] => Array ( 
			[0] => Array ( 
				[data] => Sous-total 
				[class] => Array ( 
					[0] => component-title 
					) 
				) 
			[1] => Array ( 
				[data] => 217.44 CAD 
				[class] => Array ( 
					[0] => component-total 
					) 
				) 
			) 
		[class] => Array ( 
			[0] => component-type-base-price 
			) 
		) 
	[1] => Array ( 
		[data] => Array ( 
			[0] => Array ( 
				[data] => TVQ 
				[class] => Array ( 
					[0] => component-title 
					) 
				) 
			[1] => Array ( 
				[data] => 21.69 CAD 
				[class] => Array ( 
					[0] => component-total 
					) 
				) 
			) 
		[class] => Array ( 
			[0] => component-type-taxtvq 
			) 
		) 
	[2] => Array ( 
		[data] => Array ( 
			[0] => Array ( 
				[data] => TPS 
				[class] => Array ( 
					[0] => component-title 
					) 
				) 
			[1] => Array ( 
				[data] => 10.87 CAD 
				[class] => Array ( 
					[0] => component-total 
					) 
				) 
			) 
		[class] => Array ( 
			[0] => component-type-taxtps 
			) 
		) 
	[3] => Array ( 
		[data] => Array ( 
			[0] => Array ( 
				[data] => Order total 
				[class] => Array ( 
					[0] => component-title 
					) 
				) 
			[1] => Array ( 
				[data] => 250.00 CAD 
				[class] => Array ( 
					[0] => component-total 
					) 
				) 
			) 
		[class] => Array ( 
			[0] => component-type-commerce-price-formatted-amount 
			) 
		) 
	) 
roball’s picture

I have pretty much the same use case. There may be a Quantity discount applied (Machine name of the corresponding Product pricing rule: quantity_discount_10, quantity_discount_12 and quantity_discount_15 for the discount rates 10, 12 or 15%, respectively). The Quantity discount Product pricing rules all have a light Weight value less than zero (-5) to ensure it will be applied as the first rule to the regular price. If a Quantity discount has been applied to the order, it appears as $rows[1]. I am using the following code to ensure "10% VAT" is displayed first if there are both 10 and 20% VAT rates. This of course works if there has or has not been applied a Quantity discount:

    $row_last = count($rows) - 1;
    $discount = FALSE;
    if (preg_match('/^component\-type\-discount/', $rows[1]['class'][0])) {
      // A discount is granted
      $discount = TRUE;
      $row_vat_first = 2;
    } else {
      $row_vat_first = 1;
    }
    if ($rows[$row_vat_first]['class'][0] == 'component-type-taxvat-20'
      && $rows[$row_vat_first + 1]['class'][0] == 'component-type-taxvat-10'
    ) {
      array_push($rows, $rows[$row_vat_first]);
      $rows[$row_vat_first] = $rows[$row_vat_first + 1];
      $rows[$row_vat_first + 1] = $rows[$row_last + 1];
      unset($rows[$row_last + 1]);
    }
bisonbleu’s picture

Your code works perfectly @roball. And making it discount-aware is definitely a plus; although if 2 discounts are applied it breaks. I'm wondering then if we can make it more generic and error-proof.

I've replaced the top part of your code with a simple loop that will determine the value of $row_vat_first no matter how many discounts or coupons are applied to the cart (I think). Works for me but then I don't have any discounts yet. Does this work for your use-case?

<?php
    $row_last = count($rows) - 1;
	$i = 0;
	while ($rows[$i]['class'][0] != 'component-type-taxvat-20'
	  && $rows[$i]['class'][0] != 'component-type-taxvat-10'
	) {
	  $row_vat_first = $i + 1;
	  $i++;
	}
    if ($rows[$row_vat_first]['class'][0] == 'component-type-taxvat-20'
      && $rows[$row_vat_first + 1]['class'][0] == 'component-type-taxvat-10'
    ) {
      array_push($rows, $rows[$row_vat_first]);
      $rows[$row_vat_first] = $rows[$row_vat_first + 1];
      $rows[$row_vat_first + 1] = $rows[$row_last + 1];
      unset($rows[$row_last + 1]);
    }
?>
roball’s picture

Glad that my code is useful to you.

In my use case I have either no or exactly one discount, and I need the value of $discount later, so my code works fine for my use case.

However, to easily determine $row_vat_first with any possible number of applied discounts, that code might work fine for you:

for ($i = 1; $i < $row_last; $i++) {
  if (preg_match('/^component\-type\-taxvat/', $rows[$i]['class'][0])) {
    $row_vat_first = $i;
    break;
  }
}