fees pass
This commit is contained in:
parent
ee545e7793
commit
e8e200195f
|
@ -1,14 +1,26 @@
|
|||
<?php
|
||||
/**
|
||||
* Order/cart totals calculation class.
|
||||
*
|
||||
* Methods are private and class is final to keep this as an internal API.
|
||||
* May be opened in the future once structure is stable.
|
||||
*
|
||||
* @author Automattic
|
||||
* @package WooCommerce/Classes
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Order/cart totals calculation class.
|
||||
* WC_Totals class.
|
||||
*
|
||||
* @author Automattic
|
||||
* @package WooCommerce/Classes
|
||||
* @version 3.2.0
|
||||
* @todo consider extending this for cart vs orders if lots of conditonal logic is needed.
|
||||
* @todo Instead of setting cart totals from here, do it from a subclass.
|
||||
* @todo woocommerce_tax_round_at_subtotal option - how should we handle this with precision?
|
||||
* @todo woocommerce_calculate_totals action for carts.
|
||||
* @todo woocommerce_calculated_total filter for carts.
|
||||
* @since 3.2.0
|
||||
*/
|
||||
class WC_Totals {
|
||||
|
@ -19,7 +31,7 @@ class WC_Totals {
|
|||
* @since 3.2.0
|
||||
* @var array
|
||||
*/
|
||||
protected $object;
|
||||
private $object;
|
||||
|
||||
/**
|
||||
* Line items to calculate.
|
||||
|
@ -27,37 +39,23 @@ class WC_Totals {
|
|||
* @since 3.2.0
|
||||
* @var array
|
||||
*/
|
||||
protected $items = array(); // @todo ?
|
||||
private $items = array();
|
||||
|
||||
/**
|
||||
* Discount amounts in cents after calculation.
|
||||
* Fees to calculate.
|
||||
*
|
||||
* @since 3.2.0
|
||||
* @var array
|
||||
*/
|
||||
protected $discounts = array();
|
||||
private $fees = array();
|
||||
|
||||
/**
|
||||
* Stores totals.
|
||||
* Discount amounts in cents after calculation for the cart.
|
||||
*
|
||||
* @since 3.2.0
|
||||
* @var array
|
||||
*/
|
||||
protected $totals = array(
|
||||
'fees_total' => 0,
|
||||
'fees_total_tax' => 0,
|
||||
'items_subtotal' => 0,
|
||||
'items_subtotal_tax' => 0,
|
||||
'items_total' => 0,
|
||||
'items_total_tax' => 0,
|
||||
'total' => 0,
|
||||
'taxes' => array(),
|
||||
'tax_total' => 0,
|
||||
'shipping_total' => 0,
|
||||
'shipping_tax_total' => 0,
|
||||
'discounts_total' => 0,
|
||||
'discounts_tax_total' => 0,
|
||||
);
|
||||
private $discount_totals = array();
|
||||
|
||||
/**
|
||||
* Precision so we can work in cents.
|
||||
|
@ -65,12 +63,35 @@ class WC_Totals {
|
|||
* @since 3.2.0
|
||||
* @var int
|
||||
*/
|
||||
protected $precision = 1;
|
||||
private $precision = 1;
|
||||
|
||||
/**
|
||||
* Stores totals.
|
||||
*
|
||||
* @since 3.2.0
|
||||
* @var array
|
||||
*/
|
||||
private $totals = array(
|
||||
'fees_total' => 0,
|
||||
'fees_total_tax' => 0,
|
||||
'items_subtotal' => 0,
|
||||
'items_subtotal_tax' => 0,
|
||||
'items_total' => 0,
|
||||
'items_total_tax' => 0,
|
||||
'total' => 0,
|
||||
'taxes' => array(),
|
||||
'tax_total' => 0,
|
||||
'shipping_total' => 0,
|
||||
'shipping_tax_total' => 0,
|
||||
'discounts_total' => 0,
|
||||
'discounts_tax_total' => 0,
|
||||
);
|
||||
|
||||
/**
|
||||
* Sets up the items provided, and calculate totals.
|
||||
*
|
||||
* @since 3.2.0
|
||||
* @param object $cart Cart or order object to calculate totals for.
|
||||
*/
|
||||
public function __construct( &$cart = null ) {
|
||||
$this->precision = pow( 10, wc_get_price_decimals() );
|
||||
|
@ -80,11 +101,12 @@ class WC_Totals {
|
|||
}
|
||||
|
||||
/**
|
||||
* Handles a cart or order object passed in for calculation. Normalises data.
|
||||
* Handles a cart or order object passed in for calculation. Normalises data
|
||||
* into the same format for use by this class.
|
||||
*
|
||||
* @since 3.2.0
|
||||
*/
|
||||
protected function set_items() {
|
||||
private function set_items() {
|
||||
if ( is_a( $this->object, 'WC_Cart' ) ) {
|
||||
foreach ( $this->object->get_cart() as $cart_item_key => $cart_item ) {
|
||||
$item = $this->get_default_item_props();
|
||||
|
@ -98,13 +120,13 @@ class WC_Totals {
|
|||
}
|
||||
|
||||
/**
|
||||
* Remove precision from a price.
|
||||
* Remove precision (deep) from a price.
|
||||
*
|
||||
* @since 3.2.0
|
||||
* @param int $value
|
||||
* @param int|array $value Value to remove precision from.
|
||||
* @return float
|
||||
*/
|
||||
protected function remove_precision( $value ) {
|
||||
private function remove_precision( $value ) {
|
||||
if ( is_array( $value ) ) {
|
||||
foreach ( $value as $key => $subvalue ) {
|
||||
$value[ $key ] = $this->remove_precision( $subvalue );
|
||||
|
@ -121,8 +143,12 @@ class WC_Totals {
|
|||
* @since 3.2.0
|
||||
* @return array
|
||||
*/
|
||||
protected function get_default_item_props() {
|
||||
private function get_default_item_props() {
|
||||
return (object) array(
|
||||
'key' => '',
|
||||
'quantity' => 0,
|
||||
'price' => 0,
|
||||
'product' => false,
|
||||
'price_includes_tax' => wc_prices_include_tax(),
|
||||
'subtotal' => 0,
|
||||
'subtotal_tax' => 0,
|
||||
|
@ -134,6 +160,19 @@ class WC_Totals {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default blank set of props used per fee.
|
||||
*
|
||||
* @since 3.2.0
|
||||
* @return array
|
||||
*/
|
||||
private function get_default_fee_props() {
|
||||
return (object) array(
|
||||
'total_tax' => 0,
|
||||
'taxes' => array(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Only ran if woocommerce_adjust_non_base_location_prices is true.
|
||||
*
|
||||
|
@ -141,16 +180,18 @@ class WC_Totals {
|
|||
* taxes. This is off by default unless the filter is used.
|
||||
*
|
||||
* @since 3.2.0
|
||||
* @param object $item Item to adjust the prices of.
|
||||
* @return object
|
||||
*/
|
||||
protected function adjust_non_base_location_price( $item ) {
|
||||
private function adjust_non_base_location_price( $item ) {
|
||||
$base_tax_rates = WC_Tax::get_base_tax_rates( $item->product->tax_class );
|
||||
$item_tax_rates = $this->get_item_tax_rates( $item );
|
||||
|
||||
if ( $item_tax_rates !== $base_tax_rates ) {
|
||||
// Work out a new base price without the shop's base tax
|
||||
// Work out a new base price without the shop's base tax.
|
||||
$taxes = WC_Tax::calc_tax( $item->price, $base_tax_rates, true, true );
|
||||
|
||||
// Now we have a new item price (excluding TAX)
|
||||
// Now we have a new item price (excluding TAX).
|
||||
$item->price = $item->price - array_sum( $taxes );
|
||||
$item->price_includes_tax = false;
|
||||
}
|
||||
|
@ -161,20 +202,20 @@ class WC_Totals {
|
|||
* Get discounted price of an item with precision (in cents).
|
||||
*
|
||||
* @since 3.2.0
|
||||
* @param object $item
|
||||
* @param object $item Item to get the price of.
|
||||
* @return int
|
||||
*/
|
||||
protected function get_discounted_price_in_cents( $item ) {
|
||||
return $item->price - $this->totals['discounts'][ $item->key ];
|
||||
private function get_discounted_price_in_cents( $item ) {
|
||||
return $item->price - $this->discount_totals[ $item->key ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tax rates for an item. Caches rates in class to avoid multiple look ups.
|
||||
*
|
||||
* @param object $item
|
||||
* @param object $item Item to get tax rates for.
|
||||
* @return array of taxes
|
||||
*/
|
||||
protected function get_item_tax_rates( $item ) {
|
||||
private function get_item_tax_rates( $item ) {
|
||||
$tax_class = $item->product->get_tax_class();
|
||||
return isset( $this->item_tax_rates[ $tax_class ] ) ? $this->item_tax_rates[ $tax_class ] : $this->item_tax_rates[ $tax_class ] = WC_Tax::get_rates( $item->product->get_tax_class() );
|
||||
}
|
||||
|
@ -185,7 +226,7 @@ class WC_Totals {
|
|||
* @since 3.2.0
|
||||
* @return array
|
||||
*/
|
||||
protected function get_coupons() {
|
||||
private function get_coupons() {
|
||||
if ( is_a( $this->object, 'WC_Cart' ) ) {
|
||||
return $this->object->get_coupons();
|
||||
}
|
||||
|
@ -197,27 +238,27 @@ class WC_Totals {
|
|||
* @since 3.2.0
|
||||
* @return array
|
||||
*/
|
||||
protected function get_shipping() {
|
||||
private function get_shipping() {
|
||||
// @todo get this somehow. Where does calc occur?
|
||||
return array();
|
||||
}
|
||||
|
||||
protected function get_discounts() {
|
||||
/**
|
||||
* Get discounts.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_discounts() {
|
||||
// @todo fee style API for discounts in cart/checkout.
|
||||
return array();
|
||||
}
|
||||
|
||||
protected function get_fees() {
|
||||
// @todo where should fee api be located? New class?
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a single total with or without precision (in cents).
|
||||
*
|
||||
* @since 3.2.0
|
||||
* @param string $key Total to get.
|
||||
* @param bool $in_cents
|
||||
* @param bool $in_cents Should the totals be returned in cents, or without precision.
|
||||
* @return int|float
|
||||
*/
|
||||
public function get_total( $key = 'total', $in_cents = false ) {
|
||||
|
@ -229,10 +270,10 @@ class WC_Totals {
|
|||
* Set a single total.
|
||||
*
|
||||
* @since 3.2.0
|
||||
* @param string $key
|
||||
* @param int $total
|
||||
* @param string $key Total name you want to set.
|
||||
* @param int $total Total to set.
|
||||
*/
|
||||
protected function set_total( $key = 'total', $total ) {
|
||||
private function set_total( $key = 'total', $total ) {
|
||||
$this->totals[ $key ] = $total;
|
||||
}
|
||||
|
||||
|
@ -240,7 +281,7 @@ class WC_Totals {
|
|||
* Get all totals with or without precision (in cents).
|
||||
*
|
||||
* @since 3.2.0
|
||||
* @param $in_cents bool
|
||||
* @param bool $in_cents Should the totals be returned in cents, or without precision.
|
||||
* @return array.
|
||||
*/
|
||||
public function get_totals( $in_cents = false ) {
|
||||
|
@ -255,22 +296,29 @@ class WC_Totals {
|
|||
* @since 3.2.0
|
||||
* @return array
|
||||
*/
|
||||
protected function get_merged_taxes() {
|
||||
private function get_merged_taxes() {
|
||||
$taxes = array();
|
||||
|
||||
foreach ( array_merge( $this->items, $this->fees, $this->get_shipping() ) as $item ) {
|
||||
foreach ( $item->taxes as $rate_id => $rate ) {
|
||||
$taxes[ $rate_id ] = array( 'tax_total' => 0, 'shipping_tax_total' => 0 );
|
||||
}
|
||||
}
|
||||
|
||||
foreach ( $this->items as $item ) {
|
||||
foreach ( $item->taxes as $rate_id => $rate ) {
|
||||
if ( ! isset( $taxes[ $rate_id ] ) ) {
|
||||
$taxes[ $rate_id ] = array( 'tax_total' => 0, 'shipping_tax_total' => 0 );
|
||||
}
|
||||
$taxes[ $rate_id ]['tax_total'] = $taxes[ $rate_id ]['tax_total'] + $rate;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ( $this->fees as $fee ) {
|
||||
foreach ( $fee->taxes as $rate_id => $rate ) {
|
||||
$taxes[ $rate_id ]['tax_total'] = $taxes[ $rate_id ]['tax_total'] + $rate;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ( $this->get_shipping() as $item ) {
|
||||
foreach ( $item->taxes as $rate_id => $rate ) {
|
||||
if ( ! isset( $taxes[ $rate_id ] ) ) {
|
||||
$taxes[ $rate_id ] = array( 'tax_total' => 0, 'shipping_tax_total' => 0 );
|
||||
}
|
||||
$taxes[ $rate_id ]['shipping_tax_total'] = $taxes[ $rate_id ]['shipping_tax_total'] + $rate;
|
||||
}
|
||||
}
|
||||
|
@ -284,11 +332,11 @@ class WC_Totals {
|
|||
*/
|
||||
|
||||
/**
|
||||
* Run all calculations methods on the given items.
|
||||
* Run all calculations methods on the given items in sequence.
|
||||
*
|
||||
* @since 3.2.0
|
||||
*/
|
||||
protected function calculate() {
|
||||
private function calculate() {
|
||||
$this->calculate_item_subtotals();
|
||||
$this->calculate_discounts();
|
||||
$this->calculate_item_totals();
|
||||
|
@ -311,7 +359,7 @@ class WC_Totals {
|
|||
*
|
||||
* @since 3.2.0
|
||||
*/
|
||||
protected function calculate_item_subtotals() {
|
||||
private function calculate_item_subtotals() {
|
||||
foreach ( $this->items as $item ) {
|
||||
if ( $item->price_includes_tax && apply_filters( 'woocommerce_adjust_non_base_location_prices', true ) ) {
|
||||
$item = $this->adjust_non_base_location_price( $item );
|
||||
|
@ -336,23 +384,21 @@ class WC_Totals {
|
|||
/**
|
||||
* Calculate all discount and coupon amounts.
|
||||
*
|
||||
* @todo Manual discounts.
|
||||
*
|
||||
* @since 3.2.0
|
||||
* @uses WC_Discounts class.
|
||||
*/
|
||||
protected function calculate_discounts() {
|
||||
private function calculate_discounts() {
|
||||
$discounts = new WC_Discounts( $this->items );
|
||||
|
||||
foreach ( $this->get_coupons() as $coupon ) {
|
||||
$discounts->apply_coupon( $coupon );
|
||||
}
|
||||
|
||||
foreach ( $this->get_discounts() as $discount ) {
|
||||
//$discounts->apply_discount( $coupon ); @todo
|
||||
}
|
||||
|
||||
$this->totals['discounts'] = $discounts->get_discounts();
|
||||
$this->totals['discounts_total'] = array_sum( $this->totals['discounts'] );
|
||||
// $this->totals['discounts_tax_total'] = $value;
|
||||
$this->discount_totals = $discounts->get_discounts();
|
||||
$this->totals['discounts_total'] = array_sum( $this->discount_totals );
|
||||
// @todo $this->totals['discounts_tax_total'] = $value;
|
||||
|
||||
/*$this->set_coupon_totals( wp_list_pluck( $this->coupons, 'total' ) );
|
||||
//$this->set_coupon_tax_totals( wp_list_pluck( $this->coupons, 'total_tax' ) );
|
||||
|
@ -360,11 +406,11 @@ class WC_Totals {
|
|||
}
|
||||
|
||||
/**
|
||||
* Totals are costs after discounts.
|
||||
* Totals are costs after discounts. @todo move cart specific setters to subclass?
|
||||
*
|
||||
* @since 3.2.0
|
||||
*/
|
||||
protected function calculate_item_totals() {
|
||||
private function calculate_item_totals() {
|
||||
foreach ( $this->items as $item ) {
|
||||
$item->total = $this->get_discounted_price_in_cents( $item );
|
||||
$item->total_tax = 0;
|
||||
|
@ -382,20 +428,45 @@ class WC_Totals {
|
|||
}
|
||||
$this->set_total( 'items_total', array_sum( array_values( wp_list_pluck( $this->items, 'total' ) ) ) );
|
||||
$this->set_total( 'items_total_tax', array_sum( array_values( wp_list_pluck( $this->items, 'total_tax' ) ) ) );
|
||||
|
||||
$this->object->subtotal = array_sum( wp_list_pluck( $this->items, 'total' ) ) + array_sum( wp_list_pluck( $this->items, 'total_tax' ) );
|
||||
$this->object->subtotal_ex_tax = array_sum( wp_list_pluck( $this->items, 'total' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate any fees taxes.
|
||||
* Triggers the cart fees API, grabs the list of fees, and calculates taxes.
|
||||
*
|
||||
* Note: This class sets the totals for the 'object' as they are calculated. This is so that APIs like the fees API can see these totals if needed.
|
||||
*
|
||||
* @since 3.2.0
|
||||
* @todo logic is unqiue to carts.
|
||||
*/
|
||||
protected function calculate_fee_totals() {
|
||||
foreach ( $this->get_fees() as $fee_key => $fee ) {
|
||||
if ( wc_tax_enabled() && $fee->taxable ) {
|
||||
$fee->taxes = WC_Tax::calc_tax( $fee->total, $tax_rates, false );
|
||||
private function calculate_fee_totals() {
|
||||
$this->object->calculate_fees();
|
||||
|
||||
foreach ( $this->object->get_fees() as $fee_key => $fee_object ) {
|
||||
$fee = $this->get_default_fee_props();
|
||||
$fee->object = $fee_object;
|
||||
$fee->total = $fee->object->amount * $this->precision;
|
||||
|
||||
if ( wc_tax_enabled() && $fee->object->taxable ) {
|
||||
$fee->taxes = WC_Tax::calc_tax( $fee->total, WC_Tax::get_rates( $fee->object->tax_class ), false );
|
||||
$fee->total_tax = array_sum( $fee->taxes );
|
||||
}
|
||||
|
||||
$this->fees[ $fee_key ] = $fee;
|
||||
}
|
||||
|
||||
// Store totals to self.
|
||||
$this->set_total( 'fees_total', array_sum( wp_list_pluck( $this->fees, 'total' ) ) );
|
||||
$this->set_total( 'fees_total_tax', array_sum( wp_list_pluck( $this->fees, 'total_tax' ) ) );
|
||||
|
||||
// Transfer totals to the cart.
|
||||
foreach ( $this->fees as $fee_key => $fee ) {
|
||||
$this->object->fees[ $fee_key ]->tax = $this->remove_precision( $fee->total_tax );
|
||||
$this->object->fees[ $fee_key ]->tax_data = $this->remove_precision( $fee->taxes );
|
||||
}
|
||||
$this->object->fee_total = $this->remove_precision( array_sum( wp_list_pluck( $this->fees, 'total' ) ) );
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -403,7 +474,7 @@ class WC_Totals {
|
|||
*
|
||||
* @since 3.2.0
|
||||
*/
|
||||
protected function calculate_shipping_totals() {
|
||||
private function calculate_shipping_totals() {
|
||||
//$this->set_shipping_total( array_sum( array_values( wp_list_pluck( $this->shipping, 'total' ) ) ) );
|
||||
}
|
||||
|
||||
|
@ -412,20 +483,10 @@ class WC_Totals {
|
|||
*
|
||||
* @since 3.2.0
|
||||
*/
|
||||
protected function calculate_totals() {
|
||||
private function calculate_totals() {
|
||||
$this->set_total( 'taxes', $this->get_merged_taxes() );
|
||||
$this->set_total( 'tax_total', array_sum( wp_list_pluck( $this->get_total( 'taxes', true ), 'tax_total' ) ) );
|
||||
$this->set_total( 'shipping_tax_total', array_sum( wp_list_pluck( $this->get_total( 'taxes', true ), 'shipping_tax_total' ) ) );
|
||||
|
||||
//$this->set_fees_total( array_sum( array_values( wp_list_pluck( $this->fees, 'total' ) ) ) );
|
||||
//$this->set_fees_total_tax( array_sum( array_values( wp_list_pluck( $this->fees, 'total_tax' ) ) ) );
|
||||
|
||||
|
||||
$this->set_total( 'total', round( $this->get_total( 'items_total', true ) + $this->get_total( 'fees_total', true ) + $this->get_total( 'shipping_total', true ) + $this->get_total( 'tax_total', true ) + $this->get_total( 'shipping_tax_total', true ) ) );
|
||||
|
||||
|
||||
// @todo woocommerce_tax_round_at_subtotal option - how should we handle this with precision?
|
||||
// @todo woocommerce_calculate_totals action for carts.
|
||||
// @todo woocommerce_calculated_total filter for carts.
|
||||
}
|
||||
}
|
||||
|
|
|
@ -35,6 +35,9 @@ class WC_Tests_Totals extends WC_Unit_Test_Case {
|
|||
$product = WC_Helper_Product::create_simple_product();
|
||||
$product2 = WC_Helper_Product::create_simple_product();
|
||||
|
||||
WC_Helper_Shipping::create_simple_flat_rate();
|
||||
WC()->session->set( 'chosen_shipping_methods', array( 'flat_rate' ) );
|
||||
|
||||
$coupon = new WC_Coupon;
|
||||
$coupon->set_code( 'test-coupon-10' );
|
||||
$coupon->set_amount( 10 );
|
||||
|
@ -48,21 +51,32 @@ class WC_Tests_Totals extends WC_Unit_Test_Case {
|
|||
|
||||
WC()->cart->add_to_cart( $product->get_id(), 1 );
|
||||
WC()->cart->add_to_cart( $product2->get_id(), 2 );
|
||||
WC()->cart->add_fee( "test fee", 10, true );
|
||||
WC()->cart->add_fee( "test fee 2", 20, true );
|
||||
WC()->cart->add_fee( "test fee non-taxable", 10, false );
|
||||
WC()->cart->add_discount( $coupon->get_code() );
|
||||
|
||||
add_action( 'woocommerce_cart_calculate_fees', array( $this, 'add_cart_fees_callback' ) );
|
||||
|
||||
// @todo manual discounts
|
||||
$this->totals = new WC_Totals( WC()->cart );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add fees when the fees API is called.
|
||||
*/
|
||||
public function add_cart_fees_callback() {
|
||||
WC()->cart->add_fee( "test fee", 10, true );
|
||||
WC()->cart->add_fee( "test fee 2", 20, true );
|
||||
WC()->cart->add_fee( "test fee non-taxable", 10, false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up after test.
|
||||
*/
|
||||
public function tearDown() {
|
||||
WC()->cart->empty_cart();
|
||||
WC()->session->set( 'chosen_shipping_methods', array() );
|
||||
WC_Helper_Shipping::delete_simple_flat_rate();
|
||||
update_option( 'woocommerce_calc_taxes', 'no' );
|
||||
remove_action( 'woocommerce_cart_calculate_fees', array( $this, 'add_cart_fees_callback' ) );
|
||||
|
||||
foreach ( $this->products as $product ) {
|
||||
$product->delete( true );
|
||||
|
@ -88,8 +102,13 @@ class WC_Tests_Totals extends WC_Unit_Test_Case {
|
|||
'items_subtotal_tax' => 6.00,
|
||||
'items_total' => 27.00,
|
||||
'items_total_tax' => 5.40,
|
||||
'total' => 72.40,
|
||||
'taxes' => array(), // @todo ?
|
||||
'total' => 78.40,
|
||||
'taxes' => array(
|
||||
1 => array(
|
||||
'tax_total' => 11.40,
|
||||
'shipping_tax_total' => 0.00,
|
||||
)
|
||||
),
|
||||
'tax_total' => 11.40,
|
||||
'shipping_total' => 0, // @todo ?
|
||||
'shipping_tax_total' => 0, // @todo ?
|
||||
|
|
Loading…
Reference in New Issue