'Taxes', 'description' => 'Ensures that taxes are calculated, stored and displayed correctly.', 'group' => 'Ubercart', ); } /** * Overrides DrupalWebTestCase::setUp(). */ function setUp() { $modules = array('uc_product_kit', 'uc_attribute', 'uc_cart', 'uc_payment', 'uc_payment_pack', 'uc_taxes'); $permissions = array('bypass node access', 'administer content types', 'administer rules', 'configure taxes'); parent::setUp($modules, $permissions); } function testInclusiveTaxes() { $this->drupalLogin($this->adminUser); // Create a 20% inclusive tax rate. $rate = (object) array( 'name' => $this->randomName(8), 'rate' => 0.2, 'taxed_product_types' => array('product'), 'taxed_line_items' => array(), 'weight' => 0, 'shippable' => 0, 'display_include' => 1, 'inclusion_text' => $this->randomName(6), ); uc_taxes_rate_save($rate); // Ensure Rules picks up the new condition. entity_flush_caches(); // Create a $10 product. $product = $this->createProduct(array( 'sell_price' => 10, )); // Create an attribute. $attribute = (object) array( 'name' => $this->randomName(8), 'label' => $this->randomName(8), 'description' => $this->randomName(8), 'required' => TRUE, 'display' => 1, 'ordering' => 0, ); uc_attribute_save($attribute); // Create an option with a price adjustment of $5. $option = (object) array( 'aid' => $attribute->aid, 'name' => $this->randomName(8), 'cost' => 0, 'price' => 5, 'weight' => 0, 'ordering' => 0, ); uc_attribute_option_save($option); // Attach the attribute to the product. $attribute = uc_attribute_load($attribute->aid); uc_attribute_subject_save($attribute, 'product', $product->nid, TRUE); // Create a product kit containing the product. $kit = $this->drupalCreateNode(array( 'type' => 'product_kit', 'products' => array($product->nid), 'ordering' => 0, 'mutable' => UC_PRODUCT_KIT_UNMUTABLE_WITH_LIST, 'default_qty' => 1, )); // Set the kit total to $9 to automatically apply a discount. $kit = node_load($kit->nid); $kit->kit_total = 9; node_save($kit); $kit = node_load($kit->nid); $this->assertEqual($kit->products[$product->nid]->discount, -1, 'Product kit component has correct discount applied.'); // Ensure the price is displayed tax-inclusively on the add-to-cart form. $this->drupalGet('node/' . $kit->nid); $this->assertText('$10.80' . $rate->inclusion_text, 'Tax inclusive price on node-view form is accurate.'); // $10.80 = $9.00 + 20% $this->assertRaw($option->name . ', +$6.00', 'Tax inclusive option price on node view form is accurate.'); // $6.00 = $5.00 + 20% // Add the product kit to the cart, selecting the option. $attribute_key = 'products[' . $product->nid . '][attributes][' . $attribute->aid . ']'; $this->drupalPost('node/' . $kit->nid, array($attribute_key => $option->oid), t('Add to cart')); // Check that the subtotal is $16.80 ($10 base + $5 option - $1 discount, with 20% tax) $this->drupalGet('cart'); $this->assertText('Subtotal: $16.80', 'Order subtotal is correct on cart page.'); // Make sure that the subtotal is also correct on the checkout page. $this->drupalPost('cart', array(), 'Checkout'); $this->assertText('Subtotal: $16.80', 'Order subtotal is correct on checkout page.'); // Manually proceed to checkout review. $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(); $edit = array( 'panes[delivery][delivery_first_name]' => $this->randomName(10), 'panes[delivery][delivery_last_name]' => $this->randomName(10), 'panes[delivery][delivery_street1]' => $this->randomName(10), 'panes[delivery][delivery_city]' => $this->randomName(10), 'panes[delivery][delivery_zone]' => $zone_id, 'panes[delivery][delivery_postal_code]' => mt_rand(10000, 99999), 'panes[billing][billing_first_name]' => $this->randomName(10), 'panes[billing][billing_last_name]' => $this->randomName(10), 'panes[billing][billing_street1]' => $this->randomName(10), 'panes[billing][billing_city]' => $this->randomName(10), 'panes[billing][billing_zone]' => $zone_id, 'panes[billing][billing_postal_code]' => mt_rand(10000, 99999), ); $this->drupalPost('cart/checkout', $edit, t('Review order')); $this->assertRaw(t('Your order is almost complete.')); // Make sure the price is still listed tax-inclusively. // !TODO This could be handled more specifically with a regex. $this->assertText('$16.80' . $rate->inclusion_text, 'Tax inclusive price appears in cart pane on checkout review page'); // Ensure the tax-inclusive price is listed on the order admin page. $order_id = db_query("SELECT order_id FROM {uc_orders} WHERE delivery_first_name = :name", array(':name' => $edit['panes[delivery][delivery_first_name]']))->fetchField(); $this->assertTrue($order_id, 'Order was created successfully'); $this->drupalGet('admin/store/orders/' . $order_id); $this->assertText('$16.80' . $rate->inclusion_text, 'Tax inclusive price appears on the order view page.'); // And on the invoice. $this->drupalGet('admin/store/orders/' . $order_id . '/invoice'); $this->assertText('$16.80' . $rate->inclusion_text, 'Tax inclusive price appears on the invoice.'); // And on the printable invoice. $this->drupalGet('admin/store/orders/' . $order_id . '/invoice'); $this->assertText('$16.80' . $rate->inclusion_text, 'Tax inclusive price appears on the printable invoice.'); } function loadTaxLine($order_id) { $order = uc_order_load($order_id, TRUE); foreach ($order->line_items as $line) { if ($line['type'] == 'tax') { return $line; } } return FALSE; } function assertTaxLineCorrect($line, $rate, $when) { $this->assertTrue($line, t('The tax line item was saved to the order ' . $when)); $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)); $this->assertFieldByName('line_items[' . $line['line_item_id'] . '][li_id]', $line['line_item_id'], t('Found the tax line item ID ' . $when)); $this->assertText($line['title'], t('Found the tax title ' . $when)); $this->assertText(uc_currency_format($line['amount']), t('Tax display has the correct amount ' . $when)); } function testStoredTaxDisplay() { $this->drupalLogin($this->adminUser); // Enable a payment method for the payment preview checkout pane. $edit = array('uc_payment_method_check_checkout' => 1); $this->drupalPost('admin/store/settings/payment', $edit, t('Save configuration')); // Create a 20% inclusive tax rate. $rate = (object) array( 'name' => $this->randomName(8), 'rate' => 0.2, 'taxed_product_types' => array('product'), 'taxed_line_items' => array(), 'weight' => 0, 'shippable' => 0, 'display_include' => 1, 'inclusion_text' => '', ); uc_taxes_rate_save($rate); $this->drupalGet('admin/store/settings/taxes'); $this->assertText($rate->name, t('Tax was saved successfully.')); $this->drupalGet("admin/store/settings/taxes/manage/uc_taxes_$rate->id"); $this->assertText(t('Conditions'), t('Rules configuration linked to tax.')); $this->drupalPost('node/' . $this->product->nid, array(), t('Add to cart')); // Manually step through checkout. $this->checkout() doesn't know about taxes. $this->drupalPost('cart', array(), 'Checkout'); $this->assertText( t('Enter your billing address and information here.'), t('Viewed cart page: Billing pane has been displayed.') ); $this->assertRaw($rate->name, t('Tax line item displayed.')); $this->assertRaw(uc_currency_format($rate->rate * $this->product->sell_price), t('Correct tax amount displayed.')); // Build the panes. $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(); $edit = array( 'panes[delivery][delivery_first_name]' => $this->randomName(10), 'panes[delivery][delivery_last_name]' => $this->randomName(10), 'panes[delivery][delivery_street1]' => $this->randomName(10), 'panes[delivery][delivery_city]' => $this->randomName(10), 'panes[delivery][delivery_zone]' => $zone_id, 'panes[delivery][delivery_postal_code]' => mt_rand(10000, 99999), 'panes[billing][billing_first_name]' => $this->randomName(10), 'panes[billing][billing_last_name]' => $this->randomName(10), 'panes[billing][billing_street1]' => $this->randomName(10), 'panes[billing][billing_city]' => $this->randomName(10), 'panes[billing][billing_zone]' => $zone_id, 'panes[billing][billing_postal_code]' => mt_rand(10000, 99999), ); // Submit the checkout page. $this->drupalPost('cart/checkout', $edit, t('Review order')); $this->assertRaw(t('Your order is almost complete.')); $this->assertRaw($rate->name, t('Tax line item displayed.')); $this->assertRaw(uc_currency_format($rate->rate * $this->product->sell_price), t('Correct tax amount displayed.')); // Complete the review page. $this->drupalPost(NULL, array(), t('Submit order')); $order_id = db_query("SELECT order_id FROM {uc_orders} WHERE delivery_first_name = :name", array(':name' => $edit['panes[delivery][delivery_first_name]']))->fetchField(); if ($order_id) { $this->pass( t('Order %order_id has been created', array('%order_id' => $order_id)) ); $this->drupalGet('admin/store/orders/' . $order_id . '/edit'); $this->assertTaxLineCorrect($this->loadTaxLine($order_id), $rate->rate, 'on initial order load'); $this->drupalPost('admin/store/orders/' . $order_id . '/edit', array(), t('Submit changes')); $this->assertText(t('Order changes saved.')); $this->assertTaxLineCorrect($this->loadTaxLine($order_id), $rate->rate, 'after saving order'); // Change tax rate and ensure order doesn't change. $oldrate = $rate->rate; $rate->rate = 0.1; $rate = uc_taxes_rate_save($rate); // Save order because tax changes are only updated on save. $this->drupalPost('admin/store/orders/' . $order_id . '/edit', array(), t('Submit changes')); $this->assertText(t('Order changes saved.')); $this->assertTaxLineCorrect($this->loadTaxLine($order_id), $oldrate, 'after rate change'); // Change taxable products and ensure order doesn't change. $class = $this->createProductClass(); $rate->taxed_product_types = array($class->name); uc_taxes_rate_save($rate); entity_flush_caches(); $this->drupalPost('admin/store/orders/' . $order_id . '/edit', array(), t('Submit changes')); $this->assertText(t('Order changes saved.')); $this->assertTaxLineCorrect($this->loadTaxLine($order_id), $oldrate, 'after applicable product change'); // Change order Status back to in_checkout and ensure tax-rate changes now update the order. uc_order_update_status($order_id, 'in_checkout'); $this->drupalPost('admin/store/orders/' . $order_id . '/edit', array(), t('Submit changes')); $this->assertText(t('Order changes saved.')); $this->assertFalse($this->loadTaxLine($order_id), t('The tax line was removed from the order when order status changed back to in_checkout.')); // Restore taxable product and ensure new tax is added. $rate->taxed_product_types = array('product'); uc_taxes_rate_save($rate); $this->drupalPost('admin/store/orders/' . $order_id . '/edit', array(), t('Submit changes')); $this->assertText(t('Order changes saved.')); $this->assertTaxLineCorrect($this->loadTaxLine($order_id), $rate->rate, 'when order status changed back to in_checkout'); } else { $this->fail(t('No order was created.')); } } function testTaxProductClassUpdate() { $this->drupalLogin($this->adminUser); // Create a new product class. $type = strtolower($this->randomName(12)); $edit = array( 'pcid' => $type, 'name' => $type, 'description' => $this->randomName(32), ); $this->drupalPost('admin/store/products/classes', $edit, t('Save')); node_types_rebuild(); // Create a tax rate. $tax = $this->randomName(8); $rate = (object) array( 'id' => 0, // TODO: should not have to set this 'name' => $tax, 'rate' => rand(1, 20) / 10, 'taxed_product_types' => array($type), 'taxed_line_items' => array(), 'weight' => 0, 'shippable' => 0, ); uc_taxes_rate_save($rate); // Check that the tax rate shows up at checkout. $product = $this->createProduct(array('type' => $type)); $this->drupalPost('node/' . $product->nid, array(), t('Add to cart')); $this->drupalGet('cart/checkout'); $this->assertText($tax, 'Tax line item displayed.'); // Change the machine name of the product class. $new_type = strtolower($this->randomName(12)); $edit = array( 'name' => $new_type, 'type' => $new_type, ); $this->drupalPost('admin/structure/types/manage/' . $type, $edit, t('Save content type')); // Check that the tax rate still shows up at checkout. $this->drupalPost('cart', array(), t('Remove')); $this->drupalPost('node/' . $product->nid, array(), t('Add to cart')); $this->drupalGet('cart/checkout'); $this->assertText($tax, 'Tax line item displayed after changing product class node type.'); } }