Rajout du dossier core + MAJ .gitignore

This commit is contained in:
2019-11-21 12:48:42 +01:00
parent f4aabcb9b1
commit 459f8966b0
10448 changed files with 1835600 additions and 1 deletions

View File

@@ -0,0 +1,130 @@
<?php
/*************************************************************************************/
/* This file is part of the Thelia package. */
/* */
/* Copyright (c) OpenStudio */
/* email : dev@thelia.net */
/* web : http://www.thelia.net */
/* */
/* For the full copyright and license information, please view the LICENSE.txt */
/* file that was distributed with this source code. */
/*************************************************************************************/
namespace Thelia\TaxEngine;
use Thelia\Exception\TaxEngineException;
use Thelia\Model\Product;
/**
*
* @author Etienne Roudeix <eroudeix@openstudio.fr>
*
*/
abstract class BaseTaxType
{
/**
* A var <-> value array which contains TaxtType requirements (e.g. parameters)
*
* @var array
*/
protected $requirements = array();
/**
* For a price percent tax type, return the percentage (e.g. 20 for 20%) of the product price
* to use in tax calculation.
*
* For other tax types, this method shoud return 0.
*
* @return number
*/
public function pricePercentRetriever()
{
return 0;
}
/**
* 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)
{
return 0;
}
/**
* Returns the requirements definition of this tax type. This is an array of
* TaxTypeRequirementDefinition, which defines the name and the type of
* the requirements. Example :
*
* array(
* 'percent' => new FloatType()
* );
*
* @return array of TaxTypeRequirementDefinition
*/
public function getRequirementsDefinition()
{
return array();
}
/**
* @return the name of this tax type.
*/
abstract public function getTitle();
public function calculate(Product $product, $untaxedPrice)
{
return $untaxedPrice * $this->pricePercentRetriever() + $this->fixAmountRetriever($product);
}
/**
* @throws TaxEngineException
* @return array Return the requirements array.
*/
public function getRequirements()
{
return $this->requirements;
}
public function loadRequirements($requirementsValues)
{
$requirements = $this->getRequirementsDefinition();
if (!is_array($requirements)) {
throw new TaxEngineException('getRequirementsDefinition must return an array', TaxEngineException::TAX_TYPE_BAD_ABSTRACT_METHOD);
}
foreach ($requirements as $requirement) {
$requirementName = $requirement->getName();
if (! array_key_exists($requirementName, $requirementsValues)) {
throw new TaxEngineException('Cannot load requirements : requirement value for `' . $requirementName . '` not found', TaxEngineException::TAX_TYPE_REQUIREMENT_NOT_FOUND);
}
if (! $requirement->isValueValid($requirementsValues[$requirementName])) {
throw new TaxEngineException('Requirement value for `' . $requirementName . '` does not match required type', TaxEngineException::TAX_TYPE_BAD_REQUIREMENT_VALUE);
}
$this->requirements[$requirementName] = $requirementsValues[$requirementName];
}
}
public function setRequirement($key, $value)
{
$this->requirements[$key] = $value;
return $this;
}
public function getRequirement($key)
{
if (!array_key_exists($key, $this->requirements)) {
throw new TaxEngineException('Requirement value for `' . $key . '` does not exists in BaseTaxType::$requirements', TaxEngineException::UNDEFINED_REQUIREMENT_VALUE);
}
return $this->requirements[$key];
}
}

View File

@@ -0,0 +1,237 @@
<?php
/*************************************************************************************/
/* This file is part of the Thelia package. */
/* */
/* Copyright (c) OpenStudio */
/* email : dev@thelia.net */
/* web : http://www.thelia.net */
/* */
/* For the full copyright and license information, please view the LICENSE.txt */
/* file that was distributed with this source code. */
/*************************************************************************************/
namespace Thelia\TaxEngine;
use Thelia\Exception\TaxEngineException;
use Thelia\Model\Country;
use Thelia\Model\OrderProductTax;
use Thelia\Model\Product;
use Thelia\Model\State;
use Thelia\Model\TaxRule;
use Thelia\Model\TaxRuleQuery;
use Thelia\Tools\I18n;
/**
* Class Calculator
* @package Thelia\TaxEngine
* @author Etienne Roudeix <eroudeix@openstudio.fr>
*/
class Calculator
{
/**
* @var TaxRuleQuery
*/
protected $taxRuleQuery = null;
/**
* @var null|\Propel\Runtime\Collection\ObjectCollection
*/
protected $taxRulesCollection = null;
protected $product = null;
protected $country = null;
protected $state = null;
public function __construct()
{
$this->taxRuleQuery = new TaxRuleQuery();
}
public function load(Product $product, Country $country, State $state = null)
{
$this->product = null;
$this->country = null;
$this->state = null;
$this->taxRulesCollection = null;
if ($product->getId() === null) {
throw new TaxEngineException('Product id is empty in Calculator::load', TaxEngineException::UNDEFINED_PRODUCT);
}
if ($country->getId() === null) {
throw new TaxEngineException('Country id is empty in Calculator::load', TaxEngineException::UNDEFINED_COUNTRY);
}
$this->product = $product;
$this->country = $country;
$this->state = $state;
$this->taxRulesCollection = $this->taxRuleQuery->getTaxCalculatorCollection($product->getTaxRule(), $country, $state);
return $this;
}
public function loadTaxRule(TaxRule $taxRule, Country $country, Product $product, State $state = null)
{
$this->product = null;
$this->country = null;
$this->taxRulesCollection = null;
if ($taxRule->getId() === null) {
throw new TaxEngineException('TaxRule id is empty in Calculator::loadTaxRule', TaxEngineException::UNDEFINED_TAX_RULE);
}
if ($country->getId() === null) {
throw new TaxEngineException('Country id is empty in Calculator::loadTaxRule', TaxEngineException::UNDEFINED_COUNTRY);
}
if ($product->getId() === null) {
throw new TaxEngineException('Product id is empty in Calculator::load', TaxEngineException::UNDEFINED_PRODUCT);
}
$this->country = $country;
$this->product = $product;
$this->state = $state;
$this->taxRulesCollection = $this->taxRuleQuery->getTaxCalculatorCollection($taxRule, $country, $state);
return $this;
}
public function loadTaxRuleWithoutProduct(TaxRule $taxRule, Country $country, State $state = null)
{
$this->product = null;
$this->country = null;
$this->taxRulesCollection = null;
if ($taxRule->getId() === null) {
throw new TaxEngineException('TaxRule id is empty in Calculator::loadTaxRule', TaxEngineException::UNDEFINED_TAX_RULE);
}
if ($country->getId() === null) {
throw new TaxEngineException('Country id is empty in Calculator::loadTaxRule', TaxEngineException::UNDEFINED_COUNTRY);
}
$this->country = $country;
$this->product = new Product();
$this->state = $state;
$this->taxRulesCollection = $this->taxRuleQuery->getTaxCalculatorCollection($taxRule, $country, $state);
return $this;
}
public function getTaxAmountFromUntaxedPrice($untaxedPrice, &$taxCollection = null)
{
return $this->getTaxedPrice($untaxedPrice, $taxCollection) - $untaxedPrice;
}
public function getTaxAmountFromTaxedPrice($taxedPrice)
{
return $taxedPrice - $this->getUntaxedPrice($taxedPrice);
}
/**
* @param $untaxedPrice
* @param null $taxCollection returns OrderProductTaxCollection
* @param null $askedLocale
*
* @return int
* @throws \Thelia\Exception\TaxEngineException
*/
public function getTaxedPrice($untaxedPrice, &$taxCollection = null, $askedLocale = null)
{
if (null === $this->taxRulesCollection) {
throw new TaxEngineException('Tax rules collection is empty in Calculator::getTaxedPrice', TaxEngineException::UNDEFINED_TAX_RULES_COLLECTION);
}
if (null === $this->product) {
throw new TaxEngineException('Product is empty in Calculator::getTaxedPrice', TaxEngineException::UNDEFINED_PRODUCT);
}
if (false === filter_var($untaxedPrice, FILTER_VALIDATE_FLOAT)) {
throw new TaxEngineException('BAD AMOUNT FORMAT', TaxEngineException::BAD_AMOUNT_FORMAT);
}
$taxedPrice = $untaxedPrice;
$currentPosition = 1;
$currentTax = 0;
if (null !== $taxCollection) {
$taxCollection = new OrderProductTaxCollection();
}
foreach ($this->taxRulesCollection as $taxRule) {
$position = (int) $taxRule->getTaxRuleCountryPosition();
$taxType = $taxRule->getTypeInstance();
if ($currentPosition !== $position) {
$taxedPrice += $currentTax;
$currentTax = 0;
$currentPosition = $position;
}
$taxAmount = $taxType->calculate($this->product, $taxedPrice);
$currentTax += $taxAmount;
if (null !== $taxCollection) {
$taxI18n = I18n::forceI18nRetrieving($askedLocale, 'Tax', $taxRule->getId());
$orderProductTax = new OrderProductTax();
$orderProductTax->setTitle($taxI18n->getTitle());
$orderProductTax->setDescription($taxI18n->getDescription());
$orderProductTax->setAmount($taxAmount);
$taxCollection->addTax($orderProductTax);
}
}
$taxedPrice += $currentTax;
return $taxedPrice;
}
public function getUntaxedPrice($taxedPrice)
{
if (null === $this->taxRulesCollection) {
throw new TaxEngineException('Tax rules collection is empty in Calculator::getTaxAmount', TaxEngineException::UNDEFINED_TAX_RULES_COLLECTION);
}
if (null === $this->product) {
throw new TaxEngineException('Product is empty in Calculator::getTaxedPrice', TaxEngineException::UNDEFINED_PRODUCT);
}
if (false === filter_var($taxedPrice, FILTER_VALIDATE_FLOAT)) {
throw new TaxEngineException('BAD AMOUNT FORMAT', TaxEngineException::BAD_AMOUNT_FORMAT);
}
$taxRule = $this->taxRulesCollection->getLast();
if (null === $taxRule) {
throw new TaxEngineException('Tax rules collection got no tax ', TaxEngineException::NO_TAX_IN_TAX_RULES_COLLECTION);
}
$untaxedPrice = $taxedPrice;
$currentPosition = (int) $taxRule->getTaxRuleCountryPosition();
$currentFixTax = 0;
$currentTaxFactor = 0;
do {
$position = (int) $taxRule->getTaxRuleCountryPosition();
$taxType = $taxRule->getTypeInstance();
if ($currentPosition !== $position) {
$untaxedPrice -= $currentFixTax;
$untaxedPrice = $untaxedPrice / (1+$currentTaxFactor);
$currentFixTax = 0;
$currentTaxFactor = 0;
$currentPosition = $position;
}
$currentFixTax += $taxType->fixAmountRetriever($this->product);
$currentTaxFactor += $taxType->pricePercentRetriever();
} while ($taxRule = $this->taxRulesCollection->getPrevious());
$untaxedPrice -= $currentFixTax;
$untaxedPrice = $untaxedPrice / (1+$currentTaxFactor);
return $untaxedPrice;
}
}

View File

@@ -0,0 +1,116 @@
<?php
/*************************************************************************************/
/* This file is part of the Thelia package. */
/* */
/* Copyright (c) OpenStudio */
/* email : dev@thelia.net */
/* web : http://www.thelia.net */
/* */
/* For the full copyright and license information, please view the LICENSE.txt */
/* file that was distributed with this source code. */
/*************************************************************************************/
namespace Thelia\TaxEngine;
use Thelia\Model\OrderProductTax;
/**
*
* @author Etienne Roudeix <eroudeix@openstudio.fr>
*
*/
class OrderProductTaxCollection implements \Iterator
{
private $position;
protected $taxes = array();
public function __construct()
{
foreach (func_get_args() as $tax) {
$this->addTax($tax);
}
}
public function isEmpty()
{
return count($this->taxes) == 0;
}
/**
* @param OrderProductTax $tax
*
* @return OrderProductTaxCollection
*/
public function addTax(OrderProductTax $tax)
{
$this->taxes[] = $tax;
return $this;
}
public function getCount()
{
return count($this->taxes);
}
/**
* (PHP 5 &gt;= 5.0.0)<br/>
* Return the current element
* @link http://php.net/manual/en/iterator.current.php
* @return OrderProductTax
*/
public function current()
{
return $this->taxes[$this->position];
}
/**
* (PHP 5 &gt;= 5.0.0)<br/>
* Move forward to next element
* @link http://php.net/manual/en/iterator.next.php
* @return void Any returned value is ignored.
*/
public function next()
{
$this->position++;
}
/**
* (PHP 5 &gt;= 5.0.0)<br/>
* Return the key of the current element
* @link http://php.net/manual/en/iterator.key.php
* @return mixed scalar on success, or null on failure.
*/
public function key()
{
return $this->position;
}
/**
* (PHP 5 &gt;= 5.0.0)<br/>
* Checks if current position is valid
* @link http://php.net/manual/en/iterator.valid.php
* @return boolean The return value will be casted to boolean and then evaluated.
* Returns true on success or false on failure.
*/
public function valid()
{
return isset($this->taxes[$this->position]);
}
/**
* (PHP 5 &gt;= 5.0.0)<br/>
* Rewind the Iterator to the first element
* @link http://php.net/manual/en/iterator.rewind.php
* @return void Any returned value is ignored.
*/
public function rewind()
{
$this->position = 0;
}
public function getKey($key)
{
return isset($this->taxes[$key]) ? $this->taxes[$key] : null;
}
}

View File

@@ -0,0 +1,171 @@
<?php
/*************************************************************************************/
/* This file is part of the Thelia package. */
/* */
/* Copyright (c) OpenStudio */
/* email : dev@thelia.net */
/* web : http://www.thelia.net */
/* */
/* For the full copyright and license information, please view the LICENSE.txt */
/* file that was distributed with this source code. */
/*************************************************************************************/
namespace Thelia\TaxEngine;
use Symfony\Component\HttpFoundation\RequestStack;
use Thelia\Core\HttpFoundation\Session\Session;
use Thelia\Model\AddressQuery;
use Thelia\Model\Country;
use Thelia\Model\CountryQuery;
use Thelia\Model\Customer;
use Thelia\Model\State;
/**
* Class TaxEngine
*
* @package Thelia\TaxEngine
* @author Etienne Roudeix <eroudeix@openstudio.fr>
*/
class TaxEngine
{
protected $taxCountry = null;
protected $taxState = null;
protected $typeList = null;
protected $taxTypesDirectories = array();
/** @var RequestStack */
protected $requestStack;
public function __construct(RequestStack $requestStack)
{
$this->requestStack = $requestStack;
// Intialize the defaults Tax Types
$this->taxTypesDirectories['Thelia\\TaxEngine\\TaxType'] = __DIR__ . DS . "TaxType";
}
/**
* Add a directroy which contains tax types classes. The tax engine
* will scan this directory, and add all the tax type classes.
*
* @param string $namespace the namespace of the classes in the directory
* @param string $path_to_tax_type_classes the path to the directory
*/
public function addTaxTypeDirectory($namespace, $path_to_tax_type_classes)
{
$this->taxTypesDirectories[$namespace] = $path_to_tax_type_classes;
}
/**
* Add a tax type to the current list.
*
* @param unknown $fullyQualifiedclassName the fully qualified classname, su chas MyTaxes\Taxes\MyTaxType
*
*/
public function addTaxType($fullyQualifiedclassName)
{
$this->typeList[] = $fullyQualifiedclassName;
}
public function getTaxTypeList()
{
if ($this->typeList === null) {
$this->typeList = array();
foreach ($this->taxTypesDirectories as $namespace => $directory) {
try {
$directoryIterator = new \DirectoryIterator($directory);
foreach ($directoryIterator as $fileinfo) {
if ($fileinfo->isFile()) {
$extension = $fileinfo->getExtension();
if (strtolower($extension) !== 'php')
continue;
$className = $fileinfo->getBaseName('.php');
try {
$fullyQualifiedClassName = "$namespace\\$className";
$instance = new $fullyQualifiedClassName;
if ($instance instanceof BaseTaxType) {
$this->addTaxType(get_class($instance));
}
} catch (\Exception $ex) {
// Nothing special to do
}
}
}
} catch (\UnexpectedValueException $e) {
// Nothing special to do
}
}
}
return $this->typeList;
}
/**
* Find Tax Country Id
* First look for a picked delivery address country
* Then look at the current customer default address country
* Else look at the default website country
* @return null|Country
*/
public function getDeliveryCountry()
{
if (null === $this->taxCountry) {
/* is there a logged in customer ? */
/** @var Customer $customer */
if (null !== $customer = $this->getSession()->getCustomerUser()) {
if (null !== $this->getSession()->getOrder()
&& null !== $this->getSession()->getOrder()->getChoosenDeliveryAddress()
&& null !== $currentDeliveryAddress = AddressQuery::create()->findPk($this->getSession()->getOrder()->getChoosenDeliveryAddress())) {
$this->taxCountry = $currentDeliveryAddress->getCountry();
$this->taxState = $currentDeliveryAddress->getState();
} else {
$customerDefaultAddress = $customer->getDefaultAddress();
$this->taxCountry = $customerDefaultAddress->getCountry();
$this->taxState = $customerDefaultAddress->getState();
}
}
if (null == $this->taxCountry) {
$this->taxCountry = CountryQuery::create()->findOneByByDefault(1);
$this->taxState = null;
}
}
return $this->taxCountry;
}
/**
* Find Tax State Id
*
* First look for a picked delivery address state
* Then look at the current customer default address state
* Else null
* @return null|State
* @since 2.3.0-alpha1
*/
public function getDeliveryState()
{
if (null === $this->taxCountry) {
/* is there a logged in customer ? */
$this->getDeliveryCountry();
}
return $this->taxState;
}
/**
* @return Session
*/
protected function getSession()
{
return $this->requestStack->getCurrentRequest()->getSession();
}
}

View File

@@ -0,0 +1,78 @@
<?php
/*************************************************************************************/
/* This file is part of the Thelia package. */
/* */
/* Copyright (c) OpenStudio */
/* email : dev@thelia.net */
/* web : http://www.thelia.net */
/* */
/* For the full copyright and license information, please view the LICENSE.txt */
/* file that was distributed with this source code. */
/*************************************************************************************/
namespace Thelia\TaxEngine\TaxType;
use Thelia\Exception\TaxEngineException;
use Thelia\Model\FeatureProductQuery;
use Thelia\Model\Product;
use Thelia\Type\FloatType;
use Thelia\Type\ModelValidIdType;
use Thelia\Core\Translation\Translator;
use Thelia\TaxEngine\BaseTaxType;
use Thelia\TaxEngine\TaxTypeRequirementDefinition;
/**
*
* @author Etienne Roudeix <eroudeix@openstudio.fr>
*
*/
class FeatureFixAmountTaxType extends BaseTaxType
{
public function setFeature($featureId)
{
$this->setRequirement('feature', $featureId);
return $this;
}
public function fixAmountRetriever(Product $product)
{
$taxAmount = 0;
$featureId = $this->getRequirement("feature");
$query = FeatureProductQuery::create()
->filterByProduct($product)
->filterByFeatureId($featureId)
->findOne();
if (null !== $query) {
$taxAmount = $query->getFreeTextValue();
$testInt = new FloatType();
if (!$testInt->isValid($taxAmount)) {
throw new TaxEngineException(
Translator::getInstance()->trans('Feature value does not match FLOAT format'),
TaxEngineException::FEATURE_BAD_EXPECTED_VALUE
);
}
}
return $taxAmount;
}
public function getRequirementsDefinition()
{
return array(
new TaxTypeRequirementDefinition(
'feature',
new ModelValidIdType('Feature'),
Translator::getInstance()->trans("Feature")
)
);
}
public function getTitle()
{
return Translator::getInstance()->trans("Constant amount found in one of the product's feature");
}
}

View File

@@ -0,0 +1,54 @@
<?php
/*************************************************************************************/
/* This file is part of the Thelia package. */
/* */
/* Copyright (c) OpenStudio */
/* email : dev@thelia.net */
/* web : http://www.thelia.net */
/* */
/* For the full copyright and license information, please view the LICENSE.txt */
/* file that was distributed with this source code. */
/*************************************************************************************/
namespace Thelia\TaxEngine\TaxType;
use Thelia\Type\FloatType;
use Thelia\Core\Translation\Translator;
use Thelia\TaxEngine\BaseTaxType;
use Thelia\TaxEngine\TaxTypeRequirementDefinition;
/**
*
* @author Etienne Roudeix <eroudeix@openstudio.fr>
*
*/
class FixAmountTaxType extends BaseTaxType
{
public function setAmount($amount)
{
$this->setRequirement('amount', $amount);
return $this;
}
public function fixAmountRetriever(\Thelia\Model\Product $product)
{
return $this->getRequirement("amount");
}
public function getRequirementsDefinition()
{
return array(
new TaxTypeRequirementDefinition(
'amount',
new FloatType(),
Translator::getInstance()->trans("Amount")
)
);
}
public function getTitle()
{
return Translator::getInstance()->trans("Constant amount");
}
}

View File

@@ -0,0 +1,54 @@
<?php
/*************************************************************************************/
/* This file is part of the Thelia package. */
/* */
/* Copyright (c) OpenStudio */
/* email : dev@thelia.net */
/* web : http://www.thelia.net */
/* */
/* For the full copyright and license information, please view the LICENSE.txt */
/* file that was distributed with this source code. */
/*************************************************************************************/
namespace Thelia\TaxEngine\TaxType;
use Thelia\Type\FloatType;
use Thelia\Core\Translation\Translator;
use Thelia\TaxEngine\TaxTypeRequirementDefinition;
use Thelia\TaxEngine\BaseTaxType;
/**
*
* @author Etienne Roudeix <eroudeix@openstudio.fr>
*
*/
class PricePercentTaxType extends BaseTaxType
{
public function setPercentage($percent)
{
$this->setRequirement('percent', $percent);
return $this;
}
public function pricePercentRetriever()
{
return ($this->getRequirement("percent") * 0.01);
}
public function getRequirementsDefinition()
{
return array(
new TaxTypeRequirementDefinition(
'percent',
new FloatType(),
Translator::getInstance()->trans("Percent")
)
);
}
public function getTitle()
{
return Translator::getInstance()->trans("Percentage of the product price");
}
}

View File

@@ -0,0 +1,71 @@
<?php
/*************************************************************************************/
/* This file is part of the Thelia package. */
/* */
/* Copyright (c) OpenStudio */
/* email : dev@thelia.net */
/* web : http://www.thelia.net */
/* */
/* For the full copyright and license information, please view the LICENSE.txt */
/* file that was distributed with this source code. */
/*************************************************************************************/
namespace Thelia\TaxEngine;
use Thelia\Type\TypeInterface;
/**
* This class defines a Tax type requirement
*
* @author Franck Allimant <franck@cqfdev.fr>
*/
class TaxTypeRequirementDefinition
{
/**
* @var string The requirement name
*/
protected $name;
/**
* @var TypeInterface The requirement type
*/
protected $type;
/**
* @var string The translated requirement title
*/
protected $title;
/**
* Create a new Tax type requirement
*
* @param string $name the name of the requirement
* @param TypeInterface $type the type of the data
*/
public function __construct($name, TypeInterface $type, $title = null)
{
$this->name = $name;
$this->type = $type;
$this->title = $title ?: $name;
}
public function getName()
{
return $this->name;
}
public function getType()
{
return $this->type;
}
public function getTitle()
{
return $this->title;
}
public function isValueValid($value)
{
return $this->type->isValid($value);
}
}