reports correct sum with currencies and label

This commit is contained in:
Fernando Caraballo Ortiz 2016-01-20 12:10:11 +00:00
parent 90ccc116dd
commit 069ead1e76
17 changed files with 179 additions and 14 deletions

View file

@ -226,6 +226,7 @@ class BookController extends ResourceController
* Removed ``Address`` relations to ``Country`` and ``Province`` objects, their unique ``code`` is used instead;
* Removed specific ``ZoneMembers`` i.e. ``ProvinceZoneMember`` in favor of a dynamic ``ZoneMember``;
* https://github.com/Sylius/Sylius/pull/3696
* ``exchangeRate`` is now recorded for ``Order`` at time of purchase for accurate cross-currency reporting.
### Order and SyliusOrderBundle

View file

@ -0,0 +1,34 @@
<?php
namespace Sylius\Migrations;
use Doctrine\DBAL\Migrations\AbstractMigration;
use Doctrine\DBAL\Schema\Schema;
/**
* Auto-generated Migration: Please modify to your needs!
*/
class Version20160118140609 extends AbstractMigration
{
/**
* @param Schema $schema
*/
public function up(Schema $schema)
{
// this up() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql', 'Migration can only be executed safely on \'mysql\'.');
$this->addSql('ALTER TABLE sylius_order ADD exchange_rate NUMERIC(10, 5) NOT NULL');
}
/**
* @param Schema $schema
*/
public function down(Schema $schema)
{
// this down() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql', 'Migration can only be executed safely on \'mysql\'.');
$this->addSql('ALTER TABLE sylius_order DROP exchange_rate');
}
}

View file

@ -250,6 +250,7 @@ default:
- Behat\MinkExtension\Context\MinkContext
- Sylius\Bundle\CoreBundle\Behat\HookContext
- Sylius\Bundle\AddressingBundle\Behat\AddressingContext
- Sylius\Bundle\ShippingBundle\Behat\ShippingContext
- Sylius\Bundle\CoreBundle\Behat\CoreContext
- Sylius\Bundle\PaymentBundle\Behat\PaymentContext
- Sylius\Bundle\ProductBundle\Behat\ProductContext

View file

@ -9,6 +9,7 @@ Feature: Reports
And there are following reports configured:
| code | name | description | renderer | renderer_configuration | data_fetcher | data_fetcher_configuration |
| table_report | TableReport | Lorem ipsum | table | Template:SyliusReportBundle:Table:default.html.twig | user_registration | Period:day,Start:2010-01-01,End:2010-04-01 |
| sales_report | SalesReport | Lorem ipsum | table | Template:SyliusReportBundle:Table:default.html.twig | sales_total | Period:day,Start:2010-01-01,End:2010-01-05 |
| bar_chart_report | BarChartReport | Lorem ipsum | chart | Type:bar,Template:SyliusReportBundle:Chart:default.html.twig | user_registration | Period:month,Start:2010-01-01,End:2010-04-01 |
And there are following users:
| email | enabled | created at |
@ -20,7 +21,7 @@ Feature: Reports
Scenario: Seeing created reports it the list
Given I am on the dashboard page
When I follow "Reports"
Then I should see 2 reports in the list
Then I should see 3 reports in the list
Scenario: Adding new report with default options
Given I am on the report creation page
@ -140,3 +141,44 @@ Feature: Reports
And I press "Create"
Then I should still be on the report creation page
And I should see "Report code cannot be blank"
Scenario: Getting the correct sum with exchange rate
Given the following zones are defined:
| name | type | members |
| Scandinavia | country | Norway, Sweden, Finland |
| France | country | France |
And there are following shipping categories:
| code | name |
| SC1 | Regular |
| SC2 | Heavy |
And the following shipping methods exist:
| code | category | zone | name |
| SM1 | Regular | Scandinavia | DHL |
| SM2 | Heavy | France | UPS |
And the following products exist:
| name | price | sku |
| Mug | 10 | 456 |
| Book | 22 | 948 |
And the following orders exist:
| customer | shipment | address | currency | exchange_rate |
| sylius@example.com | UPS, shipped, DTBHH380HG | Théophile Morel, 17 avenue Jean Portalis, 33000, Bordeaux, France | EUR | 1.00000 |
| linustorvalds@linux.com | DHL, shipped, DTBHH380HH | Linus Torvalds, Väätäjänniementie 59, 00440, Helsinki, Finland | USD | 0.5 |
| sylius@example.com | UPS, shipped, DTBHH380HI | Théophile Morel, 17 avenue Jean Portalis, 33000, Bordeaux, France | GBP | 1.25 |
And order #000000001 has following items:
| product | quantity |
| Mug | 2 |
| Book | 1 |
And order #000000002 has following items:
| product | quantity |
| Mug | 2 |
| Book | 1 |
And order #000000003 has following items:
| product | quantity |
| Mug | 2 |
| Book | 1 |
And order #000000001 will be completed on "2010-01-01"
And order #000000002 will be completed on "2010-01-02"
And order #000000003 will be completed on "2010-01-02"
When I am on the page of report "SalesReport"
Then the report row for date "2010-01-01" will have a total amount of "42"
Then the report row for date "2010-01-02" will have a total amount of "73.50"

View file

@ -149,7 +149,16 @@ class CoreContext extends DefaultContext
$this->createPayment($order, $paymentMethod);
if (isset($data['currency']) && '' !== trim($data['currency'])) {
$order->setCurrency($data['currency']);
} else {
$order->setCurrency('EUR');
}
if (isset($data['exchange_rate']) && '' !== trim($data['exchange_rate'])) {
$order->setExchangeRate($data['exchange_rate']);
}
$order->setPaymentState(PaymentInterface::STATE_COMPLETED);
$order->complete();

View file

@ -21,6 +21,7 @@ use Sylius\Component\Order\OrderTransitions;
* Final checkout step.
*
* @author Paweł Jędrzejewski <pawel@sylius.org>
* @author Fernando Caraballo Ortiz <caraballo.ortiz@gmail.com>
*/
class FinalizeStep extends CheckoutStep
{
@ -64,10 +65,18 @@ class FinalizeStep extends CheckoutStep
protected function completeOrder(OrderInterface $order)
{
$this->get('session')->set('sylius_order_id', $order->getId());
$currencyProvider = $this->get('sylius.currency_provider');
$this->dispatchCheckoutEvent(SyliusOrderEvents::PRE_CREATE, $order);
$this->dispatchCheckoutEvent(SyliusCheckoutEvents::FINALIZE_PRE_COMPLETE, $order);
$this->get('sm.factory')->get($order, OrderTransitions::GRAPH)->apply(OrderTransitions::SYLIUS_CREATE, true);
if ($order->getCurrency() !== $currencyProvider->getBaseCurrency()) {
$currencyRepository = $this->get('sylius.repository.currency');
$currency = $currencyRepository->findOneBy(['code' => $order->getCurrency()]);
$order->setExchangeRate($currency->getExchangeRate());
}
$manager = $this->get('sylius.manager.order');
$manager->persist($order);

View file

@ -330,8 +330,9 @@ class OrderRepository extends CartRepository implements OrderRepositoryInterface
$configuration['end'],
$groupBy);
$baseCurrencyCode = $configuration['baseCurrency'] ? 'in ' . $configuration['baseCurrency']->getCode() : '';
$queryBuilder
->select('DATE(o.completed_at) as date', 'TRUNCATE(SUM(o.total)/ 100,2) as "total sum"')
->select('DATE(o.completed_at) as date', 'TRUNCATE(SUM(o.total * o.exchange_rate)/ 100,2) as "total sum ' . $baseCurrencyCode . '"')
;
return $queryBuilder

View file

@ -23,8 +23,14 @@ use Symfony\Component\EventDispatcher\GenericEvent;
*/
class OrderCurrencyListener
{
/**
* @var CurrencyContextInterface
*/
protected $currencyContext;
/**
* @param CurrencyContextInterface $currencyContext
*/
public function __construct(CurrencyContextInterface $currencyContext)
{
$this->currencyContext = $currencyContext;

View file

@ -11,6 +11,7 @@
<gedmo:versioned />
</field>
<field name="exchangeRate" column="exchange_rate" type="decimal" precision="10" scale="5" />
<field type="string" name="checkoutState" column="checkout_state" />
<field type="string" name="paymentState" column="payment_state" />
<field type="string" name="shippingState" column="shipping_state" />

View file

@ -468,6 +468,7 @@
<service id="sylius.currency_provider" class="%sylius.currency_provider.class%">
<argument type="service" id="sylius.context.channel" />
<argument type="service" id="sylius.repository.currency" />
</service>
<service id="sylius.channel_aware_locale_provider" class="%sylius.channel_aware_locale_provider.class%">

View file

@ -125,7 +125,7 @@ class SalesTotalDataFetcherSpec extends ObjectBehavior
$this->fetch($configuration)->shouldBeLike($data);
}
public function it_does_not_allowed_wrond_data_period($orderRepository, Data $data)
public function it_does_not_allowed_wrond_data_period()
{
$configuration = array(
'start' => new \DateTime('2010-01-01 00:00:00.000000'),

View file

@ -40,6 +40,12 @@ class LoadOrdersData extends DataFixture
'MOBILE',
);
$currencyExchangeRates = array(
'GBP' => 0.8,
'USD' => 1.2,
'EUR' => 1.0
);
for ($i = 1; $i <= 50; $i++) {
/* @var $order OrderInterface */
$order = $orderFactory->createNew();
@ -62,7 +68,8 @@ class LoadOrdersData extends DataFixture
$this->createShipment($order);
$order->setCurrency($this->faker->randomElement(array('EUR', 'USD', 'GBP')));
$order->setCurrency($this->faker->randomElement(array_keys($currencyExchangeRates)));
$order->setExchangeRate($currencyExchangeRates[$order->getCurrency()]);
$order->setShippingAddress($this->createAddress());
$order->setBillingAddress($this->createAddress());
$order->setCreatedAt($this->faker->dateTimeBetween('1 year ago', 'now'));

View file

@ -18,6 +18,7 @@ use Sylius\Bundle\ResourceBundle\Behat\DefaultContext;
* ReportContext for ReportBundle scenarios.
*
* @author Mateusz Zalewski <mateusz.zalewski@lakion.com>
* @author Fernando Caraballo Ortiz <caraballo.ortiz@gmail.com>
*/
class ReportContext extends DefaultContext
{
@ -96,4 +97,33 @@ class ReportContext extends DefaultContext
return $report;
}
/**
* @Then the report row for date :date will have a total amount of :total
*/
public function theReportRowForDateWillHaveATotalAmountOf($date, $total)
{
$page = $this->getSession()->getPage();
$rows = $page->findAll('css', 'table tbody tr');
foreach ($rows as $row) {
$columns = $row->findAll('css', 'td');
if ($columns[1]->getText() === $date) {
\PHPUnit_Framework_Assert::assertEquals($columns[2]->getText(), $total);
}
}
}
/**
* @Given order #:orderNumber will be completed on :date
*/
public function orderWillBeCompletedOn($orderNumber, $date)
{
$orderRepository = $this->getContainer()->get('sylius.repository.cart');
$orderManager = $this->getContainer()->get('sylius.manager.cart');
$order = $orderRepository->findOneBy(['number' => $orderNumber]);
$order->setCompletedAt(new \DateTime($date));
$orderManager->flush();
}
}

View file

@ -22,6 +22,7 @@ use Symfony\Component\HttpFoundation\Response;
/**
* @author Mateusz Zalewski <mateusz.zalewski@lakion.com>
* @author Łukasz Chruściel <lukasz.chrusciel@lakion.com>
* @author Fernando Caraballo Ortiz <caraballo.ortiz@gmail.com>
*/
class ReportController extends ResourceController
{
@ -63,6 +64,8 @@ class ReportController extends ResourceController
*/
public function embedAction(Request $request, $report, array $configuration = array())
{
$currencyProvider = $this->get('sylius.currency_provider');
if (!$report instanceof ReportInterface) {
$report = $this->getReportRepository()->findOneBy(array('code' => $report));
}
@ -72,6 +75,8 @@ class ReportController extends ResourceController
}
$configuration = $request->query->get('configuration', $configuration);
$configuration['baseCurrency'] = $currencyProvider->getBaseCurrency();
$data = $this->getReportDataFetcher()->fetch($report, $configuration);
return new Response($this->getReportRenderer()->render($report, $data));

View file

@ -13,27 +13,30 @@ namespace Sylius\Component\Core\Currency;
use Sylius\Component\Channel\Context\ChannelContextInterface;
use Sylius\Component\Currency\Model\CurrencyInterface;
use Sylius\Component\Currency\Provider\CurrencyProviderInterface;
use Sylius\Component\Currency\Provider\CurrencyProvider;
use Sylius\Component\Resource\Repository\RepositoryInterface;
/**
* Currency provider, which returns currencies enabled for this channel.
*
* @author Paweł Jędrzejewski <pawel@sylius.org>
* @author Fernando Caraballo Ortiz <caraballo.ortiz@gmail.com>
*/
class ChannelAwareCurrencyProvider implements CurrencyProviderInterface
class ChannelAwareCurrencyProvider extends CurrencyProvider
{
/**
* Channel context.
*
* @var ChannelContextInterface
*/
protected $channelContext;
/**
* @param ChannelContextInterface $channelContext
* @param RepositoryInterface $currencyRepository
*/
public function __construct(ChannelContextInterface $channelContext)
public function __construct(ChannelContextInterface $channelContext, RepositoryInterface $currencyRepository)
{
parent::__construct($currencyRepository);
$this->channelContext = $channelContext;
}

View file

@ -15,6 +15,7 @@ use Sylius\Component\Resource\Repository\RepositoryInterface;
/**
* @author Paweł Jędrzejewski <pawel@sylius.org>
* @author Fernando Caraballo Ortiz <caraballo.ortiz@gmail.com>
*/
class CurrencyProvider implements CurrencyProviderInterface
{
@ -38,4 +39,12 @@ class CurrencyProvider implements CurrencyProviderInterface
{
return $this->currencyRepository->findBy(array('enabled' => true));
}
/**
* {@inheritdoc}
*/
public function getBaseCurrency()
{
return $this->currencyRepository->findOneBy(['base' => true]);
}
}

View file

@ -15,6 +15,7 @@ use Sylius\Component\Currency\Model\CurrencyInterface;
/**
* @author Paweł Jędrzejewski <pawel@sylius.org>
* @author Fernando Caraballo Ortiz <caraballo.ortiz@gmail.com>
*/
interface CurrencyProviderInterface
{
@ -22,4 +23,9 @@ interface CurrencyProviderInterface
* @return CurrencyInterface[]
*/
public function getAvailableCurrencies();
/**
* @return CurrencyInterface
*/
public function getBaseCurrency();
}