Skip to content

Commit

Permalink
Add tax engine as service and allow new tax type by module (#3094)
Browse files Browse the repository at this point in the history
* Add tax engine as service and allow new tax type by module

* Change tax return types to float
  • Loading branch information
lopes-vincent committed Mar 20, 2023
1 parent f222ef1 commit 35c705a
Show file tree
Hide file tree
Showing 12 changed files with 137 additions and 79 deletions.
19 changes: 19 additions & 0 deletions core/lib/Thelia/Action/Tax.php
Expand Up @@ -12,6 +12,8 @@

namespace Thelia\Action;

use Symfony\Component\DependencyInjection\Attribute\TaggedLocator;
use Symfony\Component\DependencyInjection\ServiceLocator;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Thelia\Core\Event\Tax\TaxEvent;
Expand All @@ -21,6 +23,14 @@

class Tax extends BaseAction implements EventSubscriberInterface
{
private ServiceLocator $taxTypeLocator;

public function __construct(
#[TaggedLocator('thelia.taxType')] ServiceLocator $taxTypeLocator
) {
$this->taxTypeLocator = $taxTypeLocator;
}

public function create(TaxEvent $event, $eventName, EventDispatcherInterface $dispatcher): void
{
$tax = new TaxModel();
Expand Down Expand Up @@ -68,6 +78,14 @@ public function delete(TaxEvent $event): void
}
}

public function getTaxTypeService(TaxEvent $event): void
{
$tax = $event->getTax();
if ($this->taxTypeLocator->has($tax->getType())) {
$event->setTaxTypeService($this->taxTypeLocator->get($tax->getType()));
}
}

/**
* {@inheritDoc}
*/
Expand All @@ -77,6 +95,7 @@ public static function getSubscribedEvents()
TheliaEvents::TAX_CREATE => ['create', 128],
TheliaEvents::TAX_UPDATE => ['update', 128],
TheliaEvents::TAX_DELETE => ['delete', 128],
TheliaEvents::TAX_GET_TYPE_SERVICE => ['getTaxTypeService', 128],
];
}
}
15 changes: 15 additions & 0 deletions core/lib/Thelia/Core/Event/Tax/TaxEvent.php
Expand Up @@ -14,6 +14,7 @@

use Thelia\Core\Event\ActionEvent;
use Thelia\Model\Tax;
use Thelia\TaxEngine\TaxTypeInterface;

class TaxEvent extends ActionEvent
{
Expand All @@ -26,6 +27,8 @@ class TaxEvent extends ActionEvent
protected $type;
protected $requirements;

protected ?TaxTypeInterface $taxTypeService = null;

public function __construct(Tax $tax = null)
{
$this->tax = $tax;
Expand Down Expand Up @@ -119,4 +122,16 @@ public function getRequirements()
{
return $this->requirements;
}

public function getTaxTypeService(): ?TaxTypeInterface
{
return $this->taxTypeService;
}

public function setTaxTypeService(TaxTypeInterface $taxTypeService)
{
$this->taxTypeService = $taxTypeService;

return $this;
}
}
1 change: 1 addition & 0 deletions core/lib/Thelia/Core/Event/TheliaEvents.php
Expand Up @@ -458,6 +458,7 @@ public static function getLoopExtendsEvent(string $eventName, string $loopName =
public const TAX_CREATE = 'action.createTax';
public const TAX_UPDATE = 'action.updateTax';
public const TAX_DELETE = 'action.deleteTax';
public const TAX_GET_TYPE_SERVICE = 'action.getTaxService';

// -- Profile management ---------------------------------------------

Expand Down
2 changes: 2 additions & 0 deletions core/lib/Thelia/Core/Thelia.php
Expand Up @@ -65,6 +65,7 @@
use Thelia\Model\Module;
use Thelia\Model\ModuleQuery;
use Thelia\Module\ModuleManagement;
use Thelia\TaxEngine\TaxTypeInterface;
use TheliaSmarty\Template\SmartyParser;

class Thelia extends Kernel
Expand Down Expand Up @@ -459,6 +460,7 @@ protected function loadConfiguration(ContainerBuilder $container): void
CouponInterface::class => 'thelia.coupon.addCoupon',
ConditionInterface::class => 'thelia.coupon.addCondition',
ControllerInterface::class => 'controller.service_arguments',
TaxTypeInterface::class => 'thelia.taxType',
];

foreach ($autoconfiguredInterfaces as $interfaceClass => $tag) {
Expand Down
21 changes: 12 additions & 9 deletions core/lib/Thelia/Form/TaxCreationForm.php
Expand Up @@ -12,14 +12,14 @@

namespace Thelia\Form;

use Symfony\Component\DependencyInjection\Attribute\TaggedIterator;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\HiddenType;
use Symfony\Component\Validator\Constraints;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Context\ExecutionContextInterface;
use Thelia\Core\Translation\Translator;
use Thelia\Model\Tax;
use Thelia\TaxEngine\TaxEngine;
use Thelia\TaxEngine\TaxTypeRequirementDefinition;
use Thelia\Type\TypeInterface;

Expand All @@ -32,21 +32,24 @@ class TaxCreationForm extends BaseForm
{
use StandardDescriptionFieldsTrait;

private iterable $taxTypeIterator;

public function __construct(
#[TaggedIterator('thelia.taxType')] iterable $taxTypeIterator
) {
$this->taxTypeIterator = $taxTypeIterator;
}

protected static $typeList = [];

protected function buildForm(): void
{
$types = TaxEngine::getTaxTypeList();

$typeList = [];
$requirementList = [];

foreach ($types as $classname) {
$instance = new $classname();

$typeList[$instance->getTitle()] = Tax::escapeTypeName($classname);

$requirementList[$classname] = $instance->getRequirementsDefinition();
foreach ($this->taxTypeIterator as $taxType) {
$typeList[$taxType->getTitle()] = Tax::escapeTypeName($taxType::class);
$requirementList[$taxType::class] = $taxType->getRequirementsDefinition();
}

$this->formBuilder
Expand Down
24 changes: 12 additions & 12 deletions core/lib/Thelia/Model/Tax.php
Expand Up @@ -13,9 +13,12 @@
namespace Thelia\Model;

use Propel\Runtime\Exception\PropelException;
use Propel\Runtime\Propel;
use Thelia\Core\Event\Tax\TaxEvent;
use Thelia\Core\Event\TheliaEvents;
use Thelia\Exception\TaxEngineException;
use Thelia\Model\Base\Tax as BaseTax;
use Thelia\TaxEngine\BaseTaxType;
use Thelia\Model\Map\TaxTableMap;

class Tax extends BaseTax
{
Expand Down Expand Up @@ -45,22 +48,19 @@ public function getTaxRuleCountryPosition()

public function getTypeInstance()
{
$class = $this->getType();
$eventDispatcher = Propel::getServiceContainer()->getWriteConnection(TaxTableMap::DATABASE_NAME)->getEventDispatcher();
$taxEvent = new TaxEvent($this);
$eventDispatcher->dispatch($taxEvent, TheliaEvents::TAX_GET_TYPE_SERVICE);

/* test type */
if (!class_exists($class)) {
throw new TaxEngineException('Recorded type `'.$class.'` does not exists', TaxEngineException::BAD_RECORDED_TYPE);
}
/** @var \Thelia\TaxEngine\BaseTaxType $instance */
$instance = new $class();
$typeService = $taxEvent->getTaxTypeService();

if (!$instance instanceof BaseTaxType) {
throw new TaxEngineException('Recorded type `'.$class.'` does not extends BaseTaxType', TaxEngineException::BAD_RECORDED_TYPE);
if (!$typeService) {
throw new TaxEngineException('Recorded type `'.$this->getType().'` does not exists', TaxEngineException::BAD_RECORDED_TYPE);
}

$instance->loadRequirements($this->getRequirements());
$typeService->loadRequirements($this->getRequirements());

return $instance;
return $typeService;
}

public function setRequirements($requirements)
Expand Down
25 changes: 7 additions & 18 deletions core/lib/Thelia/TaxEngine/BaseTaxType.php
Expand Up @@ -18,7 +18,7 @@
/**
* @author Etienne Roudeix <eroudeix@openstudio.fr>
*/
abstract class BaseTaxType
abstract class BaseTaxType implements TaxTypeInterface
{
/**
* A var <-> value array which contains TaxtType requirements (e.g. parameters).
Expand All @@ -32,10 +32,8 @@ abstract class BaseTaxType
* to use in tax calculation.
*
* For other tax types, this method shoud return 0.
*
* @return number
*/
public function pricePercentRetriever()
public function pricePercentRetriever(): float
{
return 0;
}
Expand All @@ -44,10 +42,8 @@ public function pricePercentRetriever()
* For constant amount tax type, return the absolute amount to use in tax calculation.
*
* For other tax types, this method shoud return 0.
*
* @return number
*/
public function fixAmountRetriever(Product $product)
public function fixAmountRetriever(Product $product): float
{
return 0;
}
Expand All @@ -60,30 +56,23 @@ public function fixAmountRetriever(Product $product)
* array(
* 'percent' => new FloatType()
* );
*
* @return array of TaxTypeRequirementDefinition
*/
public function getRequirementsDefinition()
public function getRequirementsDefinition(): array
{
return [];
}

/**
* @return the name of this tax type
*/
abstract public function getTitle();

public function calculate(Product $product, $untaxedPrice)
public function calculate(Product $product, $untaxedPrice): float
{
return $untaxedPrice * $this->pricePercentRetriever() + $this->fixAmountRetriever($product);
}

/**
* @throws TaxEngineException
*
* @return array return the requirements array
*/
public function getRequirements()
public function getRequirements(): array
{
return $this->requirements;
}
Expand Down Expand Up @@ -111,7 +100,7 @@ public function loadRequirements($requirementsValues): void
}
}

public function setRequirement($key, $value)
public function setRequirement($key, $value): self
{
$this->requirements[$key] = $value;

Expand Down
37 changes: 3 additions & 34 deletions core/lib/Thelia/TaxEngine/TaxEngine.php
Expand Up @@ -28,47 +28,16 @@ class TaxEngine
{
protected $taxCountry;
protected $taxState;
protected $typeList;

/** @var RequestStack */
protected $requestStack;

public function __construct(RequestStack $requestStack)
{
public function __construct(
RequestStack $requestStack,
) {
$this->requestStack = $requestStack;
}

public static function getTaxTypeList()
{
$taxTypeDirectory = __DIR__.DS.'TaxType';
$typeList = [];
try {
$directoryIterator = new \DirectoryIterator($taxTypeDirectory);

foreach ($directoryIterator as $fileinfo) {
if ($fileinfo->isFile()) {
$extension = $fileinfo->getExtension();
if (strtolower($extension) !== 'php') {
continue;
}
$className = $fileinfo->getBaseName('.php');

try {
$fullyQualifiedClassName = 'Thelia\\TaxEngine\\TaxType\\'.$className;
$instance = new $fullyQualifiedClassName();
$typeList[] = \get_class($instance);
} catch (\Exception $ex) {
// Nothing special to do
}
}
}
} catch (\UnexpectedValueException $e) {
// Nothing special to do
}

return $typeList;
}

/**
* Find Tax Country Id
* First look for a picked delivery address country
Expand Down
4 changes: 2 additions & 2 deletions core/lib/Thelia/TaxEngine/TaxType/FeatureFixAmountTaxType.php
Expand Up @@ -34,7 +34,7 @@ public function setFeature($featureId)
return $this;
}

public function fixAmountRetriever(Product $product)
public function fixAmountRetriever(Product $product): float
{
$taxAmount = 0;
$featureId = $this->getRequirement('feature');
Expand Down Expand Up @@ -64,7 +64,7 @@ public function fixAmountRetriever(Product $product)
return $taxAmount;
}

public function getRequirementsDefinition()
public function getRequirementsDefinition(): array
{
return [
new TaxTypeRequirementDefinition(
Expand Down
5 changes: 3 additions & 2 deletions core/lib/Thelia/TaxEngine/TaxType/FixAmountTaxType.php
Expand Up @@ -13,6 +13,7 @@
namespace Thelia\TaxEngine\TaxType;

use Thelia\Core\Translation\Translator;
use Thelia\Model\Product;
use Thelia\TaxEngine\BaseTaxType;
use Thelia\TaxEngine\TaxTypeRequirementDefinition;
use Thelia\Type\FloatType;
Expand All @@ -29,12 +30,12 @@ public function setAmount($amount)
return $this;
}

public function fixAmountRetriever(\Thelia\Model\Product $product)
public function fixAmountRetriever(Product $product): float
{
return $this->getRequirement('amount');
}

public function getRequirementsDefinition()
public function getRequirementsDefinition(): array
{
return [
new TaxTypeRequirementDefinition(
Expand Down
4 changes: 2 additions & 2 deletions core/lib/Thelia/TaxEngine/TaxType/PricePercentTaxType.php
Expand Up @@ -29,12 +29,12 @@ public function setPercentage($percent)
return $this;
}

public function pricePercentRetriever()
public function pricePercentRetriever(): float
{
return $this->getRequirement('percent') * 0.01;
}

public function getRequirementsDefinition()
public function getRequirementsDefinition(): array
{
return [
new TaxTypeRequirementDefinition(
Expand Down

0 comments on commit 35c705a

Please sign in to comment.