Skip to content

Commit

Permalink
BB-7247: Incorrect value in totals in shopping list
Browse files Browse the repository at this point in the history
- fixing bug with incorrect calculating with precision > 0
  • Loading branch information
Anton Khristiansen authored Mar 28, 2017
1 parent 6bee774 commit 4a3e02f
Show file tree
Hide file tree
Showing 6 changed files with 110 additions and 45 deletions.
39 changes: 27 additions & 12 deletions src/Oro/Bundle/PricingBundle/Provider/ProductPriceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ public function getMatchedPrices(array $productsPriceCriteria, BasePriceList $pr
$code = $productPriceCriteria->getProductUnit()->getCode();
$quantity = $productPriceCriteria->getQuantity();
$currency = $productPriceCriteria->getCurrency();
$precision = $productPriceCriteria->getProductUnit()->getDefaultPrecision();

$productPrices = array_filter(
$prices,
Expand All @@ -91,38 +92,52 @@ function (array $price) use ($id, $code, $currency) {
}
);

$price = $this->matchPriceByQuantity($productPrices, $quantity);

$result[$productPriceCriteria->getIdentifier()] = $price !== null ? Price::create($price, $currency) : null;
list($price, $matchedQuantity) = $this->matchPriceByQuantity($productPrices, $quantity);
if ($price !== null) {
$result[$productPriceCriteria->getIdentifier()] = Price::create(
$this->recalculatePricePerUnit($price, $matchedQuantity, $precision),
$currency
);
} else {
$result[$productPriceCriteria->getIdentifier()] = null;
}
}

return $result;
}

/**
* @param float $price
* @param float $quantityPerAmount
* @param int $precision
* @return float
*/
protected function recalculatePricePerUnit($price, $quantityPerAmount, $precision)
{
return $precision > 0 ?
$price / $quantityPerAmount :
$price;
}

/**
* @param array $prices
* @param float $expectedQuantity
* @return float
* @return array
*/
protected function matchPriceByQuantity(array $prices, $expectedQuantity)
{
$price = null;

$matchedQuantity = null;
foreach ($prices as $productPrice) {
$quantity = (float)$productPrice['quantity'];

if ($expectedQuantity < $quantity) {
break;
}

if ($expectedQuantity >= $quantity) {
$price = (float)$productPrice['value'];
} else {
break;
$matchedQuantity = $quantity;
}
}

return $price;
return [$price, $matchedQuantity];
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,17 +150,17 @@ public function getMatchingPriceActionDataProvider()
'unit' => 'liter',
'currency' => 'USD',
'expected' => [
'value' => 12.2,
'value' => 1.22,
'currency' => 'USD'
]
],
[
'product' => 'product-1',
'qty' => 100,
'qty' => 120,
'unit' => 'liter',
'currency' => 'USD',
'expected' => [
'value' => 12.2,
'value' => 1.22,
'currency' => 'USD'
]
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,19 +207,18 @@ public function testGetMatchedPrices(
public function getMatchedPricesDataProvider()
{
$currency = 'USD';
$prodUnitQty1 = $this->getProductPriceCriteria(1, $currency);
$prodUnitQty105 = $this->getProductPriceCriteria(10.5, $currency);
$prodUnitQty50 = $this->getProductPriceCriteria(50, $currency);

$repositoryData = $this->getRepositoryData($prodUnitQty50);
$prodUnitQty1 = $this->getProductPriceCriteria(1, $currency, 0);
$prodUnitQty105 = $this->getProductPriceCriteria(10.5, $currency, 0);
$prodUnitQty50 = $this->getProductPriceCriteria(50, $currency, 3);

return [
[
'productPriceCriteria' => [$prodUnitQty1, $prodUnitQty105],
'repositoryData' => $repositoryData,
'productPriceCriteria' => [$prodUnitQty1, $prodUnitQty105, $prodUnitQty50],
'repositoryData' => $this->getRepositoryData($prodUnitQty50),
'expectedData' => [
$prodUnitQty1->getIdentifier() => null,
$prodUnitQty105->getIdentifier() => Price::create(15, $currency),
$prodUnitQty50->getIdentifier() => Price::create(6, $currency),
]
],
];
Expand All @@ -230,12 +229,13 @@ public function getMatchedPricesDataProvider()
* @param string $currency
* @return ProductPriceCriteria
*/
protected function getProductPriceCriteria($quantity, $currency)
protected function getProductPriceCriteria($quantity, $currency, $precision)
{
/** @var Product $product */
$product = $this->getEntity('Oro\Bundle\ProductBundle\Entity\Product', 42);

$productUnit = new ProductUnit();
$productUnit->setDefaultPrecision($precision);
$productUnit->setCode('kg');

return new ProductPriceCriteria($product, $productUnit, $quantity, $currency);
Expand Down Expand Up @@ -269,7 +269,7 @@ protected function getRepositoryData(ProductPriceCriteria $productPriceCriteria)
'id' => $product->getId(),
'code' => $productUnit->getCode(),
'quantity' => 20,
'value' => 300,
'value' => 120,
'currency' => 'USD'
],
[
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,15 +68,18 @@ function ($value) {
}
)
);
$this->productPriceProvider = $this->getMockBuilder('Oro\Bundle\PricingBundle\Provider\ProductPriceProvider')

$this->productPriceProvider = $this
->getMockBuilder('Oro\Bundle\PricingBundle\Provider\ProductPriceProvider')
->disableOriginalConstructor()
->getMock();

$this->doctrineHelper = $this->getMockBuilder('Oro\Bundle\EntityBundle\ORM\DoctrineHelper')
->disableOriginalConstructor()
->getMock();

$this->priceListTreeHandler = $this->getMockBuilder('Oro\Bundle\PricingBundle\Model\PriceListTreeHandler')
$this->priceListTreeHandler = $this
->getMockBuilder('Oro\Bundle\PricingBundle\Model\PriceListTreeHandler')
->disableOriginalConstructor()
->getMock();

Expand All @@ -95,27 +98,42 @@ protected function tearDown()
unset($this->translator, $this->provider);
}

public function testGetSubtotal()
{
$value = 142.0;
/**
* @dataProvider getPriceDataProvider
* @param float $value
* @param string $identifier
* @param float $defaultQuantity
* @param float $quantity
* @param float $expectedValue
* @param int $precision
* @param string $code
*/
public function testGetSubtotal(
$value,
$identifier,
$defaultQuantity,
$quantity,
$expectedValue,
$precision,
$code
) {
$currency = 'USD';
$identifier = '1-code-2-USD';

$this->translator->expects($this->once())
->method('trans')
->with(LineItemNotPricedSubtotalProvider::NAME . '.label')
->willReturn('test');

$product = $this->prepareProduct();
$productUnit = $this->prepareProductUnit();
$productUnit = $this->prepareProductUnit($code, $precision);
$this->prepareEntityManager($product, $productUnit);
$this->preparePrice($value, $identifier);
$this->preparePrice($value, $identifier, $defaultQuantity);

$entity = new EntityNotPricedStub();
$lineItem = new LineItemNotPricedStub();
$lineItem->setProduct($product);
$lineItem->setProductUnit($productUnit);
$lineItem->setQuantity(2);
$lineItem->setQuantity($quantity);

$entity->addLineItem($lineItem);

Expand All @@ -135,7 +153,7 @@ public function testGetSubtotal()
$this->assertEquals('test', $subtotal->getLabel());
$this->assertEquals($entity->getCurrency(), $subtotal->getCurrency());
$this->assertInternalType('float', $subtotal->getAmount());
$this->assertEquals($value * 2, $subtotal->getAmount());
$this->assertEquals($expectedValue, (float)$subtotal->getAmount());
$this->assertTrue($subtotal->isVisible());
}

Expand Down Expand Up @@ -178,15 +196,18 @@ public function testIsNotSupported()
/**
* @return ProductUnit|\PHPUnit_Framework_MockObject_MockObject
*/
protected function prepareProductUnit()
protected function prepareProductUnit($code, $precision)
{
/** @var ProductUnit|\PHPUnit_Framework_MockObject_MockObject $productUnit */
$productUnit = $this->getMockBuilder('Oro\Bundle\ProductBundle\Entity\ProductUnit')
->disableOriginalConstructor()
->getMock();
$productUnit->expects($this->any())
->method('getCode')
->willReturn('code');
->willReturn($code);
$productUnit->expects($this->any())
->method('getDefaultPrecision')
->willReturn($precision);

return $productUnit;
}
Expand All @@ -208,7 +229,7 @@ protected function prepareProduct()
}

/**
* @param Product$product
* @param Product $product
* @param ProductUnit $productUnit
*/
protected function prepareEntityManager(Product $product, ProductUnit $productUnit)
Expand All @@ -229,20 +250,49 @@ protected function prepareEntityManager(Product $product, ProductUnit $productUn
}

/**
* @param float $value
* @param string $identifier
* @param $value
* @param $identifier
* @param $defaultQuantity
*/
protected function preparePrice($value, $identifier)
protected function preparePrice($value, $identifier, $defaultQuantity)
{
/** @var Price|\PHPUnit_Framework_MockObject_MockObject $price */
$price = $this->getMockBuilder('Oro\Bundle\CurrencyBundle\Entity\Price')
->disableOriginalConstructor()
->getMock();
$price->expects($this->any())
->method('getValue')
->willReturn($value);
->willReturn($value / $defaultQuantity);

$this->productPriceProvider->expects($this->any())
->method('getMatchedPrices')
->willReturn([$identifier => $price]);
}

/**
* @return array
*/
public function getPriceDataProvider()
{
return [
'kilogram' => [
'value' => 25.0,
'identifier' => '1-kg-3-USD',
'defaultQuantity' => 0.5,
'quantity' => 3,
'expectedValue' => 150,
'precision' => 3,
'code' => 'kg'
],
'item' => [
'value' => 142.0,
'identifier' => '1-item-2-USD',
'defaultQuantity' => 1,
'quantity' => 2,
'expectedValue' => 284,
'precision' => 0,
'code' => 'item'
],
];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -311,8 +311,8 @@ oro:
short: kg
short_plural: kgs
value:
full: '{0} none|{1} %count% kilogram|]1,Inf] %count% kilograms'
short: '{0} none|{1} %count% kg|]1,Inf] %count% kg'
full: '{0} %count% kilogram|{1} %count% kilogram|]1,Inf] %count% kilograms'
short: '{0}%count% kg|{1} %count% kg|]1,Inf] %count% kg'

product_unit.piece:
label:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,8 @@ public function addProductFromViewDataProvider()
[
'product' => LoadProductData::PRODUCT_2,
'unit' => 'product_unit.liter',
'quantity' => 14,
'expectedSubtotals' => ['EUR' => 1573, 'USD' => 1611.8],
'quantity' => 15,
'expectedSubtotals' => ['EUR' => 1573, 'USD' => 1456.25],
],
[
'product' => LoadProductData::PRODUCT_1,
Expand Down

0 comments on commit 4a3e02f

Please sign in to comment.