Split fixed cart from other discounts in class.

This commit is contained in:
Mike Jolley 2017-07-18 20:42:47 +01:00
parent 37fbd96de8
commit d24faebea8
3 changed files with 135 additions and 104 deletions

View File

@ -954,11 +954,12 @@ class WC_Cart {
// Add item after merging with $cart_item_data - hook to allow plugins to modify cart item
$this->cart_contents[ $cart_item_key ] = apply_filters( 'woocommerce_add_cart_item', array_merge( $cart_item_data, array(
'product_id' => $product_id,
'variation_id' => $variation_id,
'variation' => $variation,
'quantity' => $quantity,
'data' => $product_data,
'key' => $cart_item_key,
'product_id' => $product_id,
'variation_id' => $variation_id,
'variation' => $variation,
'quantity' => $quantity,
'data' => $product_data,
) ), $cart_item_key );
}

View File

@ -20,13 +20,6 @@ class WC_Discounts {
*/
protected $items = array();
/**
* An array of coupons which have been applied.
*
* @var array
*/
protected $coupons = array();
/**
* An array of discounts which have been applied.
*
@ -34,6 +27,14 @@ class WC_Discounts {
*/
protected $discounts = array();
/**
* Reset items and discounts to 0.
*/
protected function reset() {
$this->items = array();
$this->discounts = array( 'cart' => 0 );
}
/**
* Get items.
*
@ -51,40 +52,38 @@ class WC_Discounts {
* @param array $raw_items List of raw cart or order items.
*/
public function set_items( $raw_items ) {
$this->items = array();
$this->reset();
if ( ! empty( $raw_items ) && is_array( $raw_items ) ) {
foreach ( $raw_items as $raw_item ) {
$item = (object) array(
'price' => 0, // Unit price without discounts.
'discounted_price' => 0, // Unit price with discounts.
'quantity' => 0, // Line qty.
'price' => 0, // Line price without discounts.
'quantity' => 0, // Line qty.
'product' => false,
);
if ( is_a( $raw_item, 'WC_Cart_Item' ) ) {
$item->quantity = $raw_item->get_quantity();
$item->price = $raw_item->get_price();
//$item->quantity = $raw_item->get_quantity();
//$item->price = $raw_item->get_price() * $raw_item->get_quantity();
//$item->is_taxable = $raw_item->is_taxable();
//$item->tax_class = $raw_item->get_tax_class();
// @todo
} elseif ( is_a( $raw_item, 'WC_Order_Item_Product' ) ) {
$item->key = $raw_item->get_id();
$item->quantity = $raw_item->get_quantity();
$item->price = $raw_item->get_subtotal();
$item->product = $raw_item->get_product();
} else {
$item->key = $raw_item['key'];
// @todo remove when we implement WC_Cart_Item. This is the old cart item schema.
$item->quantity = $raw_item['quantity'];
$item->price = $raw_item['data']->get_price();
$item->price = $raw_item['data']->get_price() * $raw_item['quantity'];
$item->product = $raw_item['data'];
}
$item->discounted_price = $item->price;
$this->items[] = $item;
$this->items[ $item->key ] = $item;
$this->discounts[ $item->key ] = 0;
}
}
}
public function get_discounted_totals() {}
public function get_applied_discounts() {}
public function get_applied_coupons() {}
/**
* Get all discount totals.
*
@ -92,12 +91,17 @@ class WC_Discounts {
* @return array
*/
public function get_discounts() {
return array(
'items',
'discount_totals' => array(
// 'code' => 'amount'
)
);
return $this->discounts;
}
/**
* Get discount by key.
*
* @since 3.2.0
* @return array
*/
public function get_discount( $key ) {
return isset( $this->discounts[ $key ] ) ? $this->discounts[ $key ] : 0;
}
/**
@ -111,99 +115,105 @@ class WC_Discounts {
if ( ! is_a( $coupon, 'WC_Coupon' ) ) {
return false;
}
$items = $this->get_items();
$type = $coupon->get_discount_type();
$amount = $coupon->get_amount();
switch ( $type ) {
switch ( $coupon->get_discount_type() ) {
case 'percent' :
$this->apply_percentage_discount( $amount );
$this->apply_percentage_discount( $coupon->get_amount() );
break;
case 'fixed_product' :
$this->apply_fixed_product_discount( $amount );
$this->apply_fixed_product_discount( $coupon->get_amount() );
break;
case 'fixed_cart' :
$this->apply_fixed_cart_discount( $amount );
$this->apply_fixed_cart_discount( $coupon->get_amount() );
break;
}
}
/**
* Get discounted price of an item.
*
* @since 3.2.0
* @param object $item
* @return float
*/
public function get_discounted_price( $item ) {
return $item->price - $this->discounts[ $item->key ];
}
/**
* Apply a discount amount to an item and ensure it does not go negative.
*
* @since 3.2.0
* @param object $item
* @param float $discount
* @return float
*/
protected function apply_discount_to_item( &$item, $discount ) {
$discounted_price = $this->get_discounted_price( $item );
$discount = $discount > $discounted_price ? $discounted_price : $discount;
$this->discounts[ $item->key ] = $this->discounts[ $item->key ] + $discount;
return $discount;
}
/**
* Apply a discount amount to the cart.
*
* @since 3.2.0
* @param object $item
* @param float $discount
*/
protected function apply_discount_to_cart( $discount ) {
$this->discounts['cart'] += $discount;
}
/**
* Apply percent discount to items.
*
* @since 3.2.0
* @param float $amount
*/
private function apply_percentage_discount( $amount ) {
protected function apply_percentage_discount( $amount ) {
foreach ( $this->items as $item ) {
$this->apply_discount_to_item( $item, (float) $amount * ( $item->discounted_price / 100 ) );
$this->apply_discount_to_item( $item, (float) $amount * ( $this->get_discounted_price( $item ) / 100 ) );
}
}
/**
* Apply fixed product discount to items.
*
* @since 3.2.0
* @param float $amount
*/
private function apply_fixed_product_discount( $discount ) {
protected function apply_fixed_product_discount( $discount ) {
foreach ( $this->items as $item ) {
$this->apply_discount_to_item( $item, $discount );
$this->apply_discount_to_item( $item, $discount * $item->quantity );
}
}
/**
* Apply fixed cart discount to items.
*
* @param float $amount
*/
private function apply_fixed_cart_discount( $total_amount ) {
// Fixed amount needs to be divided equally between items.
$item_count = array_sum( wp_list_pluck( $this->items, 'quantity' ) );
$discount = floor( $total_amount / $item_count );
$discounted = 0; // Keep track of what actually gets discounted, since some products may be cheaper than the discount.
if ( 0 < $discount ) {
foreach ( $this->items as $item ) {
$discounted += $this->apply_discount_to_item( $item, $discount );
}
// Anything left?
if ( $discounted && ( $remaining_discount = $total_amount - $discounted ) ) {
$this->apply_fixed_cart_discount( $remaining_discount );
}
// Amount is too small to divide equally.
} elseif ( $total_amount ) {
$discount = $total_amount;
while ( $discount > 0 ) {
$did_discount = false;
foreach ( $this->items as $item ) {
if ( $this->apply_discount_to_item( $item, 0.1 ) ) {
$discount -= 0.1;
$did_discount = true;
}
}
if ( ! $did_discount ) {
break;
}
}
}
}
/*protected function filter_taxable_items( $item ) {
return $item->is_taxable;
}*/
/**
* Apply a discount amount to an item and ensure it does not go negative.
* Apply fixed cart discount to items. Cart discounts will be stored and
* displayed separately to items, and taxes will be apportioned.
*
* @param object $item
* @param float $discount
* @return float
* @since 3.2.0
* @param float $cart_discount
*/
private function apply_discount_to_item( &$item, $discount ) {
if ( $discount > $item->discounted_price ) {
$discount = $item->discounted_price;
protected function apply_fixed_cart_discount( $cart_discount ) {
// @todo Fixed cart discounts will be apportioned based on tax class.
/*$tax_class_counts = array_count_values( wp_list_pluck( array_filter( $this->items, array( $this, 'filter_taxable_items' ) ), 'tax_class' ) );
$item_count = array_sum( wp_list_pluck( $this->items, 'quantity' ) );
$cart_discount_taxes = array();
foreach ( $tax_class_counts as $tax_class => $tax_class_count ) {
$proportion = $tax_class_count / $item_count;
$cart_discount_proportion = $cart_discount * $proportion;
$tax_rates = WC_Tax::get_rates( $tax_class );
$cart_discount_taxes[ $tax_class ] = WC_Tax::calc_tax( $cart_discount_proportion, $tax_rates );
}
$item->discounted_price = $item->discounted_price - $discount;
return $discount;
var_dump($cart_discount_taxes);*/
$this->apply_discount_to_cart( $cart_discount );
}
}

View File

@ -25,12 +25,12 @@ class WC_Tests_Discounts extends WC_Unit_Test_Case {
// Test setting items to the cart.
$discounts = new WC_Discounts();
$discounts->set_items( WC()->cart->get_cart() );
$this->assertEquals( array( (object) array( 'price' => '10', 'discounted_price' => '10', 'quantity' => 1 ) ), $discounts->get_items() );
$this->assertEquals( 1, count( $discounts->get_items() ) );
// Test setting items to an order.
$discounts = new WC_Discounts();
$discounts->set_items( $order->get_items() );
$this->assertEquals( array( (object) array( 'price' => '10', 'discounted_price' => '10', 'quantity' => 1 ) ), $discounts->get_items() );
$this->assertEquals( 1, count( $discounts->get_items() ) );
// Empty array of items.
$discounts = new WC_Discounts();
@ -55,7 +55,22 @@ class WC_Tests_Discounts extends WC_Unit_Test_Case {
$discounts = new WC_Discounts();
// Create dummy content.
$tax_rate = array(
'tax_rate_country' => '',
'tax_rate_state' => '',
'tax_rate' => '20.0000',
'tax_rate_name' => 'VAT',
'tax_rate_priority' => '1',
'tax_rate_compound' => '0',
'tax_rate_shipping' => '1',
'tax_rate_order' => '1',
'tax_rate_class' => '',
);
$tax_rate_id = WC_Tax::_insert_tax_rate( $tax_rate );
update_option( 'woocommerce_calc_taxes', 'yes' );
$product = WC_Helper_Product::create_simple_product();
$product->set_tax_status( 'taxable' );
$product->save();
WC()->cart->empty_cart();
WC()->cart->add_to_cart( $product->get_id(), 1 );
$coupon = new WC_Coupon;
@ -67,13 +82,13 @@ class WC_Tests_Discounts extends WC_Unit_Test_Case {
$coupon->set_discount_type( 'percent' );
$discounts->set_items( WC()->cart->get_cart() );
$discounts->apply_coupon( $coupon );
$this->assertEquals( array( (object) array( 'price' => '10', 'discounted_price' => '8', 'quantity' => 1 ) ), $discounts->get_items() );
$this->assertEquals( 8, $discounts->get_discounted_price( current( $discounts->get_items() ) ) );
// Apply a fixed cart coupon.
$coupon->set_discount_type( 'fixed_cart' );
$discounts->set_items( WC()->cart->get_cart() );
$discounts->apply_coupon( $coupon );
$this->assertEquals( array( (object) array( 'price' => '10', 'discounted_price' => '0', 'quantity' => 1 ) ), $discounts->get_items() );
$this->assertEquals( 20, $discounts->get_discount( 'cart' ) );
// Apply a fixed cart coupon.
$coupon->set_discount_type( 'fixed_cart' );
@ -81,7 +96,10 @@ class WC_Tests_Discounts extends WC_Unit_Test_Case {
WC()->cart->add_to_cart( $product->get_id(), 4 );
$discounts->set_items( WC()->cart->get_cart() );
$discounts->apply_coupon( $coupon );
$this->assertEquals( array( (object) array( 'price' => '40', 'discounted_price' => '20', 'quantity' => 4 ) ), $discounts->get_items() );
$this->assertEquals( 20, $discounts->get_discount( 'cart' ) );
$discounts->apply_coupon( $coupon );
$this->assertEquals( 40, $discounts->get_discount( 'cart' ) );
// Apply a fixed product coupon.
$coupon->set_discount_type( 'fixed_product' );
@ -90,10 +108,12 @@ class WC_Tests_Discounts extends WC_Unit_Test_Case {
WC()->cart->add_to_cart( $product->get_id(), 4 );
$discounts->set_items( WC()->cart->get_cart() );
$discounts->apply_coupon( $coupon );
$this->assertEquals( array( (object) array( 'price' => '40', 'discounted_price' => '36', 'quantity' => 4 ) ), $discounts->get_items() );
$this->assertEquals( 36, $discounts->get_discounted_price( current( $discounts->get_items() ) ) );
// Cleanup.
WC()->cart->empty_cart();
$product->delete( true );
WC_Tax::_delete_tax_rate( $tax_rate_id );
update_option( 'woocommerce_calc_taxes', 'no' );
}
}