Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
134 changes: 134 additions & 0 deletions code_samples/discounts/src/Command/OrderPriceCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
<?php declare(strict_types=1);

namespace App\Command;

use Exception;
use Ibexa\Contracts\Core\Repository\PermissionResolver;
use Ibexa\Contracts\Core\Repository\UserService;
use Ibexa\Contracts\OrderManagement\OrderServiceInterface;
use Ibexa\Contracts\ProductCatalog\CurrencyServiceInterface;
use Ibexa\Contracts\ProductCatalog\PriceResolverInterface;
use Ibexa\Contracts\ProductCatalog\ProductPriceServiceInterface;
use Ibexa\Contracts\ProductCatalog\ProductServiceInterface;
use Ibexa\Contracts\ProductCatalog\Values\Price\PriceContext;
use Ibexa\Contracts\ProductCatalog\Values\Price\PriceEnvelopeInterface;
use Ibexa\Discounts\Value\Price\Stamp\DiscountStamp;
use Ibexa\OrderManagement\Discounts\Value\DiscountsData;
use Money\Money;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

final class OrderPriceCommand extends Command
{
protected static $defaultName = 'app:discounts:prices';

private PermissionResolver $permissionResolver;

private UserService $userService;

private ProductServiceInterface $productService;

private OrderServiceInterface $orderService;

private ProductPriceServiceInterface $productPriceService;

private CurrencyServiceInterface $currencyService;

private PriceResolverInterface $priceResolver;

public function __construct(
PermissionResolver $permissionResolver,
UserService $userService,
ProductServiceInterface $productService,
OrderServiceInterface $orderService,
ProductPriceServiceInterface $productPriceService,
CurrencyServiceInterface $currencyService,
PriceResolverInterface $priceResolver
) {
parent::__construct();

$this->permissionResolver = $permissionResolver;
$this->userService = $userService;
$this->productService = $productService;
$this->orderService = $orderService;
$this->productPriceService = $productPriceService;
$this->currencyService = $currencyService;
$this->priceResolver = $priceResolver;
}

public function execute(InputInterface $input, OutputInterface $output): int
{
$this->permissionResolver->setCurrentUserReference($this->userService->loadUserByLogin('admin'));

$productCode = 'product_code_control_unit_0';
$orderIdentifier = '4315bc58-1e96-4f21-82a0-15f736cbc4bc';
$currencyCode = 'EUR';

$output->writeln('Product data:');
$product = $this->productService->getProduct($productCode);
$currency = $this->currencyService->getCurrencyByCode($currencyCode);

$basePrice = $this->productPriceService->getPriceByProductAndCurrency($product, $currency);
$resolvedPrice = $this->priceResolver->resolvePrice($product, new PriceContext($currency));

if ($resolvedPrice === null) {
throw new Exception('Could not resolve price for the product');
}

$output->writeln(sprintf('Base price: %s', $this->formatPrice($basePrice->getMoney())));
$output->writeln(sprintf('Discounted price: %s', $this->formatPrice($resolvedPrice->getMoney())));

if ($resolvedPrice instanceof PriceEnvelopeInterface) {
/** @var \Ibexa\Discounts\Value\Price\Stamp\DiscountStamp $discountStamp */
foreach ($resolvedPrice->all(DiscountStamp::class) as $discountStamp) {
$output->writeln(
sprintf(
'Discount applied: %s , new amount: %s',
$discountStamp->getDiscount()->getName(),
$this->formatPrice(
$discountStamp->getNewPrice()
)
)
);
}
}

$output->writeln('Order details:');

$order = $this->orderService->getOrderByIdentifier($orderIdentifier);
foreach ($order->getItems() as $item) {
/** @var ?DiscountsData $discountData */
$discountData = $item->getContext()['discount_data'] ?? null;
if ($discountData instanceof DiscountsData) {
$output->writeln(
sprintf(
'Product bought with discount: %s, base price: %s, discounted price: %s',
$item->getProduct()->getName(),
$this->formatPrice($discountData->getOriginalPrice()),
$this->formatPrice(
$item->getValue()->getUnitPriceGross()
)
)
);
} else {
$output->writeln(
sprintf(
'Product bought with original price: %s, price: %s',
$item->getProduct()->getName(),
$this->formatPrice(
$item->getValue()->getUnitPriceGross()
)
)
);
}
}

return Command::SUCCESS;
}

private function formatPrice(Money $money): string
{
return $money->getAmount() / 100.0 . ' ' . $money->getCurrency()->getCode();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php declare(strict_types=1);

namespace App\Discounts\Condition;

use Ibexa\Contracts\Discounts\Value\DiscountConditionInterface;
use Ibexa\Discounts\Value\AbstractDiscountExpressionAware;

final class IsAccountAnniversary extends AbstractDiscountExpressionAware implements DiscountConditionInterface
{
public const IDENTIFIER = 'is_account_anniversary';

public function __construct(?int $tolerance = null)
{
parent::__construct([
'tolerance' => $tolerance ?? 0,
]);
}

public function getTolerance(): int
{
return $this->getExpressionValue('tolerance');
}

public function getIdentifier(): string
{
return self::IDENTIFIER;
}

public function getExpression(): string
{
return 'is_anniversary(current_user_registration_date, tolerance)';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php declare(strict_types=1);

namespace App\Discounts\Condition;

use Ibexa\Contracts\Discounts\Value\DiscountConditionInterface;
use Ibexa\Discounts\Repository\DiscountCondition\DiscountConditionFactoryInterface;

final class IsAccountAnniversaryConditionFactory implements DiscountConditionFactoryInterface
{
public function createDiscountCondition(?array $expressionValues): DiscountConditionInterface
{
return new IsAccountAnniversary(
$expressionValues['tolerance'] ?? null
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php declare(strict_types=1);

namespace App\Discounts\ExpressionProvider;

use Ibexa\Contracts\Core\Repository\PermissionResolver;
use Ibexa\Contracts\Core\Repository\UserService;
use Ibexa\Contracts\Discounts\DiscountVariablesResolverInterface;
use Ibexa\Contracts\ProductCatalog\Values\Price\PriceContextInterface;

final class CurrentUserRegistrationDateResolver implements DiscountVariablesResolverInterface
{
private PermissionResolver $permissionResolver;

private UserService $userService;

public function __construct(PermissionResolver $permissionResolver, UserService $userService)
{
$this->permissionResolver = $permissionResolver;
$this->userService = $userService;
}

/** @return array{current_user_registration_date: \DateTimeInterface}
*/
public function getVariables(PriceContextInterface $priceContext): array
{
return [
'current_user_registration_date' => $this->userService->loadUser(
$this->permissionResolver->getCurrentUserReference()->getUserId()
)->getContentInfo()->publishedDate,
];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
<?php declare(strict_types=1);

namespace App\Discounts\ExpressionProvider;

use DateTimeImmutable;
use DateTimeInterface;
use EzSystems\EzPlatformGraphQL\GraphQL\Mutation\InputHandler\FieldType\Date;

final class IsAnniversaryResolver
{
private const YEAR_MONTH_DAY_FORMAT = 'Y-m-d';

private const MONTH_DAY_FORMAT = 'm-d';

private const REFERENCE_YEAR = 2000;

public function __invoke(DateTimeInterface $date, int $tolerance = 0): bool
{
$d1 = $this->unifyYear(new DateTimeImmutable());
$d2 = $this->unifyYear($date);

$diff = $d1->diff($d2, true)->days;

// Check if the difference between dates is within the tolerance
return $diff <= $tolerance;
}

private function unifyYear(DateTimeInterface $date): DateTimeImmutable
{
// Create a new date using the reference year but with the same month and day
$date = DateTimeImmutable::createFromFormat(
self::YEAR_MONTH_DAY_FORMAT,
self::REFERENCE_YEAR . '-' . $date->format(self::MONTH_DAY_FORMAT)
);

if ($date === false) {
throw new \RuntimeException('Failed to unify year for date.');
}

return $date;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php declare(strict_types=1);

namespace App\Discounts;

use Ibexa\Contracts\Discounts\DiscountPrioritizationStrategyInterface;
use Ibexa\Contracts\Discounts\Value\Query\SortClause\UpdatedAt;

final class RecentDiscountPrioritizationStrategy implements DiscountPrioritizationStrategyInterface
{
private DiscountPrioritizationStrategyInterface $inner;

public function __construct(DiscountPrioritizationStrategyInterface $inner)
{
$this->inner = $inner;
}

public function getOrder(): array
{
return array_merge(
[new UpdatedAt()],
$this->inner->getOrder()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php

declare(strict_types=1);

namespace App\Discounts\Rule;

use Ibexa\Contracts\Discounts\DiscountValueFormatterInterface;
use Ibexa\Contracts\Discounts\Value\DiscountRuleInterface;
use Money\Money;

final class PurchaseParityValueFormatter implements DiscountValueFormatterInterface
{
public function format(DiscountRuleInterface $discountRule, ?Money $money = null): string
{
return 'Regional discount';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<?php declare(strict_types=1);

namespace App\Discounts\Rule;

use Ibexa\Contracts\Discounts\Value\DiscountRuleInterface;
use Ibexa\Discounts\Value\AbstractDiscountExpressionAware;

final class PurchasingPowerParityRule extends AbstractDiscountExpressionAware implements DiscountRuleInterface
{
public const TYPE = 'purchasing_power_parity';

private const DEFAULT_PARITY_MAP = [
'default' => 100,
'germany' => 81.6,
'france' => 80,
'spain' => 69,
];

/** @param ?array<string, float> $powerParityMap */
public function __construct(?array $powerParityMap = null)
{
parent::__construct(
[
'power_parity_map' => $powerParityMap ?? self::DEFAULT_PARITY_MAP,
]
);
}

/** @return array<string, float> */
public function getMap(): array
{
return $this->getExpressionValue('power_parity_map');
}

public function getExpression(): string
{
return 'amount * (power_parity_map[get_current_region().getIdentifier()] / power_parity_map["default"])';
}

public function getType(): string
{
return self::TYPE;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php declare(strict_types=1);

namespace App\Discounts\Rule;

use Ibexa\Contracts\Discounts\Value\DiscountRuleInterface;
use Ibexa\Discounts\Repository\DiscountRule\DiscountRuleFactoryInterface;

final class PurchasingPowerParityRuleFactory implements DiscountRuleFactoryInterface
{
public function createDiscountRule(?array $expressionValues): DiscountRuleInterface
{
return new PurchasingPowerParityRule($expressionValues['power_parity_map'] ?? null);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?php declare(strict_types=1);

namespace App\Discounts\Step;

use Ibexa\Contracts\Discounts\Admin\Form\Data\AbstractDiscountStep;

final class AnniversaryConditionStep extends AbstractDiscountStep
{
public const IDENTIFIER = 'anniversary_condition_step';

public bool $enabled;

public int $tolerance;

public function __construct(bool $enabled = false, int $tolerance = 0)
{
$this->enabled = $enabled;
$this->tolerance = $tolerance;
}
}
Loading
Loading