uc_taxes.test 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. <?php
  2. /**
  3. * @file
  4. * Tax tests.
  5. */
  6. /**
  7. * Tests the tax functionality.
  8. */
  9. class UbercartTaxesTestCase extends UbercartTestHelper {
  10. public static function getInfo() {
  11. return array(
  12. 'name' => 'Taxes',
  13. 'description' => 'Ensures that taxes are calculated, stored and displayed correctly.',
  14. 'group' => 'Ubercart',
  15. );
  16. }
  17. /**
  18. * Overrides DrupalWebTestCase::setUp().
  19. */
  20. protected function setUp($modules = array(), $permissions = array()) {
  21. $modules = array('uc_product_kit', 'uc_attribute', 'uc_cart', 'uc_payment', 'uc_payment_pack', 'uc_taxes');
  22. $permissions = array('bypass node access', 'administer content types', 'administer rules', 'configure taxes');
  23. parent::setUp($modules, $permissions);
  24. }
  25. public function testInclusiveTaxes() {
  26. $this->drupalLogin($this->adminUser);
  27. // Create a 20% inclusive tax rate.
  28. $rate = (object) array(
  29. 'name' => $this->randomName(8),
  30. 'rate' => 0.2,
  31. 'taxed_product_types' => array('product'),
  32. 'taxed_line_items' => array(),
  33. 'weight' => 0,
  34. 'shippable' => 0,
  35. 'display_include' => 1,
  36. 'inclusion_text' => $this->randomName(6),
  37. );
  38. uc_taxes_rate_save($rate);
  39. // Ensure Rules picks up the new condition.
  40. entity_flush_caches();
  41. // Create a $10 product.
  42. $product = $this->createProduct(array(
  43. 'sell_price' => 10,
  44. ));
  45. // Create an attribute.
  46. $attribute = (object) array(
  47. 'name' => $this->randomName(8),
  48. 'label' => $this->randomName(8),
  49. 'description' => $this->randomName(8),
  50. 'required' => TRUE,
  51. 'display' => 1,
  52. 'ordering' => 0,
  53. );
  54. uc_attribute_save($attribute);
  55. // Create an option with a price adjustment of $5.
  56. $option = (object) array(
  57. 'aid' => $attribute->aid,
  58. 'name' => $this->randomName(8),
  59. 'cost' => 0,
  60. 'price' => 5,
  61. 'weight' => 0,
  62. 'ordering' => 0,
  63. );
  64. uc_attribute_option_save($option);
  65. // Attach the attribute to the product.
  66. $attribute = uc_attribute_load($attribute->aid);
  67. uc_attribute_subject_save($attribute, 'product', $product->nid, TRUE);
  68. // Create a product kit containing the product.
  69. $kit = $this->drupalCreateNode(array(
  70. 'type' => 'product_kit',
  71. 'products' => array($product->nid),
  72. 'ordering' => 0,
  73. 'mutable' => UC_PRODUCT_KIT_UNMUTABLE_WITH_LIST,
  74. 'default_qty' => 1,
  75. ));
  76. // Set the kit total to $9 to automatically apply a discount.
  77. $kit = node_load($kit->nid);
  78. $kit->kit_total = 9;
  79. node_save($kit);
  80. $kit = node_load($kit->nid);
  81. $this->assertEqual($kit->products[$product->nid]->discount, -1, 'Product kit component has correct discount applied.');
  82. // Ensure the price is displayed tax-inclusively on the add-to-cart form.
  83. $this->drupalGet('node/' . $kit->nid);
  84. $this->assertText('$10.80' . $rate->inclusion_text, 'Tax inclusive price on node-view form is accurate.'); // $10.80 = $9.00 + 20%
  85. $this->assertRaw($option->name . ', +$6.00</option>', 'Tax inclusive option price on node view form is accurate.'); // $6.00 = $5.00 + 20%
  86. // Add the product kit to the cart, selecting the option.
  87. $attribute_key = 'products[' . $product->nid . '][attributes][' . $attribute->aid . ']';
  88. $this->drupalPost('node/' . $kit->nid, array($attribute_key => $option->oid), t('Add to cart'));
  89. // Check that the subtotal is $16.80 ($10 base + $5 option - $1 discount, with 20% tax)
  90. $this->drupalGet('cart');
  91. $this->assertText('Subtotal: $16.80', 'Order subtotal is correct on cart page.');
  92. // Make sure that the subtotal is also correct on the checkout page.
  93. $this->drupalPost('cart', array(), 'Checkout');
  94. $this->assertText('Subtotal: $16.80', 'Order subtotal is correct on checkout page.');
  95. // Manually proceed to checkout review.
  96. $zone_id = db_query_range('SELECT zone_id FROM {uc_zones} WHERE zone_country_id = :country ORDER BY rand()', 0, 1, array('country' => variable_get('uc_store_country', 840)))->fetchField();
  97. $edit = array(
  98. 'panes[delivery][delivery_first_name]' => $this->randomName(10),
  99. 'panes[delivery][delivery_last_name]' => $this->randomName(10),
  100. 'panes[delivery][delivery_street1]' => $this->randomName(10),
  101. 'panes[delivery][delivery_city]' => $this->randomName(10),
  102. 'panes[delivery][delivery_zone]' => $zone_id,
  103. 'panes[delivery][delivery_postal_code]' => mt_rand(10000, 99999),
  104. 'panes[billing][billing_first_name]' => $this->randomName(10),
  105. 'panes[billing][billing_last_name]' => $this->randomName(10),
  106. 'panes[billing][billing_street1]' => $this->randomName(10),
  107. 'panes[billing][billing_city]' => $this->randomName(10),
  108. 'panes[billing][billing_zone]' => $zone_id,
  109. 'panes[billing][billing_postal_code]' => mt_rand(10000, 99999),
  110. );
  111. $this->drupalPost('cart/checkout', $edit, t('Review order'));
  112. $this->assertRaw(t('Your order is almost complete.'));
  113. // Make sure the price is still listed tax-inclusively.
  114. // !TODO This could be handled more specifically with a regex.
  115. $this->assertText('$16.80' . $rate->inclusion_text, 'Tax inclusive price appears in cart pane on checkout review page');
  116. // Ensure the tax-inclusive price is listed on the order admin page.
  117. $order_id = db_query("SELECT order_id FROM {uc_orders} WHERE delivery_first_name = :name", array(':name' => $edit['panes[delivery][delivery_first_name]']))->fetchField();
  118. $this->assertTrue($order_id, 'Order was created successfully');
  119. $this->drupalGet('admin/store/orders/' . $order_id);
  120. $this->assertText('$16.80' . $rate->inclusion_text, 'Tax inclusive price appears on the order view page.');
  121. // And on the invoice.
  122. $this->drupalGet('admin/store/orders/' . $order_id . '/invoice');
  123. $this->assertText('$16.80' . $rate->inclusion_text, 'Tax inclusive price appears on the invoice.');
  124. // And on the printable invoice.
  125. $this->drupalGet('admin/store/orders/' . $order_id . '/invoice');
  126. $this->assertText('$16.80' . $rate->inclusion_text, 'Tax inclusive price appears on the printable invoice.');
  127. }
  128. protected function loadTaxLine($order_id) {
  129. $order = uc_order_load($order_id, TRUE);
  130. foreach ($order->line_items as $line) {
  131. if ($line['type'] == 'tax') {
  132. return $line;
  133. }
  134. }
  135. return FALSE;
  136. }
  137. protected function assertTaxLineCorrect($line, $rate, $when) {
  138. $this->assertTrue($line, t('The tax line item was saved to the order ' . $when));
  139. $this->assertTrue(number_format($rate * $this->product->sell_price, 2) == number_format($line['amount'], 2), t('Stored tax line item has the correct amount ' . $when));
  140. $this->assertFieldByName('line_items[' . $line['line_item_id'] . '][li_id]', $line['line_item_id'], t('Found the tax line item ID ' . $when));
  141. $this->assertText($line['title'], t('Found the tax title ' . $when));
  142. $this->assertText(uc_currency_format($line['amount']), t('Tax display has the correct amount ' . $when));
  143. }
  144. public function testStoredTaxDisplay() {
  145. $this->drupalLogin($this->adminUser);
  146. // Enable a payment method for the payment preview checkout pane.
  147. $edit = array('uc_payment_method_check_checkout' => 1);
  148. $this->drupalPost('admin/store/settings/payment', $edit, t('Save configuration'));
  149. // Create a 20% inclusive tax rate.
  150. $rate = (object) array(
  151. 'name' => $this->randomName(8),
  152. 'rate' => 0.2,
  153. 'taxed_product_types' => array('product'),
  154. 'taxed_line_items' => array(),
  155. 'weight' => 0,
  156. 'shippable' => 0,
  157. 'display_include' => 1,
  158. 'inclusion_text' => '',
  159. );
  160. uc_taxes_rate_save($rate);
  161. $this->drupalGet('admin/store/settings/taxes');
  162. $this->assertText($rate->name, t('Tax was saved successfully.'));
  163. $this->drupalGet("admin/store/settings/taxes/manage/uc_taxes_$rate->id");
  164. $this->assertText(t('Conditions'), t('Rules configuration linked to tax.'));
  165. $this->drupalPost('node/' . $this->product->nid, array(), t('Add to cart'));
  166. // Manually step through checkout. $this->checkout() doesn't know about taxes.
  167. $this->drupalPost('cart', array(), 'Checkout');
  168. $this->assertText(
  169. t('Enter your billing address and information here.'),
  170. t('Viewed cart page: Billing pane has been displayed.')
  171. );
  172. $this->assertRaw($rate->name, t('Tax line item displayed.'));
  173. $this->assertRaw(uc_currency_format($rate->rate * $this->product->sell_price), t('Correct tax amount displayed.'));
  174. // Build the panes.
  175. $zone_id = db_query_range('SELECT zone_id FROM {uc_zones} WHERE zone_country_id = :country ORDER BY rand()', 0, 1, array('country' => variable_get('uc_store_country', 840)))->fetchField();
  176. $edit = array(
  177. 'panes[delivery][delivery_first_name]' => $this->randomName(10),
  178. 'panes[delivery][delivery_last_name]' => $this->randomName(10),
  179. 'panes[delivery][delivery_street1]' => $this->randomName(10),
  180. 'panes[delivery][delivery_city]' => $this->randomName(10),
  181. 'panes[delivery][delivery_zone]' => $zone_id,
  182. 'panes[delivery][delivery_postal_code]' => mt_rand(10000, 99999),
  183. 'panes[billing][billing_first_name]' => $this->randomName(10),
  184. 'panes[billing][billing_last_name]' => $this->randomName(10),
  185. 'panes[billing][billing_street1]' => $this->randomName(10),
  186. 'panes[billing][billing_city]' => $this->randomName(10),
  187. 'panes[billing][billing_zone]' => $zone_id,
  188. 'panes[billing][billing_postal_code]' => mt_rand(10000, 99999),
  189. );
  190. // Submit the checkout page.
  191. $this->drupalPost('cart/checkout', $edit, t('Review order'));
  192. $this->assertRaw(t('Your order is almost complete.'));
  193. $this->assertRaw($rate->name, t('Tax line item displayed.'));
  194. $this->assertRaw(uc_currency_format($rate->rate * $this->product->sell_price), t('Correct tax amount displayed.'));
  195. // Complete the review page.
  196. $this->drupalPost(NULL, array(), t('Submit order'));
  197. $order_id = db_query("SELECT order_id FROM {uc_orders} WHERE delivery_first_name = :name", array(':name' => $edit['panes[delivery][delivery_first_name]']))->fetchField();
  198. if ($order_id) {
  199. $this->pass(
  200. t('Order %order_id has been created', array('%order_id' => $order_id))
  201. );
  202. $this->drupalGet('admin/store/orders/' . $order_id . '/edit');
  203. $this->assertTaxLineCorrect($this->loadTaxLine($order_id), $rate->rate, 'on initial order load');
  204. $this->drupalPost('admin/store/orders/' . $order_id . '/edit', array(), t('Submit changes'));
  205. $this->assertText(t('Order changes saved.'));
  206. $this->assertTaxLineCorrect($this->loadTaxLine($order_id), $rate->rate, 'after saving order');
  207. // Change tax rate and ensure order doesn't change.
  208. $oldrate = $rate->rate;
  209. $rate->rate = 0.1;
  210. $rate = uc_taxes_rate_save($rate);
  211. // Save order because tax changes are only updated on save.
  212. $this->drupalPost('admin/store/orders/' . $order_id . '/edit', array(), t('Submit changes'));
  213. $this->assertText(t('Order changes saved.'));
  214. $this->assertTaxLineCorrect($this->loadTaxLine($order_id), $oldrate, 'after rate change');
  215. // Change taxable products and ensure order doesn't change.
  216. $class = $this->createProductClass();
  217. $rate->taxed_product_types = array($class->name);
  218. uc_taxes_rate_save($rate);
  219. entity_flush_caches();
  220. $this->drupalPost('admin/store/orders/' . $order_id . '/edit', array(), t('Submit changes'));
  221. $this->assertText(t('Order changes saved.'));
  222. $this->assertTaxLineCorrect($this->loadTaxLine($order_id), $oldrate, 'after applicable product change');
  223. // Change order Status back to in_checkout and ensure tax-rate changes now update the order.
  224. uc_order_update_status($order_id, 'in_checkout');
  225. $this->drupalPost('admin/store/orders/' . $order_id . '/edit', array(), t('Submit changes'));
  226. $this->assertText(t('Order changes saved.'));
  227. $this->assertFalse($this->loadTaxLine($order_id), t('The tax line was removed from the order when order status changed back to in_checkout.'));
  228. // Restore taxable product and ensure new tax is added.
  229. $rate->taxed_product_types = array('product');
  230. uc_taxes_rate_save($rate);
  231. $this->drupalPost('admin/store/orders/' . $order_id . '/edit', array(), t('Submit changes'));
  232. $this->assertText(t('Order changes saved.'));
  233. $this->assertTaxLineCorrect($this->loadTaxLine($order_id), $rate->rate, 'when order status changed back to in_checkout');
  234. }
  235. else {
  236. $this->fail(t('No order was created.'));
  237. }
  238. }
  239. public function testTaxProductClassUpdate() {
  240. $this->drupalLogin($this->adminUser);
  241. // Create a new product class.
  242. $type = strtolower($this->randomName(12));
  243. $edit = array(
  244. 'pcid' => $type,
  245. 'name' => $type,
  246. 'description' => $this->randomName(32),
  247. );
  248. $this->drupalPost('admin/store/products/classes', $edit, t('Save'));
  249. node_types_rebuild();
  250. // Create a tax rate.
  251. $tax = $this->randomName(8);
  252. $rate = (object) array(
  253. 'id' => 0, // TODO: should not have to set this
  254. 'name' => $tax,
  255. 'rate' => rand(1, 20) / 10,
  256. 'taxed_product_types' => array($type),
  257. 'taxed_line_items' => array(),
  258. 'weight' => 0,
  259. 'shippable' => 0,
  260. );
  261. uc_taxes_rate_save($rate);
  262. // Check that the tax rate shows up at checkout.
  263. $product = $this->createProduct(array('type' => $type));
  264. $this->drupalPost('node/' . $product->nid, array(), t('Add to cart'));
  265. $this->drupalGet('cart/checkout');
  266. $this->assertText($tax, 'Tax line item displayed.');
  267. // Change the machine name of the product class.
  268. $new_type = strtolower($this->randomName(12));
  269. $edit = array(
  270. 'name' => $new_type,
  271. 'type' => $new_type,
  272. );
  273. $this->drupalPost('admin/structure/types/manage/' . $type, $edit, t('Save content type'));
  274. // Check that the tax rate still shows up at checkout.
  275. $this->drupalPost('cart', array(), t('Remove'));
  276. $this->drupalPost('node/' . $product->nid, array(), t('Add to cart'));
  277. $this->drupalGet('cart/checkout');
  278. $this->assertText($tax, 'Tax line item displayed after changing product class node type.');
  279. }
  280. }