diff --git a/commerce.module b/commerce.module index d1402f6..8b4910a 100644 --- a/commerce.module +++ b/commerce.module @@ -323,35 +323,43 @@ function commerce_currency_get_symbol($currency_code = NULL) { * Formats a price for a particular currency. * * @param $price - * A numeric price value. + * A numeric price amount value. * @param $currency_code * The three character code of the currency. * @param $object * When present, the object to which the price is attached. + * @param $convert + * Boolean indicating whether or not the amount needs to be converted to a + * decimal price amount when formatting. * * @return * A fully formatted currency. */ -function commerce_currency_format($price, $currency_code, $object = NULL) { +function commerce_currency_format($amount, $currency_code, $object = NULL, $convert = TRUE) { // First load the currency array. $currency = commerce_currency_load($currency_code); + // Then convert the price amount to the currency's major unit decimal value. + if ($convert = TRUE) { + $amount = commerce_currency_amount_to_decimal($amount, $currency_code); + } + // Invoke the custom format callback if specified. if (!empty($currency['format_callback'])) { - return $currency['format_callback']($price, $currency, $object); + return $currency['format_callback']($amount, $currency, $object); } // Separate the negative symbol from the number itself. - if ($price < 0) { + if ($amount < 0) { $negative = TRUE; - $price = abs($price); + $price = abs($amount); } else { $negative = FALSE; } // Format the price as a number. - $price = number_format(commerce_currency_round($price, $currency), $currency['decimals'], $currency['decimal_separator'], $currency['thousands_separator']); + $price = number_format(commerce_currency_round($amount, $currency), $currency['decimals'], $currency['decimal_separator'], $currency['thousands_separator']); // Establish the replacement values to format this price for its currency. $replacements = array( @@ -363,7 +371,7 @@ function commerce_currency_format($price, $currency_code, $object = NULL) { '@negative' => $negative ? '-' : '', ); - return t('@code_before @negative@symbol_before@price @symbol_after @code_after', $replacements); + return trim(t('@code_before @negative@symbol_before@price @symbol_after @code_after', $replacements)); } /** @@ -431,16 +439,19 @@ function commerce_currency_convert($amount, $currency_code, $target_currency_cod /** * Converts a price amount to an integer value for storage in the database. * - * @param $amount - * The price amount to convert to an integer. + * @param $decimal + * The decimal amount to convert to a price amount. * @param $currency_code * The currency code of the price whose decimals value will be used to - * multiply by the proper factor when converting the amount. + * multiply by the proper factor when converting the decimal amount. + * @param $round + * Whether or not the return value should be rounded and cast to an integer; + * defaults to TRUE as necessary for standard price amount column storage. * * @return - * The integer value of the price amount. + * The appropriate price amount based on the currency's decimals value. */ -function commerce_currency_amount_to_integer($amount, $currency_code) { +function commerce_currency_decimal_to_amount($decimal, $currency_code, $round = TRUE) { static $factors; // If the divisor for this currency hasn't been calculated yet... @@ -451,24 +462,28 @@ function commerce_currency_amount_to_integer($amount, $currency_code) { } // Ensure the amount has the proper number of decimal places for the currency. - $amount = commerce_currency_round($amount, commerce_currency_load($currency_code)); - - return (int) ($amount * $factors[$currency_code]); + if ($round) { + $decimal = commerce_currency_round($decimal, commerce_currency_load($currency_code)); + return (int) ($decimal * $factors[$currency_code]); + } + else { + return $decimal * $factors[$currency_code]; + } } /** - * Converts an integer value to a price amount when loading from the database. + * Converts a price amount to a decimal value based on the currency. * - * @param $integer - * The integer to convert to a price amount. + * @param $amount + * The price amount to convert to a decimal value. * @param $currency_code * The currency code of the price whose decimals value will be used to - * divide by the proper divisor when converting the integer. + * divide by the proper divisor when converting the amount. * * @return - * The price amount depending on the number of decimals the currency uses. + * The decimal amount depending on the number of places the currency uses. */ -function commerce_currency_integer_to_amount($integer, $currency_code) { +function commerce_currency_amount_to_decimal($amount, $currency_code) { static $divisors; // If the divisor for this currency hasn't been calculated yet... @@ -478,7 +493,7 @@ function commerce_currency_integer_to_amount($integer, $currency_code) { $divisors[$currency_code] = pow(10, $currency['decimals']); } - return $integer / $divisors[$currency_code]; + return $amount / $divisors[$currency_code]; } /** diff --git a/modules/cart/commerce_cart.module b/modules/cart/commerce_cart.module index 014786c..4972c83 100644 --- a/modules/cart/commerce_cart.module +++ b/modules/cart/commerce_cart.module @@ -597,10 +597,8 @@ function commerce_cart_order_refresh($order) { $data['components'] = $current_data['components']; $line_item_wrapper->commerce_unit_price->data = $data; - // Save the updated line item and clear the entity cache. Use a clone so - // the save doesn't interfere with price values in a live line item object - // that would be modified on presave. - commerce_line_item_save(clone($line_item_wrapper->value())); + // Save the updated line item and clear the entity cache. + commerce_line_item_save($line_item_wrapper->value()); entity_get_controller('commerce_line_item')->resetCache(array($line_item_wrapper->line_item_id->value())); $line_item_changed = TRUE; @@ -617,7 +615,7 @@ function commerce_cart_order_refresh($order) { if ($order_wrapper->value() != $original_order || $line_item_changed) { // Use a clone so the save doesn't interfere with price values in a live // order object that would be modified on presave. - commerce_order_save(clone($order_wrapper->value())); + commerce_order_save($order_wrapper->value()); } return $order_wrapper; diff --git a/modules/cart/tests/commerce_cart.test b/modules/cart/tests/commerce_cart.test index 8120224..f2c868a 100644 --- a/modules/cart/tests/commerce_cart.test +++ b/modules/cart/tests/commerce_cart.test @@ -515,7 +515,7 @@ class CommerceCartTestCaseAnonymousToAuthenticated extends CommerceCartTestCase // Change the price to check if the amount gets updated when the user logs // in. $new_price = $this->product->commerce_price[LANGUAGE_NONE][0]['amount'] + rand(2, 500); - $this->product->commerce_price[LANGUAGE_NONE][0]['amount'] = commerce_currency_integer_to_amount($new_price, $this->product->commerce_price[LANGUAGE_NONE][0]['currency_code']); + $this->product->commerce_price[LANGUAGE_NONE][0]['amount'] = commerce_currency_amount_to_decimal($new_price, $this->product->commerce_price[LANGUAGE_NONE][0]['currency_code']); $this->product->is_new = FALSE; $this->product = commerce_product_save($this->product); @@ -529,7 +529,7 @@ class CommerceCartTestCaseAnonymousToAuthenticated extends CommerceCartTestCase $this->drupalGet($this->getCommerceUrl('cart')); $this->assertNoText(t('Your shopping cart is empty.'), t('Cart is not empty')); $this->assertText($this->product->title, t('Product is still in the cart')); - $this->assertText(trim(commerce_currency_integer_to_amount($this->product->commerce_price[LANGUAGE_NONE][0]['amount'], $this->product->commerce_price[LANGUAGE_NONE][0]['currency_code'])), t('Product price has been updated')); + $this->assertText(trim(commerce_currency_amount_to_decimal($this->product->commerce_price[LANGUAGE_NONE][0]['amount'], $this->product->commerce_price[LANGUAGE_NONE][0]['currency_code'])), t('Product price has been updated')); // Check if the order is the same. $this->assertTrue($order_anonymous->order_id == $order_authenticated->order_id, t('Cart has been converted successfully')); diff --git a/modules/order/commerce_order.install b/modules/order/commerce_order.install index f09ce57..d1a2e70 100644 --- a/modules/order/commerce_order.install +++ b/modules/order/commerce_order.install @@ -180,3 +180,171 @@ function commerce_order_schema() { return $schema; } + +/** + * Between 7.x-1.0-beta2 and 7.x-1.0-beta3 we determined we needed to revise the + * way we handled price amounts, preferring to preserve integer amounts as + * loaded from the database until formatting them as decimal values upon display + * instead of converting them to decimals upon loading. The initial reasons and + * related issues are outlined in http://drupal.org/node/1124416. + * + * While the fix did not involve changing the database schema at all, it did + * change the way price amounts were stored in the components array of a price's + * data array. Therefore, the following update functions are responsible for + * loading and resaving entities the change will affect, primarily to result in + * a recalculated order total components array./ + */ + +/** + * Loads and resaves all the products on the site, updating the default price + * field to have proper component price amount values. + */ +function commerce_order_update_7100(&$sandbox) { + // Ensure there are no stale prices in the field cache. + field_cache_clear(); + + // Establish the progress variables. + if (!isset($sandbox['progress'])) { + $sandbox['progress'] = 0; + $sandbox['current_product_id'] = 0; + $sandbox['max'] = db_query("SELECT COUNT(DISTINCT product_id) FROM {commerce_product}")->fetchField(); + } + + // Load the next 50 products. + $products = db_select('commerce_product', 'cp') + ->fields('cp', array('product_id')) + ->condition('product_id', $sandbox['current_product_id'], '>') + ->range(0, 50) + ->orderBy('product_id', 'ASC') + ->execute(); + + // Loop over the products, loading, adjusting, and resaving each one. + foreach ($products as $product) { + $product = commerce_product_load($product->product_id); + + // If the commerce_price field has a components array, multiply its price + // amounts by the proper value for its currency. + if (!empty($product->commerce_price)) { + foreach ($product->commerce_price as $langcode => &$data) { + foreach ($data as $delta => &$item) { + if (!empty($item['data']['components'])) { + foreach ($item['data']['components'] as $key => &$component) { + $component['price']['amount'] = commerce_currency_decimal_to_amount($component['price']['amount'], $component['price']['currency_code'], FALSE); + } + } + } + } + } + + commerce_product_save($product); + + $sandbox['progress']++; + $sandbox['current_product_id'] = $product->product_id; + } + + $sandbox['#finished'] = empty($sandbox['max']) ? 1 : ($sandbox['progress'] / $sandbox['max']); + + return t('All products have been loaded and saved with updated price component arrays.'); +} + +/** + * Loads and resaves all the line items on the site, updating the unit price + * field to have proper component price amount values. + */ +function commerce_order_update_7101(&$sandbox) { + // Ensure there are no stale prices in the field cache. + field_cache_clear(); + + // Establish the progress variables. + if (!isset($sandbox['progress'])) { + $sandbox['progress'] = 0; + $sandbox['current_line_item_id'] = 0; + $sandbox['max'] = db_query("SELECT COUNT(DISTINCT line_item_id) FROM {commerce_line_item}")->fetchField(); + } + + // Load the next 50 line items. + $line_items = db_select('commerce_line_item', 'cli') + ->fields('cli', array('line_item_id')) + ->condition('line_item_id', $sandbox['current_line_item_id'], '>') + ->range(0, 50) + ->orderBy('line_item_id', 'ASC') + ->execute(); + + // Loop over the line items, loading, adjusting, and resaving each one. + foreach ($line_items as $line_item) { + $line_item = commerce_line_item_load($line_item->line_item_id); + + // If the commerce_unit_price field has a components array, multiply its + // amounts by the proper value for its currency. + if (!empty($line_item->commerce_unit_price)) { + foreach ($line_item->commerce_unit_price as $langcode => &$data) { + foreach ($data as $delta => &$item) { + if (!empty($item['data']['components'])) { + foreach ($item['data']['components'] as $key => &$component) { + $component['price']['amount'] = commerce_currency_decimal_to_amount($component['price']['amount'], $component['price']['currency_code'], FALSE); + } + } + } + } + } + + commerce_line_item_save($line_item); + + $sandbox['progress']++; + $sandbox['current_line_item_id'] = $line_item->line_item_id; + } + + $sandbox['#finished'] = empty($sandbox['max']) ? 1 : ($sandbox['progress'] / $sandbox['max']); + + return t('All line items have been loaded and saved with updated price component arrays.'); +} + +/** + * Loads and resaves all the orders on the site to rebuild the order total price + * component arrays. + */ +function commerce_order_update_7102(&$sandbox) { + // Ensure there are no stale prices in the field cache. + field_cache_clear(); + + // Establish the progress variables. + if (!isset($sandbox['progress'])) { + $sandbox['progress'] = 0; + $sandbox['current_order_id'] = 0; + $sandbox['max'] = db_query("SELECT COUNT(DISTINCT order_id) FROM {commerce_order}")->fetchField(); + } + + // Load the next 50 orders. + $orders = db_select('commerce_order', 'co') + ->fields('co', array('order_id')) + ->condition('order_id', $sandbox['current_order_id'], '>') + ->range(0, 50) + ->orderBy('order_id', 'ASC') + ->execute(); + + // Loop over the orders, loading and resaving each one. + foreach ($orders as $order) { + $order = commerce_order_load($order->order_id); + + // Save the order as a new revision with an update log message. + $order->revision = TRUE; + $order->log = t('Order updated for 7.0-1.0-beta3 price component changes.'); + + commerce_order_save($order); + + $sandbox['progress']++; + $sandbox['current_order_id'] = $order->order_id; + } + + $sandbox['#finished'] = empty($sandbox['max']) ? 1 : ($sandbox['progress'] / $sandbox['max']); + + return t('All orders have been loaded and saved with updated price component arrays.'); +} + +/** + * Truncates the pre-calculated price table. + */ +function commerce_order_update_7103() { + db_query("TRUNCATE TABLE {commerce_calculated_price}"); + return t('The calculated price table has been cleared. If your site uses product sell price pre-calculation, you will need to recalculate these prices.'); +} diff --git a/modules/order/tests/commerce_order_ui.test b/modules/order/tests/commerce_order_ui.test index 48f5e2f..682a56b 100644 --- a/modules/order/tests/commerce_order_ui.test +++ b/modules/order/tests/commerce_order_ui.test @@ -188,7 +188,7 @@ class CommerceOrderUIAdminTest extends CommerceBaseTestCase { $line_item_id = $order_wrapper->commerce_line_items->get(0)->value()->line_item_id; // Format the price based in the currency. - $price = commerce_currency_integer_to_amount($product_wrapper->commerce_price->amount->value(), $product_wrapper->commerce_price->currency_code->value()); + $price = commerce_currency_amount_to_decimal($product_wrapper->commerce_price->amount->value(), $product_wrapper->commerce_price->currency_code->value()); // Check the existance of the fields for quantity and price of the line // item just created. @@ -200,7 +200,7 @@ class CommerceOrderUIAdminTest extends CommerceBaseTestCase { // Generate new quantity and prices and save them. $new_qty = rand(0,99); $new_currency_code = 'EUR'; - $new_price = commerce_currency_integer_to_amount(rand(2, 500), $new_currency_code); + $new_price = commerce_currency_amount_to_decimal(rand(2, 500), $new_currency_code); $edit = array( 'commerce_line_items[und][line_items][' . $line_item_id . '][quantity]' => $new_qty, 'commerce_line_items[und][line_items][' . $line_item_id . '][commerce_unit_price][und][0][amount]' => $new_price, diff --git a/modules/payment/commerce_payment.module b/modules/payment/commerce_payment.module index c17cd90..1f25a3d 100644 --- a/modules/payment/commerce_payment.module +++ b/modules/payment/commerce_payment.module @@ -603,15 +603,7 @@ function _commerce_payment_transaction_create($values = array()) { * The saved transaction object. */ function commerce_payment_transaction_save($transaction) { - // Convert the transaction amount to an integer. - $transaction->amount = commerce_currency_amount_to_integer($transaction->amount, $transaction->currency_code); - - $transaction = entity_get_controller('commerce_payment_transaction')->save($transaction); - - // Convert the amount back from an integer. - $transaction->amount = commerce_currency_integer_to_amount($transaction->amount, $transaction->currency_code); - - return $transaction; + return entity_get_controller('commerce_payment_transaction')->save($transaction); } /** @@ -639,18 +631,7 @@ function commerce_payment_transaction_load($transaction_id) { * An array of transaction objects indexed by transaction_id. */ function commerce_payment_transaction_load_multiple($transaction_ids = array(), $conditions = array(), $reset = FALSE) { - $transactions = entity_load('commerce_payment_transaction', $transaction_ids, $conditions, $reset); - - // Convert the loaded amount integers to price decimal amounts. - foreach ($transactions as $transaction_id => &$transaction) { - // Ensure it happens only once per entity. - if (empty($transaction->amount_converted)) { - $transaction->amount = commerce_currency_integer_to_amount($transaction->amount, $transaction->currency_code); - $transaction->amount_converted = TRUE; - } - } - - return $transactions; + return entity_load('commerce_payment_transaction', $transaction_ids, $conditions, $reset); } /** @@ -855,7 +836,7 @@ function commerce_payment_transaction_access($op, $order, $transaction = NULL, $ */ function commerce_payment_order_balance($order, $totals = array()) { $wrapper = entity_metadata_wrapper('commerce_order', $order); - $order_total = commerce_line_items_total($wrapper->commerce_line_items); + $order_total = $wrapper->commerce_order_total->value(); // Calculate the transaction totals if not supplied. if (empty($totals)) { diff --git a/modules/payment/includes/commerce_payment.forms.inc b/modules/payment/includes/commerce_payment.forms.inc index 08b3f71..0a14df5 100644 --- a/modules/payment/includes/commerce_payment.forms.inc +++ b/modules/payment/includes/commerce_payment.forms.inc @@ -29,6 +29,7 @@ function commerce_payment_order_transaction_add_form($form, &$form_state, $order '#type' => 'fieldset', '#title' => t('Payment terminal: @title', array('@title' => $payment_method['title'])), '#attributes' => array('class' => array('payment-terminal')), + '#element_validate' => array('commerce_payment_order_transaction_add_form_payment_terminal_validate'), ); // Establish defaults for the amount if possible. @@ -44,7 +45,7 @@ function commerce_payment_order_transaction_add_form($form, &$form_state, $order $form['payment_terminal']['amount'] = array( '#type' => 'textfield', '#title' => t('Amount'), - '#default_value' => $default_amount, + '#default_value' => commerce_currency_amount_to_decimal($default_amount, $default_currency_code), '#size' => 10, '#prefix' => '
', ); @@ -166,6 +167,22 @@ function commerce_payment_order_transaction_add_form_add_refresh($form, $form_st } /** + * Validation callback for the payment terminal to check the amount data type + * and convert it to a proper integer amount on input. + */ +function commerce_payment_order_transaction_add_form_payment_terminal_validate($element, &$form_state) { + // If a payment method has already been selected... + if (!empty($form_state['payment_method']) && !empty($form_state['values']['amount'])) { + if (!is_numeric($form_state['values']['amount'])) { + form_set_error('amount', t('You must enter a numeric amount value.')); + } + else { + form_set_value($element['amount'], commerce_currency_decimal_to_amount($form_state['values']['amount'], $form_state['values']['currency_code']), $form_state); + } + } +} + +/** * Validation callback for commerce_payment_order_transaction_add_form(). */ function commerce_payment_order_transaction_add_form_validate($form, &$form_state) { diff --git a/modules/payment/includes/commerce_payment_transaction.controller.inc b/modules/payment/includes/commerce_payment_transaction.controller.inc index ede79bd..81b4384 100644 --- a/modules/payment/includes/commerce_payment_transaction.controller.inc +++ b/modules/payment/includes/commerce_payment_transaction.controller.inc @@ -77,6 +77,9 @@ class CommercePaymentTransactionEntityController extends DrupalDefaultEntityCont $transaction->revision_timestamp = REQUEST_TIME; $update_transaction = TRUE; + // Round the amount to ensure it's an integer for storage. + $transaction->amount = round($transaction->amount); + // Give modules the opportunity to prepare field data for saving. rules_invoke_all('commerce_payment_transaction_presave', $transaction); field_attach_presave('commerce_payment_transaction', $transaction); diff --git a/modules/payment/includes/views/handlers/commerce_payment_handler_field_amount.inc b/modules/payment/includes/views/handlers/commerce_payment_handler_field_amount.inc index 62b6aeb..46de4f6 100644 --- a/modules/payment/includes/views/handlers/commerce_payment_handler_field_amount.inc +++ b/modules/payment/includes/views/handlers/commerce_payment_handler_field_amount.inc @@ -40,32 +40,20 @@ class commerce_payment_handler_field_amount extends views_handler_field { ); } - /** - * Convert the amount integer to a price decimal amount. - * - * No point in using $this->get_value() because we are also setting the - * value as well, and there's no set_value(). - */ - function pre_render(&$values) { - foreach ($values as $key => &$value) { - $value->{$this->field_alias} = commerce_currency_integer_to_amount($value->{$this->field_alias}, $value->{$this->aliases['currency_code']}); - } - } - function render($values) { $value = $this->get_value($values); $currency_code = $this->get_value($values, 'currency_code'); switch ($this->options['display_format']) { case 'formatted': - return commerce_currency_format($value, $currency_code); + return commerce_currency_format($value, $currency_code, NULL, FALSE); case 'raw': // First load the full currency array. $currency = commerce_currency_load($currency_code); // Format the price as a number. - return number_format(commerce_currency_round($value, $currency), $currency['decimals']); + return number_format(commerce_currency_round(commerce_currency_amount_to_decimal($value, $currency_code), $currency), $currency['decimals']); } } } diff --git a/modules/price/commerce_price.module b/modules/price/commerce_price.module index 62ee73b..ce8ba0a 100644 --- a/modules/price/commerce_price.module +++ b/modules/price/commerce_price.module @@ -41,7 +41,7 @@ function commerce_price_field_info() { */ function commerce_price_field_validate($entity_type, $entity, $field, $instance, $langcode, &$items, &$errors) { // Ensure only numeric values are entered in price fields. - foreach ($items as $delta => $item) { + foreach ($items as $delta => &$item) { if (!empty($item['amount']) && !is_numeric($item['amount'])) { $errors[$field['field_name']][$langcode][$delta][] = array( 'error' => 'price_numeric', @@ -58,12 +58,6 @@ function commerce_price_field_load($entity_type, $entities, $field, $instances, // Convert amounts to their floating point values and deserialize data arrays. foreach ($entities as $id => $entity) { foreach ($items[$id] as $delta => $item) { - // Convert the loaded amount integer to a price amount based on the currency. - $items[$id][$delta]['amount'] = commerce_currency_integer_to_amount( - $items[$id][$delta]['amount'], - $items[$id][$delta]['currency_code'] - ); - // Unserialize the data array if necessary. if (!empty($items[$id][$delta]['data'])) { $items[$id][$delta]['data'] = unserialize($items[$id][$delta]['data']); @@ -81,12 +75,6 @@ function commerce_price_field_load($entity_type, $entities, $field, $instances, function commerce_price_field_presave($entity_type, $entity, $field, $instance, $langcode, &$items) { // Convert amounts to integers and serialize data arrays before saving. foreach ($items as $delta => $item) { - // Convert the price amount to an integer based on the currency. - $items[$delta]['amount'] = commerce_currency_amount_to_integer( - $items[$delta]['amount'], - $items[$delta]['currency_code'] - ); - // Serialize an existing data array. if (isset($item['data']) && is_array($item['data'])) { $items[$delta]['data'] = serialize($item['data']); @@ -95,6 +83,76 @@ function commerce_price_field_presave($entity_type, $entity, $field, $instance, } /** + * Converts saved price field data columns back to arrays for use in the rest of + * the current page request execution. + * + * @param $entity_type + * The entity type variable passed through hook_field_attach_*(). + * @param $entity + * The entity variable passed through hook_field_attach_*(). + */ +function _commerce_price_field_attach_unserialize_data($entity_type, $entity) { + $options = array( + 'default' => FALSE, + 'deleted' => FALSE, + 'language' => NULL, + ); + + // Determine the list of instances to iterate on. + list(, , $bundle) = entity_extract_ids($entity_type, $entity); + $instances = _field_invoke_get_instances($entity_type, $bundle, $options); + + // Iterate through the instances and collect results. + $return = array(); + foreach ($instances as $instance) { + $field_name = $instance['field_name']; + $field = field_info_field($field_name); + + // If the instance is a price field with data... + if ($field['type'] == 'commerce_price' && isset($entity->{$field_name})) { + // Iterate over the items arrays for each language. + foreach (array_keys($entity->{$field_name}) as $langcode) { + $items = isset($entity->{$field_name}[$langcode]) ? $entity->{$field_name}[$langcode] : array(); + + // For each item in the array, unserialize or initialize its data array. + foreach ($items as $delta => $item) { + if (!empty($item['data']) && !is_array($item['data'])) { + $entity->{$field_name}[$langcode][$delta]['data'] = unserialize($item['data']); + } + elseif (!is_array($item['data'])) { + $entity->{$field_name}[$langcode][$delta]['data'] = array('components' => array()); + } + } + } + } + } +} + +/** + * Implements hook_field_attach_insert(). + * + * This hook is used to unserialize the price field's data array after it has + * been inserted, because the data array is serialized before it is saved and + * must be unserialized for compatibility with API requests performed during the + * same request after the insert occurs. + */ +function commerce_price_field_attach_insert($entity_type, $entity) { + _commerce_price_field_attach_unserialize_data($entity_type, $entity); +} + +/** + * Implements hook_field_update(). + * + * This hook is used to unserialize the price field's data array after it has + * been updated, because the data array is serialized before it is saved and + * must be unserialized for compatibility with API requests performed during the + * same request after the update occurs. + */ +function commerce_price_field_attach_update($entity_type, $entity) { + _commerce_price_field_attach_unserialize_data($entity_type, $entity); +} + +/** * Implementation of hook_field_is_empty(). */ function commerce_price_field_is_empty($item, $field) { @@ -471,8 +529,8 @@ function commerce_price_field_widget_form(&$form, &$form_state, $field, $instanc if (isset($items[$delta]['amount'])) { $currency = commerce_currency_load($items[$delta]['currency_code']); - // Round the default value. - $default_amount = round($items[$delta]['amount'], 2); + // Convert the price amount to a user friendly decimal value. + $default_amount = commerce_currency_amount_to_decimal($items[$delta]['amount'], $currency['code']); // Run it through number_format() to add the decimal places in if necessary. if (strpos($default_amount, '.') === FALSE || strpos($default_amount, '.') > strlen($default_amount) - $currency['decimals']) { @@ -564,10 +622,20 @@ function commerce_price_field_widget_form(&$form, &$form_state, $field, $instanc '#default_value' => !empty($items[$delta]['data']) ? $items[$delta]['data'] : array('components' => array()), ); + $element['#element_validate'][] = 'commerce_price_field_widget_validate'; + return $element; } /** + * Implements hook_field_widget_validate(). + */ +function commerce_price_field_widget_validate($element, &$form_state) { + // Convert the decimal amount value entered to an integer based amount value. + form_set_value($element['amount'], commerce_currency_decimal_to_amount($element['amount']['#value'], $element['currency_code']['#value']), $form_state); +} + +/** * Implements hook_field_widget_error(). */ function commerce_price_field_widget_error($element, $error, $form, &$form_state) { diff --git a/modules/product/tests/commerce_product_ui.test b/modules/product/tests/commerce_product_ui.test index f72bd9e..939ce7a 100644 --- a/modules/product/tests/commerce_product_ui.test +++ b/modules/product/tests/commerce_product_ui.test @@ -201,7 +201,7 @@ class CommerceProductUIAdminTest extends CommerceBaseTestCase { $this->pass(t('Test the integrity of the edit product form:')); $this->assertFieldByName('sku', $product_wrapper->sku->value(), t('SKU field is present')); $this->assertFieldByName('title', $product_wrapper->title->value(), t('Title field is present')); - $this->assertFieldByName('commerce_price[und][0][amount]', commerce_currency_integer_to_amount($product_wrapper->commerce_price->amount->value(), $product_wrapper->commerce_price->currency_code->value()), t('Price field is present')); + $this->assertFieldByName('commerce_price[und][0][amount]', commerce_currency_amount_to_decimal($product_wrapper->commerce_price->amount->value(), $product_wrapper->commerce_price->currency_code->value()), t('Price field is present')); $this->assertFieldByName('status', $product->status, t('Status field is present')); $this->assertFieldById('edit-submit', t('Save product'), t('Save product button is present')); $this->assertRaw(l('Cancel', 'admin/commerce/products'), t('Cancel link is present')); diff --git a/modules/product_pricing/commerce_product_pricing.module b/modules/product_pricing/commerce_product_pricing.module index d1ec6e2..9e928e2 100644 --- a/modules/product_pricing/commerce_product_pricing.module +++ b/modules/product_pricing/commerce_product_pricing.module @@ -94,7 +94,7 @@ function commerce_product_calculate_sell_price($product, $precalc = FALSE) { // Wrap the line item, swap in the price, and return it. $wrapper = entity_metadata_wrapper('commerce_line_item', $line_item); - $wrapper->commerce_unit_price->amount = commerce_currency_integer_to_amount($result->amount, $result->currency_code); + $wrapper->commerce_unit_price->amount = $result->amount; $wrapper->commerce_unit_price->currency_code = $result->currency_code; // Unserialize the saved prices data array and initialize to an empty @@ -282,10 +282,7 @@ function commerce_product_pre_calculate_sell_prices($from = NULL, $count = 1) { 'field_name' => 'commerce_price', 'language' => !empty($product->language) ? $product->language : '', 'delta' => 0, - 'amount' => commerce_currency_amount_to_integer( - $wrapper->commerce_unit_price->amount->value(), - $wrapper->commerce_unit_price->currency_code->value() - ), + 'amount' => round($wrapper->commerce_unit_price->amount->value()), 'currency_code' => $wrapper->commerce_unit_price->currency_code->value(), 'data' => $wrapper->commerce_unit_price->data->value(), 'created' => time(), diff --git a/modules/tax/commerce_tax.module b/modules/tax/commerce_tax.module index 60f2c3d..35f26fe 100644 --- a/modules/tax/commerce_tax.module +++ b/modules/tax/commerce_tax.module @@ -309,8 +309,8 @@ function commerce_tax_field_attach_form($entity_type, $entity, &$form, &$form_st foreach ($options[$optgroup] as $name => $title) { $tax_rate = commerce_tax_rate_load($name); - // If this tax rates component is included in the price, it must - // be the default! + // If this tax rate's component is included in the price, it + // must be the default! foreach ($form[$key][$form[$key]['#language']][$delta]['data']['#default_value']['components'] as $component) { if ($component['included'] && $component['name'] == $tax_rate['price_component']) { $default = $name; @@ -338,8 +338,12 @@ function commerce_tax_field_attach_form($entity_type, $entity, &$form, &$form_st '#attached' => array( 'css' => array(drupal_get_path('module', 'commerce_tax') . '/theme/commerce_tax.css'), ), - '#element_validate' => array('commerce_tax_price_field_validate'), ); + + // Append a validation handler to the price field's element validate + // array to add the included tax price component after the price has + // been converted from a decimal. + $form[$key][$form[$key]['#language']][$delta]['#element_validate'][] = 'commerce_tax_price_field_validate'; } } } @@ -354,7 +358,6 @@ function commerce_tax_field_attach_form($entity_type, $entity, &$form, &$form_st function commerce_tax_price_field_validate($element, &$form_state) { // Build an array of form parents to the price array. $parents = $element['#parents']; - array_pop($parents); // Get the price array from the form state. $price = $form_state['values']; @@ -364,13 +367,13 @@ function commerce_tax_price_field_validate($element, &$form_state) { } // If a tax was specified... - if (!empty($element['#value'])) { + if (!empty($element['include_tax']['#value'])) { // Remove the include_tax value from the price array and reset the components. unset($price['include_tax']); $price['data']['components'] = array(); // Load and reverse apply the tax. - $tax_rate = commerce_tax_rate_load($element['#value']); + $tax_rate = commerce_tax_rate_load($element['include_tax']['#value']); $amount = $price['amount'] / (1 + $tax_rate['rate']); // Add a base price to the data array.