diff --git a/core/lib/Thelia/Action/Order.php b/core/lib/Thelia/Action/Order.php index b48f72439..eea8b82b7 100755 --- a/core/lib/Thelia/Action/Order.php +++ b/core/lib/Thelia/Action/Order.php @@ -197,6 +197,7 @@ class Order extends BaseAction implements EventSubscriberInterface $taxRuleI18n = I18n::forceI18nRetrieving($this->getSession()->getLang()->getLocale(), 'TaxRule', $product->getTaxRuleId()); $taxDetail = $product->getTaxRule()->getTaxDetail( + $product, $taxCountry, $cartItem->getPrice(), $cartItem->getPromoPrice(), diff --git a/core/lib/Thelia/Action/TaxRule.php b/core/lib/Thelia/Action/TaxRule.php new file mode 100644 index 000000000..b3656d993 --- /dev/null +++ b/core/lib/Thelia/Action/TaxRule.php @@ -0,0 +1,162 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace Thelia\Action; + +use Propel\Runtime\ActiveQuery\Criteria; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Thelia\Core\Event\Tax\TaxRuleEvent; +use Thelia\Core\Event\TheliaEvents; +use Thelia\Model\TaxRuleCountry; +use Thelia\Model\TaxRuleCountryQuery; +use Thelia\Model\TaxRule as TaxRuleModel; +use Thelia\Model\TaxRuleQuery; + +class TaxRule extends BaseAction implements EventSubscriberInterface +{ + /** + * @param TaxRuleEvent $event + */ + public function create(TaxRuleEvent $event) + { + $product = new TaxRuleModel(); + + $product + ->setDispatcher($this->getDispatcher()) + + ->setRef($event->getRef()) + ->setTitle($event->getTitle()) + ->setLocale($event->getLocale()) + ->setVisible($event->getVisible()) + + // Set the default tax rule to this product + ->setTaxRule(TaxRuleQuery::create()->findOneByIsDefault(true)) + + //public function create($defaultCategoryId, $basePrice, $priceCurrencyId, $taxRuleId, $baseWeight) { + + ->create( + $event->getDefaultCategory(), + $event->getBasePrice(), + $event->getCurrencyId(), + $event->getTaxRuleId(), + $event->getBaseWeight() + ); + ; + + $event->setTaxRule($product); + } + + /** + * @param TaxRuleEvent $event + */ + public function update(TaxRuleEvent $event) + { + if (null !== $taxRule = TaxRuleQuery::create()->findPk($event->getId())) { + + $taxRule + ->setLocale($event->getLocale()) + ->setTitle($event->getTitle()) + ->setDescription($event->getDescription()) + ->save() + ; + + + + $event->setTaxRule($taxRule); + } + } + + /** + * @param TaxRuleEvent $event + */ + public function updateTaxes(TaxRuleEvent $event) + { + if (null !== $taxRule = TaxRuleQuery::create()->findPk($event->getId())) { + + $taxList = json_decode($event->getTaxList(), true); + + /* clean the current tax rule for the countries */ + TaxRuleCountryQuery::create() + ->filterByTaxRule($taxRule) + ->filterByCountryId($event->getCountryList(), Criteria::IN) + ->delete(); + + /* for each country */ + foreach($event->getCountryList() as $country) { + $position = 1; + /* on applique les nouvelles regles */ + foreach($taxList as $tax) { + if(is_array($tax)) { + foreach($tax as $samePositionTax) { + $taxModel = new TaxRuleCountry(); + $taxModel->setTaxRule($taxRule) + ->setCountryId($country) + ->setTaxId($samePositionTax) + ->setPosition($position); + $taxModel->save(); + } + } else { + $taxModel = new TaxRuleCountry(); + $taxModel->setTaxRule($taxRule) + ->setCountryId($country) + ->setTaxId($tax) + ->setPosition($position); + $taxModel->save(); + } + $position++; + } + } + + $event->setTaxRule($taxRule); + } + } + + /** + * @param TaxRuleEvent $event + */ + public function delete(TaxRuleEvent $event) + { + if (null !== $taxRule = TaxRuleQuery::create()->findPk($event->getId())) { + + $taxRule + ->delete() + ; + + $event->setTaxRule($taxRule); + } + } + + /** + * {@inheritDoc} + */ + public static function getSubscribedEvents() + { + return array( + TheliaEvents::TAX_RULE_CREATE => array("create", 128), + TheliaEvents::TAX_RULE_UPDATE => array("update", 128), + TheliaEvents::TAX_RULE_TAXES_UPDATE => array("updateTaxes", 128), + TheliaEvents::TAX_RULE_DELETE => array("delete", 128), + + ); + } +} diff --git a/core/lib/Thelia/Config/Resources/action.xml b/core/lib/Thelia/Config/Resources/action.xml index a9b3e247d..939cc9d88 100755 --- a/core/lib/Thelia/Config/Resources/action.xml +++ b/core/lib/Thelia/Config/Resources/action.xml @@ -106,6 +106,11 @@ + + + + + diff --git a/core/lib/Thelia/Config/Resources/config.xml b/core/lib/Thelia/Config/Resources/config.xml index d4d63ac77..39ba508fd 100755 --- a/core/lib/Thelia/Config/Resources/config.xml +++ b/core/lib/Thelia/Config/Resources/config.xml @@ -45,7 +45,9 @@ + + @@ -111,6 +113,9 @@
+ + + diff --git a/core/lib/Thelia/Config/Resources/routing/admin.xml b/core/lib/Thelia/Config/Resources/routing/admin.xml index 5df02e5bd..62049d285 100755 --- a/core/lib/Thelia/Config/Resources/routing/admin.xml +++ b/core/lib/Thelia/Config/Resources/routing/admin.xml @@ -787,6 +787,31 @@ + + + + Thelia\Controller\Admin\TaxRuleController::defaultAction + + + + Thelia\Controller\Admin\TaxRuleController::updateAction + \d+ + + + + Thelia\Controller\Admin\TaxRuleController::processUpdateAction + + + + Thelia\Controller\Admin\TaxRuleController::processUpdateTaxesAction + + + + Thelia\Controller\Admin\TaxRuleController::deleteAction + + + + diff --git a/core/lib/Thelia/Controller/Admin/TaxRuleController.php b/core/lib/Thelia/Controller/Admin/TaxRuleController.php new file mode 100644 index 000000000..b50a39638 --- /dev/null +++ b/core/lib/Thelia/Controller/Admin/TaxRuleController.php @@ -0,0 +1,270 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace Thelia\Controller\Admin; + +use Thelia\Core\Event\Tax\TaxRuleEvent; +use Thelia\Core\Event\TheliaEvents; +use Thelia\Form\TaxRuleCreationForm; +use Thelia\Form\TaxRuleModificationForm; +use Thelia\Form\TaxRuleTaxListUpdateForm; +use Thelia\Model\CountryQuery; +use Thelia\Model\TaxRuleQuery; + +class TaxRuleController extends AbstractCrudController +{ + public function __construct() + { + parent::__construct( + 'taxrule', + 'manual', + 'order', + + 'admin.configuration.taxrule.view', + 'admin.configuration.taxrule.create', + 'admin.configuration.taxrule.update', + 'admin.configuration.taxrule.delete', + + TheliaEvents::TAX_RULE_CREATE, + TheliaEvents::TAX_RULE_UPDATE, + TheliaEvents::TAX_RULE_DELETE + ); + } + + protected function getCreationForm() + { + return new TaxRuleCreationForm($this->getRequest()); + } + + protected function getUpdateForm() + { + return new TaxRuleModificationForm($this->getRequest()); + } + + protected function getCreationEvent($formData) + { + $event = new TaxRuleEvent(); + + /* @todo fill event */ + + return $event; + } + + protected function getUpdateEvent($formData) + { + $event = new TaxRuleEvent(); + + $event->setLocale($formData['locale']); + $event->setId($formData['id']); + $event->setTitle($formData['title']); + $event->setDescription($formData['description']); + + return $event; + } + + protected function getUpdateTaxListEvent($formData) + { + $event = new TaxRuleEvent(); + + $event->setId($formData['id']); + $event->setTaxList($formData['tax_list']); + $event->setCountryList($formData['country_list']); + + return $event; + } + + protected function getDeleteEvent() + { + $event = new TaxRuleEvent(); + + $event->setId( + $this->getRequest()->get('tax_rule_id', 0) + ); + + return $event; + } + + protected function eventContainsObject($event) + { + return $event->hasTaxRule(); + } + + protected function hydrateObjectForm($object) + { + $data = array( + 'id' => $object->getId(), + 'locale' => $object->getLocale(), + 'title' => $object->getTitle(), + 'description' => $object->getDescription(), + ); + + // Setup the object form + return new TaxRuleModificationForm($this->getRequest(), "form", $data); + } + + protected function hydrateTaxUpdateForm($object) + { + $data = array( + 'id' => $object->getId(), + ); + + // Setup the object form + return new TaxRuleTaxListUpdateForm($this->getRequest(), "form", $data); + } + + protected function getObjectFromEvent($event) + { + return $event->hasTaxRule() ? $event->getTaxRule() : null; + } + + protected function getExistingObject() + { + return TaxRuleQuery::create() + ->joinWithI18n($this->getCurrentEditionLocale()) + ->findOneById($this->getRequest()->get('tax_rule_id')); + } + + protected function getObjectLabel($object) + { + return $object->getTitle(); + } + + protected function getObjectId($object) + { + return $object->getId(); + } + + protected function getViewArguments($country = null) + { + return array( + 'tab' => $this->getRequest()->get('tab', 'data'), + 'country' => $country === null ? $this->getRequest()->get('country', CountryQuery::create()->findOneByByDefault(1)->getId()) : $country, + ); + } + + protected function getRouteArguments() + { + return array( + 'tax_rule_id' => $this->getRequest()->get('tax_rule_id'), + ); + } + + protected function renderListTemplate($currentOrder) + { + // We always return to the feature edition form + return $this->render( + 'taxes-rules', + array() + ); + } + + protected function renderEditionTemplate() + { + // We always return to the feature edition form + return $this->render('tax-rule-edit', array_merge($this->getViewArguments(), $this->getRouteArguments())); + } + + protected function redirectToEditionTemplate($request = null, $country = null) + { + // We always return to the feature edition form + $this->redirectToRoute( + "admin.configuration.taxes-rules.update", + $this->getViewArguments($country), + $this->getRouteArguments() + ); + } + + protected function redirectToListTemplate() + { + $this->redirectToRoute( + "admin.configuration.taxes-rules.list" + ); + } + + public function updateAction() + { + if (null !== $response = $this->checkAuth($this->updatePermissionIdentifier)) return $response; + + $object = $this->getExistingObject(); + + if ($object != null) { + + // Hydrate the form abd pass it to the parser + $changeTaxesForm = $this->hydrateTaxUpdateForm($object); + + // Pass it to the parser + $this->getParserContext()->addForm($changeTaxesForm); + } + + return parent::updateAction(); + } + + public function processUpdateTaxesAction() + { + // Check current user authorization + if (null !== $response = $this->checkAuth('admin.configuration.taxrule.update')) return $response; + + $error_msg = false; + + // Create the form from the request + $changeForm = new TaxRuleTaxListUpdateForm($this->getRequest()); + + try { + // Check the form against constraints violations + $form = $this->validateForm($changeForm, "POST"); + + // Get the form field values + $data = $form->getData(); + + $changeEvent = $this->getUpdateTaxListEvent($data); + + $this->dispatch(TheliaEvents::TAX_RULE_TAXES_UPDATE, $changeEvent); + + if (! $this->eventContainsObject($changeEvent)) + throw new \LogicException( + $this->getTranslator()->trans("No %obj was updated.", array('%obj', $this->objectName))); + + // Log object modification + if (null !== $changedObject = $this->getObjectFromEvent($changeEvent)) { + $this->adminLogAppend(sprintf("%s %s (ID %s) modified", ucfirst($this->objectName), $this->getObjectLabel($changedObject), $this->getObjectId($changedObject))); + } + + if ($response == null) { + $this->redirectToEditionTemplate($this->getRequest(), isset($data['country_list'][0]) ? $data['country_list'][0] : null); + } else { + return $response; + } + } catch (FormValidationException $ex) { + // Form cannot be validated + $error_msg = $this->createStandardFormValidationErrorMessage($ex); + } catch (\Exception $ex) { + // Any other error + $error_msg = $ex->getMessage(); + } + + $this->setupFormErrorContext($this->getTranslator()->trans("%obj modification", array('%obj' => 'taxrule')), $error_msg, $changeForm, $ex); + + // At this point, the form has errors, and should be redisplayed. + return $this->renderEditionTemplate(); + } +} \ No newline at end of file diff --git a/core/lib/Thelia/Core/Event/Tax/TaxRuleEvent.php b/core/lib/Thelia/Core/Event/Tax/TaxRuleEvent.php new file mode 100644 index 000000000..e364bfd84 --- /dev/null +++ b/core/lib/Thelia/Core/Event/Tax/TaxRuleEvent.php @@ -0,0 +1,122 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace Thelia\Core\Event\Tax; +use Thelia\Core\Event\ActionEvent; +use Thelia\Model\TaxRule; + +class TaxRuleEvent extends ActionEvent +{ + protected $taxRule = null; + + protected $locale; + protected $id; + protected $title; + protected $description; + protected $countryList; + protected $taxList; + + public function __construct(TaxRule $taxRule = null) + { + $this->taxRule = $taxRule; + } + + public function hasTaxRule() + { + return ! is_null($this->taxRule); + } + + public function getTaxRule() + { + return $this->taxRule; + } + + public function setTaxRule(TaxRule $taxRule) + { + $this->taxRule = $taxRule; + + return $this; + } + + public function setDescription($description) + { + $this->description = $description; + } + + public function getDescription() + { + return $this->description; + } + + public function setId($id) + { + $this->id = $id; + } + + public function getId() + { + return $this->id; + } + + public function setTitle($title) + { + $this->title = $title; + } + + public function getTitle() + { + return $this->title; + } + + public function setLocale($locale) + { + $this->locale = $locale; + } + + public function getLocale() + { + return $this->locale; + } + + public function setCountryList($countryList) + { + $this->countryList = $countryList; + } + + public function getCountryList() + { + return $this->countryList; + } + + public function setTaxList($taxList) + { + $this->taxList = $taxList; + } + + public function getTaxList() + { + return $this->taxList; + } + + +} diff --git a/core/lib/Thelia/Core/Event/TheliaEvents.php b/core/lib/Thelia/Core/Event/TheliaEvents.php index 4e4f55ba8..a21e0a9c9 100755 --- a/core/lib/Thelia/Core/Event/TheliaEvents.php +++ b/core/lib/Thelia/Core/Event/TheliaEvents.php @@ -519,6 +519,14 @@ final class TheliaEvents const CHANGE_DEFAULT_CURRENCY = 'action.changeDefaultCurrency'; + // -- Tax Rules management --------------------------------------------- + + const TAX_RULE_CREATE = "action.createTaxRule"; + const TAX_RULE_UPDATE = "action.updateTaxRule"; + const TAX_RULE_DELETE = "action.deleteTaxRule"; + const TAX_RULE_SET_DEFAULT = "action.setDefaultTaxRule"; + const TAX_RULE_TAXES_UPDATE = "action.updateTaxesTaxRule"; + // -- Product templates management ----------------------------------------- const TEMPLATE_CREATE = "action.createTemplate"; diff --git a/core/lib/Thelia/Core/Template/Loop/Country.php b/core/lib/Thelia/Core/Template/Loop/Country.php index c3271a6ac..a44e49277 100755 --- a/core/lib/Thelia/Core/Template/Loop/Country.php +++ b/core/lib/Thelia/Core/Template/Loop/Country.php @@ -113,6 +113,7 @@ class Country extends BaseI18nLoop ->set("CHAPO", $country->getVirtualColumn('i18n_CHAPO')) ->set("DESCRIPTION", $country->getVirtualColumn('i18n_DESCRIPTION')) ->set("POSTSCRIPTUM", $country->getVirtualColumn('i18n_POSTSCRIPTUM')) + ->set("IS_DEFAULT", $country->getByDefault() === 1 ? "1" : "0") ->set("ISOCODE", $country->getIsocode()) ->set("ISOALPHA2", $country->getIsoalpha2()) ->set("ISOALPHA3", $country->getIsoalpha3()) diff --git a/core/lib/Thelia/Core/Template/Loop/Tax.php b/core/lib/Thelia/Core/Template/Loop/Tax.php new file mode 100644 index 000000000..1ddf18824 --- /dev/null +++ b/core/lib/Thelia/Core/Template/Loop/Tax.php @@ -0,0 +1,167 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace Thelia\Core\Template\Loop; + +use Propel\Runtime\ActiveQuery\Criteria; +use Thelia\Core\Template\Element\BaseI18nLoop; +use Thelia\Core\Template\Element\LoopResult; +use Thelia\Core\Template\Element\LoopResultRow; + +use Thelia\Core\Template\Loop\Argument\ArgumentCollection; +use Thelia\Core\Template\Loop\Argument\Argument; + +use Thelia\Model\Base\TaxRuleCountryQuery; +use Thelia\Type\TypeCollection; +use Thelia\Type; +use Thelia\Model\TaxQuery; + +/** + * + * Tax loop + * + * + * Class Tax + * @package Thelia\Core\Template\Loop + * @author Etienne Roudeix + */ +class Tax extends BaseI18nLoop +{ + public $timestampable = true; + + /** + * @return ArgumentCollection + */ + protected function getArgDefinitions() + { + return new ArgumentCollection( + Argument::createIntListTypeArgument('id'), + Argument::createIntListTypeArgument('exclude'), + Argument::createIntListTypeArgument('tax_rule'), + Argument::createIntListTypeArgument('exclude_tax_rule'), + Argument::createIntTypeArgument('country'), + new Argument( + 'order', + new TypeCollection( + new Type\EnumListType(array('id', 'id_reverse', 'alpha', 'alpha_reverse')) + ), + 'alpha' + ) + ); + } + + /** + * @param $pagination + * + * @return \Thelia\Core\Template\Element\LoopResult + */ + public function exec(&$pagination) + { + $search = TaxQuery::create(); + + /* manage translations */ + $locale = $this->configureI18nProcessing($search, array('TITLE', 'DESCRIPTION')); + + $id = $this->getId(); + + if (null !== $id) { + $search->filterById($id, Criteria::IN); + } + + $exclude = $this->getExclude(); + + if (null !== $exclude) { + $search->filterById($exclude, Criteria::NOT_IN); + } + + $country = $this->getCountry(); + + $taxRule = $this->getTax_rule(); + if(null !== $taxRule && null !== $country) { + $search->filterByTaxRuleCountry( + TaxRuleCountryQuery::create() + ->filterByCountryId($country, Criteria::EQUAL) + ->filterByTaxRuleId($taxRule, Criteria::IN) + ->find(), + Criteria::IN + ); + } + + $excludeTaxRule = $this->getExclude_tax_rule(); + if(null !== $excludeTaxRule && null !== $country) { + $excludedTaxes = TaxRuleCountryQuery::create() + ->filterByCountryId($country, Criteria::EQUAL) + ->filterByTaxRuleId($excludeTaxRule, Criteria::IN) + ->find(); + /*DOES NOT WORK + * $search->filterByTaxRuleCountry( + $excludedTaxes, + Criteria::NOT_IN + );*/ + foreach($excludedTaxes as $excludedTax) { + $search->filterByTaxRuleCountry($excludedTax, Criteria::NOT_EQUAL); + } + } + + $orders = $this->getOrder(); + + foreach ($orders as $order) { + switch ($order) { + case "id": + $search->orderById(Criteria::ASC); + break; + case "id_reverse": + $search->orderById(Criteria::DESC); + break; + case "alpha": + $search->addAscendingOrderByColumn('i18n_TITLE'); + break; + case "alpha_reverse": + $search->addDescendingOrderByColumn('i18n_TITLE'); + break; + } + } + + /* perform search */ + $taxes = $this->search($search, $pagination); + + $loopResult = new LoopResult($taxes); + + foreach ($taxes as $tax) { + + $loopResultRow = new LoopResultRow($loopResult, $tax, $this->versionable, $this->timestampable, $this->countable); + + $loopResultRow + ->set("ID" , $tax->getId()) + ->set("IS_TRANSLATED" , $tax->getVirtualColumn('IS_TRANSLATED')) + ->set("LOCALE" , $locale) + ->set("TITLE" , $tax->getVirtualColumn('i18n_TITLE')) + ->set("DESCRIPTION" , $tax->getVirtualColumn('i18n_DESCRIPTION')) + ; + + $loopResult->addRow($loopResultRow); + } + + return $loopResult; + } +} diff --git a/core/lib/Thelia/Core/Template/Loop/TaxRuleCountry.php b/core/lib/Thelia/Core/Template/Loop/TaxRuleCountry.php new file mode 100644 index 000000000..91616f398 --- /dev/null +++ b/core/lib/Thelia/Core/Template/Loop/TaxRuleCountry.php @@ -0,0 +1,162 @@ +. */ +/* */ +/*************************************************************************************/ + +namespace Thelia\Core\Template\Loop; + +use Propel\Runtime\ActiveQuery\Criteria; +use Propel\Runtime\ActiveQuery\Join; +use Thelia\Core\Template\Element\BaseI18nLoop; +use Thelia\Core\Template\Element\LoopResult; +use Thelia\Core\Template\Element\LoopResultRow; + +use Thelia\Core\Template\Loop\Argument\ArgumentCollection; +use Thelia\Core\Template\Loop\Argument\Argument; + +use Thelia\Model\Map\CountryTableMap; +use Thelia\Model\Map\TaxRuleCountryTableMap; +use Thelia\Model\Map\TaxTableMap; +use Thelia\Type\TypeCollection; +use Thelia\Type; +use Thelia\Model\TaxRuleCountryQuery; + +/** + * + * TaxRuleCountry loop + * + * + * Class TaxRuleCountry + * @package Thelia\Core\Template\Loop + * @author Etienne Roudeix + */ +class TaxRuleCountry extends BaseI18nLoop +{ + public $timestampable = true; + + /** + * @return ArgumentCollection + */ + protected function getArgDefinitions() + { + return new ArgumentCollection( + Argument::createIntTypeArgument('country'), + Argument::createIntListTypeArgument('taxes'), + Argument::createIntTypeArgument('tax_rule', null, true) + ); + } + + /** + * @param $pagination + * + * @return \Thelia\Core\Template\Element\LoopResult + */ + public function exec(&$pagination) + { + $search = TaxRuleCountryQuery::create(); + + $country = $this->getCountry(); + $taxes = $this->getTaxes(); + + if((null === $country && null === $taxes)) { + throw new \InvalidArgumentException('You must provide either `country` or `taxes` parameter in tax-rule-country loop'); + } + + if((null === $country && null !== $taxes)) { + throw new \InvalidArgumentException('You must provide `country` parameter with `taxes` parameter in tax-rule-country loop'); + } + + if(null !== $taxes) { + $search->groupByCountryId(); + + $originalCountryJoin = new Join(); + $originalCountryJoin->addExplicitCondition(TaxRuleCountryTableMap::TABLE_NAME, 'TAX_RULE_ID', null, TaxRuleCountryTableMap::TABLE_NAME, 'TAX_RULE_ID', 'origin'); + $originalCountryJoin->addExplicitCondition(TaxRuleCountryTableMap::TABLE_NAME, 'TAX_ID', null, TaxRuleCountryTableMap::TABLE_NAME, 'TAX_ID', 'origin'); + $originalCountryJoin->addExplicitCondition(TaxRuleCountryTableMap::TABLE_NAME, 'POSITION', null, TaxRuleCountryTableMap::TABLE_NAME, 'POSITION', 'origin'); + $originalCountryJoin->addExplicitCondition(TaxRuleCountryTableMap::TABLE_NAME, 'COUNTRY_ID', null, TaxRuleCountryTableMap::TABLE_NAME, 'COUNTRY_ID', 'origin', Criteria::NOT_EQUAL); + $originalCountryJoin->setJoinType(Criteria::LEFT_JOIN); + + $search->addJoinObject($originalCountryJoin, 's_to_o'); + $search->where('`origin`.`COUNTRY_ID`' . Criteria::EQUAL . '?', $country, \PDO::PARAM_INT); + + $search->having('COUNT(*)=?', count($taxes), \PDO::PARAM_INT); + + /* manage tax translation */ + $this->configureI18nProcessing( + $search, + array('TITLE', 'CHAPO', 'DESCRIPTION', 'POSTSCRIPTUM'), + CountryTableMap::TABLE_NAME, + 'COUNTRY_ID' + ); + } elseif(null !== $country) { + $search->filterByCountryId($country); + + /* manage tax translation */ + $this->configureI18nProcessing( + $search, + array('TITLE', 'DESCRIPTION'), + TaxTableMap::TABLE_NAME, + 'TAX_ID' + ); + } + + $taxRule = $this->getTax_rule(); + $search->filterByTaxRuleId($taxRule); + + $search->orderByPosition(Criteria::ASC); + + /* perform search */ + $taxRuleCountries = $this->search($search, $pagination); + + $loopResult = new LoopResult($taxRuleCountries); + + foreach ($taxRuleCountries as $taxRuleCountry) { + + $loopResultRow = new LoopResultRow($loopResult, $taxRuleCountry, $this->versionable, $this->timestampable, $this->countable); + + if(null !== $taxes) { + $loopResultRow + ->set("TAX_RULE" , $taxRuleCountry->getTaxRuleId()) + ->set("COUNTRY" , $taxRuleCountry->getCountryId()) + ->set("COUNTRY_TITLE" , $taxRuleCountry->getVirtualColumn(CountryTableMap::TABLE_NAME . '_i18n_TITLE')) + ->set("COUNTRY_CHAPO" , $taxRuleCountry->getVirtualColumn(CountryTableMap::TABLE_NAME . '_i18n_CHAPO')) + ->set("COUNTRY_DESCRIPTION" , $taxRuleCountry->getVirtualColumn(CountryTableMap::TABLE_NAME . '_i18n_DESCRIPTION')) + ->set("COUNTRY_POSTSCRIPTUM" , $taxRuleCountry->getVirtualColumn(CountryTableMap::TABLE_NAME . '_i18n_POSTSCRIPTUM')) + ; + }elseif(null !== $country) { + $loopResultRow + ->set("TAX_RULE" , $taxRuleCountry->getTaxRuleId()) + ->set("COUNTRY" , $taxRuleCountry->getCountryId()) + ->set("TAX" , $taxRuleCountry->getTaxId()) + ->set("POSITION" , $taxRuleCountry->getPosition()) + ->set("TAX_TITLE" , $taxRuleCountry->getVirtualColumn(TaxTableMap::TABLE_NAME . '_i18n_TITLE')) + ->set("TAX_DESCRIPTION" , $taxRuleCountry->getVirtualColumn(TaxTableMap::TABLE_NAME . '_i18n_DESCRIPTION')) + ; + } + + + + $loopResult->addRow($loopResultRow); + } + + return $loopResult; + } +} diff --git a/core/lib/Thelia/Core/Template/Smarty/Plugins/DataAccessFunctions.php b/core/lib/Thelia/Core/Template/Smarty/Plugins/DataAccessFunctions.php index 17d9e37a3..594b45ae9 100755 --- a/core/lib/Thelia/Core/Template/Smarty/Plugins/DataAccessFunctions.php +++ b/core/lib/Thelia/Core/Template/Smarty/Plugins/DataAccessFunctions.php @@ -163,16 +163,16 @@ class DataAccessFunctions extends AbstractSmartyPlugin public function countryDataAccess($params, $smarty) { - if (array_key_exists('defaultCountry', self::$dataAccessCache)) { - $defaultCountry = self::$dataAccessCache['defaultCountry']; - } else { - $defaultCountry = CountryQuery::create()->findOneByByDefault(1); - self::$dataAccessCache['defaultCountry'] = $defaultCountry; - } - - switch ($params["attr"]) { + switch ($params["ask"]) { case "default": - return $defaultCountry->getId(); + /*if (array_key_exists('defaultCountry', self::$dataAccessCache)) { + $defaultCountry = self::$dataAccessCache['defaultCountry']; + } else { + $defaultCountry = CountryQuery::create()->findOneByByDefault(1); + self::$dataAccessCache['defaultCountry'] = $defaultCountry; + }*/ + $defaultCountry = CountryQuery::create()->filterByByDefault(1)->limit(1); + return $this->dataAccessWithI18n("defaultCountry", $params, $defaultCountry); } } diff --git a/core/lib/Thelia/Core/Template/Smarty/Plugins/Form.php b/core/lib/Thelia/Core/Template/Smarty/Plugins/Form.php index e7613bbac..76be30ad0 100755 --- a/core/lib/Thelia/Core/Template/Smarty/Plugins/Form.php +++ b/core/lib/Thelia/Core/Template/Smarty/Plugins/Form.php @@ -182,7 +182,8 @@ $this->assignFieldValues($template, $formFieldView->vars["full_name"], $fieldVar public function renderHiddenFormField($params, \Smarty_Internal_Template $template) { - $field = ''; + $attrFormat = '%s="%s"'; + $field = ''; $instance = $this->getInstanceFromParams($params); @@ -192,7 +193,13 @@ $this->assignFieldValues($template, $formFieldView->vars["full_name"], $fieldVar foreach ($formView->getIterator() as $row) { if ($this->isHidden($row) && $row->isRendered() === false) { - $return .= sprintf($field, $row->vars["full_name"], $row->vars["value"]); + $attributeList = array(); + if(isset($row->vars["attr"])) { + foreach($row->vars["attr"] as $attrKey => $attrValue) { + $attributeList[] = sprintf($attrFormat, $attrKey, $attrValue); + } + } + $return .= sprintf($field, $row->vars["full_name"], $row->vars["value"], implode(' ', $attributeList)); } } diff --git a/core/lib/Thelia/Exception/TaxEngineException.php b/core/lib/Thelia/Exception/TaxEngineException.php index 86f8952b9..8150d7169 100755 --- a/core/lib/Thelia/Exception/TaxEngineException.php +++ b/core/lib/Thelia/Exception/TaxEngineException.php @@ -40,9 +40,12 @@ class TaxEngineException extends \RuntimeException const UNDEFINED_REQUIREMENTS = 504; const UNDEFINED_REQUIREMENT_VALUE = 505; const UNDEFINED_TAX_RULE = 506; + const NO_TAX_IN_TAX_RULES_COLLECTION = 507; const BAD_AMOUNT_FORMAT = 601; + const FEATURE_BAD_EXPECTED_VALUE = 701; + public function __construct($message, $code = null, $previous = null) { if ($code === null) { diff --git a/core/lib/Thelia/Form/TaxRuleCreationForm.php b/core/lib/Thelia/Form/TaxRuleCreationForm.php new file mode 100644 index 000000000..a069bd005 --- /dev/null +++ b/core/lib/Thelia/Form/TaxRuleCreationForm.php @@ -0,0 +1,45 @@ +. */ +/* */ +/*************************************************************************************/ +namespace Thelia\Form; + +use Symfony\Component\Validator\Constraints; +use Symfony\Component\Validator\Constraints\NotBlank; +use Thelia\Core\Translation\Translator; +use Thelia\Model\CountryQuery; + +class TaxRuleCreationForm extends BaseForm +{ + protected function buildForm($change_mode = false) + { + $this->formBuilder + ->add("locale", "text", array( + "constraints" => array(new NotBlank()) + )) + ; + } + + public function getName() + { + return "thelia_tax_rule_creation"; + } +} diff --git a/core/lib/Thelia/Form/TaxRuleModificationForm.php b/core/lib/Thelia/Form/TaxRuleModificationForm.php new file mode 100644 index 000000000..5884e4b57 --- /dev/null +++ b/core/lib/Thelia/Form/TaxRuleModificationForm.php @@ -0,0 +1,71 @@ +. */ +/* */ +/*************************************************************************************/ +namespace Thelia\Form; + +use Symfony\Component\Validator\Constraints; +use Symfony\Component\Validator\ExecutionContextInterface; +use Thelia\Model\TaxRuleQuery; + +class TaxRuleModificationForm extends TaxRuleCreationForm +{ + use StandardDescriptionFieldsTrait; + + protected function buildForm() + { + parent::buildForm(true); + + $this->formBuilder + ->add("id", "hidden", array( + "required" => true, + "constraints" => array( + new Constraints\NotBlank(), + new Constraints\Callback( + array( + "methods" => array( + array($this, "verifyTaxRuleId"), + ), + ) + ), + ) + )) + ; + + // Add standard description fields + $this->addStandardDescFields(array('postscriptum', 'chapo', 'locale')); + } + + public function getName() + { + return "thelia_tax_rule_modification"; + } + + public function verifyTaxRuleId($value, ExecutionContextInterface $context) + { + $taxRule = TaxRuleQuery::create() + ->findPk($value); + + if (null === $taxRule) { + $context->addViolation("Tax rule ID not found"); + } + } +} diff --git a/core/lib/Thelia/Form/TaxRuleTaxListUpdateForm.php b/core/lib/Thelia/Form/TaxRuleTaxListUpdateForm.php new file mode 100644 index 000000000..1d96c7e4d --- /dev/null +++ b/core/lib/Thelia/Form/TaxRuleTaxListUpdateForm.php @@ -0,0 +1,127 @@ +. */ +/* */ +/*************************************************************************************/ +namespace Thelia\Form; + +use Symfony\Component\Validator\Constraints; +use Symfony\Component\Validator\ExecutionContextInterface; +use Thelia\Model\CountryQuery; +use Thelia\Model\TaxQuery; +use Thelia\Model\TaxRuleQuery; +use Thelia\Type\JsonType; + +class TaxRuleTaxListUpdateForm extends BaseForm +{ + protected function buildForm() + { + $countryList = array(); + foreach(CountryQuery::create()->find() as $country) { + $countryList[$country->getId()] = $country->getId(); + } + + $this->formBuilder + ->add("id", "hidden", array( + "required" => true, + "constraints" => array( + new Constraints\NotBlank(), + new Constraints\Callback( + array( + "methods" => array( + array($this, "verifyTaxRuleId"), + ), + ) + ), + ) + )) + ->add("tax_list", "hidden", array( + "required" => true, + "attr" => array( + "id" => 'tax_list', + ), + "constraints" => array( + new Constraints\Callback( + array( + "methods" => array( + array($this, "verifyTaxList"), + ), + ) + ), + ) + )) + ->add("country_list", "choice", array( + "choices" => $countryList, + "required" => true, + "multiple" => true, + "constraints" => array( + new Constraints\NotBlank(), + ) + )) + ; + } + + public function getName() + { + return "thelia_tax_rule_taxlistupdate"; + } + + public function verifyTaxRuleId($value, ExecutionContextInterface $context) + { + $taxRule = TaxRuleQuery::create() + ->findPk($value); + + if (null === $taxRule) { + $context->addViolation("Tax rule ID not found"); + } + } + + public function verifyTaxList($value, ExecutionContextInterface $context) + { + $jsonType = new JsonType(); + if(!$jsonType->isValid($value)) { + $context->addViolation("Tax list is not valid JSON"); + } + + $taxList = json_decode($value, true); + + /* check we have 2 level max */ + + foreach($taxList as $taxLevel1) { + if(is_array($taxLevel1)) { + foreach($taxLevel1 as $taxLevel2) { + if(is_array($taxLevel2)) { + $context->addViolation("Bad tax list JSON"); + } else { + $taxModel = TaxQuery::create()->findPk($taxLevel2); + if (null === $taxModel) { + $context->addViolation("Tax ID not found in tax list JSON"); + } + } + } + } else { + $taxModel = TaxQuery::create()->findPk($taxLevel1); + if (null === $taxModel) { + $context->addViolation("Tax ID not found in tax list JSON"); + } + } + } + } +} diff --git a/core/lib/Thelia/Model/TaxRule.php b/core/lib/Thelia/Model/TaxRule.php index e66e013d0..72d8ae851 100755 --- a/core/lib/Thelia/Model/TaxRule.php +++ b/core/lib/Thelia/Model/TaxRule.php @@ -16,14 +16,14 @@ class TaxRule extends BaseTaxRule * * @return OrderProductTaxCollection */ - public function getTaxDetail(Country $country, $untaxedAmount, $untaxedPromoAmount, $askedLocale = null) + public function getTaxDetail(Product $product, Country $country, $untaxedAmount, $untaxedPromoAmount, $askedLocale = null) { $taxCalculator = new Calculator(); $taxCollection = new OrderProductTaxCollection(); - $taxCalculator->loadTaxRule($this, $country)->getTaxedPrice($untaxedAmount, $taxCollection, $askedLocale); + $taxCalculator->loadTaxRule($this, $country, $product)->getTaxedPrice($untaxedAmount, $taxCollection, $askedLocale); $promoTaxCollection = new OrderProductTaxCollection(); - $taxCalculator->loadTaxRule($this, $country)->getTaxedPrice($untaxedPromoAmount, $promoTaxCollection, $askedLocale); + $taxCalculator->loadTaxRule($this, $country, $product)->getTaxedPrice($untaxedPromoAmount, $promoTaxCollection, $askedLocale); foreach($taxCollection as $index => $tax) { $tax->setPromoAmount($promoTaxCollection->getKey($index)->getAmount()); diff --git a/core/lib/Thelia/TaxEngine/Calculator.php b/core/lib/Thelia/TaxEngine/Calculator.php index 8039dec39..a067f200d 100755 --- a/core/lib/Thelia/TaxEngine/Calculator.php +++ b/core/lib/Thelia/TaxEngine/Calculator.php @@ -76,7 +76,7 @@ class Calculator return $this; } - public function loadTaxRule(TaxRule $taxRule, Country $country) + public function loadTaxRule(TaxRule $taxRule, Country $country, Product $product) { $this->product = null; $this->country = null; @@ -88,8 +88,12 @@ class Calculator 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->taxRulesCollection = $this->taxRuleQuery->getTaxCalculatorCollection($taxRule, $country); @@ -117,7 +121,11 @@ class Calculator public function getTaxedPrice($untaxedPrice, &$taxCollection = null, $askedLocale = null) { if(null === $this->taxRulesCollection) { - throw new TaxEngineException('Tax rules collection is empty in Calculator::getTaxAmount', TaxEngineException::UNDEFINED_TAX_RULES_COLLECTION); + 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)) { @@ -143,7 +151,7 @@ class Calculator $currentPosition = $position; } - $taxAmount = round($taxType->calculate($taxedPrice), 2); + $taxAmount = round($taxType->calculate($this->product, $taxedPrice), 2); $currentTax += $taxAmount; if(null !== $taxCollection) { @@ -167,12 +175,20 @@ class Calculator 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; @@ -192,7 +208,7 @@ class Calculator $currentPosition = $position; } - $currentFixTax += $taxType->fixAmountRetriever(); + $currentFixTax += $taxType->fixAmountRetriever($this->product); $currentTaxFactor += $taxType->pricePercentRetriever(); diff --git a/core/lib/Thelia/TaxEngine/TaxType/BaseTaxType.php b/core/lib/Thelia/TaxEngine/TaxType/BaseTaxType.php index 149e3f1df..1e0a11ca7 100755 --- a/core/lib/Thelia/TaxEngine/TaxType/BaseTaxType.php +++ b/core/lib/Thelia/TaxEngine/TaxType/BaseTaxType.php @@ -23,6 +23,7 @@ namespace Thelia\TaxEngine\TaxType; use Thelia\Exception\TaxEngineException; +use Thelia\Model\Product; use Thelia\Type\TypeInterface; /** @@ -34,14 +35,17 @@ abstract class BaseTaxType { protected $requirements = null; - public abstract function calculate($untaxedPrice); - public abstract function pricePercentRetriever(); - public abstract function fixAmountRetriever(); + public abstract function fixAmountRetriever(Product $product); public abstract function getRequirementsList(); + public function calculate(Product $product, $untaxedPrice) + { + return $untaxedPrice * $this->pricePercentRetriever() + $this->fixAmountRetriever($product); + } + public function loadRequirements($requirementsValues) { $this->requirements = $this->getRequirementsList(); diff --git a/core/lib/Thelia/TaxEngine/TaxType/FeatureFixAmountTaxType.php b/core/lib/Thelia/TaxEngine/TaxType/FeatureFixAmountTaxType.php new file mode 100755 index 000000000..32c78c1ac --- /dev/null +++ b/core/lib/Thelia/TaxEngine/TaxType/FeatureFixAmountTaxType.php @@ -0,0 +1,68 @@ +. */ +/* */ +/*************************************************************************************/ +namespace Thelia\TaxEngine\TaxType; + +use Thelia\Exception\TaxEngineException; +use Thelia\Model\FeatureProductQuery; +use Thelia\Model\Product; +use Thelia\Type\FloatType; +use Thelia\Type\ModelValidIdType; + +/** + * + * @author Etienne Roudeix + * + */ +class FeatureFixAmountTaxType extends BaseTaxType +{ + public function pricePercentRetriever() + { + return 0; + } + + public function fixAmountRetriever(Product $product) + { + $featureId = $this->getRequirement("feature"); + + $query = FeatureProductQuery::create() + ->filterByProduct($product) + ->filterByFeatureId($featureId) + ->findOne(); + + $taxAmount = $query->getFreeTextValue(); + + $testInt = new FloatType(); + if(!$testInt->isValid($taxAmount)) { + throw new TaxEngineException('Feature value does not match FLOAT format', TaxEngineException::FEATURE_BAD_EXPECTED_VALUE); + } + + return $taxAmount; + } + + public function getRequirementsList() + { + return array( + 'feature' => new ModelValidIdType('Feature'), + ); + } +} diff --git a/core/lib/Thelia/TaxEngine/TaxType/FeatureSlicePercentTaxType.php b/core/lib/Thelia/TaxEngine/TaxType/FeatureSlicePercentTaxType.php index 911439574..311a83272 100755 --- a/core/lib/Thelia/TaxEngine/TaxType/FeatureSlicePercentTaxType.php +++ b/core/lib/Thelia/TaxEngine/TaxType/FeatureSlicePercentTaxType.php @@ -23,7 +23,7 @@ namespace Thelia\TaxEngine\TaxType; use Thelia\Type\FloatToFloatArrayType; -use Thelia\Type\ModelType; +use Thelia\Type\ModelValidIdType; /** * @@ -32,17 +32,12 @@ use Thelia\Type\ModelType; */ class featureSlicePercentTaxType extends BaseTaxType { - public function calculate($untaxedPrice) - { - - } - public function pricePercentRetriever() { } - public function fixAmountRetriever() + public function fixAmountRetriever(\Thelia\Model\Product $product) { } @@ -50,7 +45,7 @@ class featureSlicePercentTaxType extends BaseTaxType public function getRequirementsList() { return array( - 'featureId' => new ModelType('Currency'), + 'featureId' => new ModelValidIdType('Currency'), 'slices' => new FloatToFloatArrayType(), ); } diff --git a/core/lib/Thelia/TaxEngine/TaxType/FixAmountTaxType.php b/core/lib/Thelia/TaxEngine/TaxType/FixAmountTaxType.php index acd52bf8a..e62136d99 100755 --- a/core/lib/Thelia/TaxEngine/TaxType/FixAmountTaxType.php +++ b/core/lib/Thelia/TaxEngine/TaxType/FixAmountTaxType.php @@ -31,17 +31,12 @@ use Thelia\Type\FloatType; */ class FixAmountTaxType extends BaseTaxType { - public function calculate($untaxedPrice) - { - return $this->getRequirement("amount"); - } - public function pricePercentRetriever() { return 0; } - public function fixAmountRetriever() + public function fixAmountRetriever(\Thelia\Model\Product $product) { return $this->getRequirement("amount"); } diff --git a/core/lib/Thelia/TaxEngine/TaxType/PricePercentTaxType.php b/core/lib/Thelia/TaxEngine/TaxType/PricePercentTaxType.php index a8cd8c759..342f51a6d 100755 --- a/core/lib/Thelia/TaxEngine/TaxType/PricePercentTaxType.php +++ b/core/lib/Thelia/TaxEngine/TaxType/PricePercentTaxType.php @@ -31,17 +31,12 @@ use Thelia\Type\FloatType; */ class PricePercentTaxType extends BaseTaxType { - public function calculate($untaxedPrice) - { - return $untaxedPrice * $this->getRequirement("percent") * 0.01; - } - public function pricePercentRetriever() { return ($this->getRequirement("percent") * 0.01); } - public function fixAmountRetriever() + public function fixAmountRetriever(\Thelia\Model\Product $product) { return 0; } diff --git a/core/lib/Thelia/Tests/Action/OrderTest.php b/core/lib/Thelia/Tests/Action/OrderTest.php index 67628e718..7d0918650 100644 --- a/core/lib/Thelia/Tests/Action/OrderTest.php +++ b/core/lib/Thelia/Tests/Action/OrderTest.php @@ -347,7 +347,7 @@ class OrderTest extends \PHPUnit_Framework_TestCase /* check tax */ $orderProductTaxList = $orderProduct->getOrderProductTaxes(); - foreach ($cartItem->getProduct()->getTaxRule()->getTaxDetail($validDeliveryAddress->getCountry(), $cartItem->getPrice(), $cartItem->getPromoPrice()) as $index => $tax) { + foreach ($cartItem->getProduct()->getTaxRule()->getTaxDetail($cartItem->getProduct(), $validDeliveryAddress->getCountry(), $cartItem->getPrice(), $cartItem->getPromoPrice()) as $index => $tax) { $orderProductTax = $orderProductTaxList[$index]; $this->assertEquals($tax->getAmount(), $orderProductTax->getAmount()); $this->assertEquals($tax->getPromoAmount(), $orderProductTax->getPromoAmount()); diff --git a/core/lib/Thelia/Tests/TaxEngine/CalculatorTest.php b/core/lib/Thelia/Tests/TaxEngine/CalculatorTest.php index f8c6ec6c0..0dbbc73bb 100755 --- a/core/lib/Thelia/Tests/TaxEngine/CalculatorTest.php +++ b/core/lib/Thelia/Tests/TaxEngine/CalculatorTest.php @@ -126,14 +126,64 @@ class CalculatorTest extends \PHPUnit_Framework_TestCase { $taxRulesCollection = new ObjectCollection(); + $aProduct = ProductQuery::create()->findOne(); + if(null === $aProduct) { + return; + } + $calculator = new Calculator(); $rewritingUrlQuery = $this->getProperty('taxRulesCollection'); $rewritingUrlQuery->setValue($calculator, $taxRulesCollection); + $product = $this->getProperty('product'); + $product->setValue($calculator, $aProduct); + $calculator->getTaxedPrice('foo'); } + /** + * @expectedException \Thelia\Exception\TaxEngineException + * @expectedExceptionCode 501 + */ + public function testGetUntaxedPriceAndGetTaxAmountFromTaxedPriceWithNoProductLoaded() + { + $taxRulesCollection = new ObjectCollection(); + $taxRulesCollection->setModel('\Thelia\Model\Tax'); + + $calculator = new Calculator(); + + $rewritingUrlQuery = $this->getProperty('taxRulesCollection'); + $rewritingUrlQuery->setValue($calculator, $taxRulesCollection); + + $calculator->getTaxAmountFromTaxedPrice(600.95); + } + + /** + * @expectedException \Thelia\Exception\TaxEngineException + * @expectedExceptionCode 507 + */ + public function testGetUntaxedPriceAndGetTaxAmountFromTaxedPriceWithEmptyTaxRuleCollection() + { + $taxRulesCollection = new ObjectCollection(); + $taxRulesCollection->setModel('\Thelia\Model\Tax'); + + $aProduct = ProductQuery::create()->findOne(); + if(null === $aProduct) { + return; + } + + $calculator = new Calculator(); + + $rewritingUrlQuery = $this->getProperty('taxRulesCollection'); + $rewritingUrlQuery->setValue($calculator, $taxRulesCollection); + + $product = $this->getProperty('product'); + $product->setValue($calculator, $aProduct); + + $calculator->getTaxAmountFromTaxedPrice(600.95); + } + public function testGetTaxedPriceAndGetTaxAmountFromUntaxedPrice() { $taxRulesCollection = new ObjectCollection(); @@ -163,11 +213,19 @@ class CalculatorTest extends \PHPUnit_Framework_TestCase ->setVirtualColumn('taxRuleCountryPosition', 3); $taxRulesCollection->append($tax); + $aProduct = ProductQuery::create()->findOne(); + if(null === $aProduct) { + return; + } + $calculator = new Calculator(); $rewritingUrlQuery = $this->getProperty('taxRulesCollection'); $rewritingUrlQuery->setValue($calculator, $taxRulesCollection); + $product = $this->getProperty('product'); + $product->setValue($calculator, $aProduct); + $taxAmount = $calculator->getTaxAmountFromUntaxedPrice(500); $taxedPrice = $calculator->getTaxedPrice(500); @@ -211,11 +269,19 @@ class CalculatorTest extends \PHPUnit_Framework_TestCase ->setVirtualColumn('taxRuleCountryPosition', 3); $taxRulesCollection->append($tax); + $aProduct = ProductQuery::create()->findOne(); + if(null === $aProduct) { + return; + } + $calculator = new Calculator(); $rewritingUrlQuery = $this->getProperty('taxRulesCollection'); $rewritingUrlQuery->setValue($calculator, $taxRulesCollection); + $product = $this->getProperty('product'); + $product->setValue($calculator, $aProduct); + $taxAmount = $calculator->getTaxAmountFromTaxedPrice(600.95); $untaxedPrice = $calculator->getUntaxedPrice(600.95); diff --git a/core/lib/Thelia/Type/ModelValidIdType.php b/core/lib/Thelia/Type/ModelValidIdType.php new file mode 100755 index 000000000..9ae94e497 --- /dev/null +++ b/core/lib/Thelia/Type/ModelValidIdType.php @@ -0,0 +1,70 @@ +. */ +/* */ +/*************************************************************************************/ +namespace Thelia\Type; + +use Propel\Runtime\ActiveQuery\ModelCriteria; +use Thelia\Exception\TypeException; + +/** + * + * @author Etienne Roudeix + * + */ +class ModelValidIdType implements TypeInterface +{ + protected $expectedModelActiveRecordQuery = null; + + /** + * @param $expectedModelActiveRecord + * @throws TypeException + */ + public function __construct($expectedModelActiveRecord) + { + $class = '\\Thelia\\Model\\' . $expectedModelActiveRecord . 'Query'; + + if (!(class_exists($class) || !new $class instanceof ModelCriteria)) { + throw new TypeException('MODEL NOT FOUND', TypeException::MODEL_NOT_FOUND); + } + + $this->expectedModelActiveRecordQuery = $class; + } + + public function getType() + { + return 'Model valid Id type'; + } + + public function isValid($value) + { + $queryClass = $this->expectedModelActiveRecordQuery; + + return null !== $queryClass::create()->findPk($value); + } + + public function getFormattedValue($value) + { + $queryClass = $this->expectedModelActiveRecordQuery; + + return $this->isValid($value) ? $queryClass::create()->findPk($value) : null; + } +} diff --git a/install/faker_add_ecotax.php b/install/faker_add_ecotax.php new file mode 100755 index 000000000..4a9c8de39 --- /dev/null +++ b/install/faker_add_ecotax.php @@ -0,0 +1,128 @@ +boot(); + +$faker = Faker\Factory::create(); + +$con = \Propel\Runtime\Propel::getConnection( + Thelia\Model\Map\ProductTableMap::DATABASE_NAME +); +$con->beginTransaction(); + +// Intialize URL management + +try { + $options = getopt('f::e::'); + + $forceEcotaxFeatureId = $options['f']; + if(null !== $forceEcotaxFeatureId && !filter_var($forceEcotaxFeatureId, FILTER_VALIDATE_INT)) { + exit('[ERROR] bad value for f option\n'); + } + + $forceEcotaxId = $options['e']; + if(null !== $forceEcotaxId && !filter_var($forceEcotaxId, FILTER_VALIDATE_INT)) { + exit('[ERROR] bad value for e option\n'); + } + + echo "Adding Ecotax feature\n"; + $feature = null; + if(null !== $forceEcotaxFeatureId) { + $feature = \Thelia\Model\FeatureQuery::create()->findPk($forceEcotaxFeatureId); + if(null === $feature) { + echo "Feature `$forceEcotaxFeatureId` not found\n"; + } + } + if(null === $feature) { + $feature = new \Thelia\Model\Feature(); + $feature->setVisible(1); + $feature->save(); + echo sprintf("Ecotax feature added with ID \n", $feature->getId()); + } + + $fr = \Thelia\Model\Base\FeatureI18nQuery::create() + ->filterByLocale('fr_FR') + ->filterByFeature($feature) + ->findOne(); + if(null === $fr) { + $fr = new \Thelia\Model\FeatureI18n(); + $fr->setLocale('fr_FR') + ->setFeature($feature); + } + $fr->setTitle('Ecotaxe'); + $fr->save($con); + + $us = \Thelia\Model\Base\FeatureI18nQuery::create() + ->filterByLocale('en_US') + ->filterByFeature($feature) + ->findOne(); + if(null === $us) { + $us = new \Thelia\Model\FeatureI18n(); + $us->setLocale('en_US') + ->setFeature($feature); + } + $us->setTitle('Ecotax'); + $us->save($con); + + echo "Adding ecotax\n"; + + $tax = null; + if(null !== $forceEcotaxId) { + $tax = \Thelia\Model\TaxQuery::create()->findPk($forceEcotaxId); + if(null === $tax) { + echo "Tax `$forceEcotaxId` not found\n"; + } + } + if(null === $tax) { + $tax = new \Thelia\Model\Tax(); + $tax->setType('FeatureFixAmountTaxType') + ->setSerializedRequirements( + base64_encode(sprintf('{"feature":%s}', $feature->getId())) + ); + $tax->save(); + echo sprintf("Ecotax added with ID \n", $tax->getId()); + } + + $fr = \Thelia\Model\Base\TaxI18nQuery::create() + ->filterByLocale('fr_FR') + ->filterByTax($tax) + ->findOne(); + if(null === $fr) { + $fr = new \Thelia\Model\TaxI18n(); + $fr->setLocale('fr_FR') + ->setTax($tax); + } + $fr->setTitle('Ecotaxe'); + $fr->save($con); + + $us = \Thelia\Model\Base\TaxI18nQuery::create() + ->filterByLocale('en_US') + ->filterByTax($tax) + ->findOne(); + if(null === $us) { + $us = new \Thelia\Model\TaxI18n(); + $us->setLocale('en_US') + ->setTax($tax); + } + $us->setTitle('Ecotax'); + $us->save($con); + + $con->commit(); + + echo "Successfully terminated.\n"; + +} catch (Exception $e) { + echo "error : ".$e->getMessage()."\n"; + $con->rollBack(); +} \ No newline at end of file diff --git a/install/insert.sql b/install/insert.sql index d91c511dc..8e6585a5b 100755 --- a/install/insert.sql +++ b/install/insert.sql @@ -1149,25 +1149,31 @@ INSERT INTO `country_i18n` (`id`, `locale`, `title`, `description`, `chapo`, `po INSERT INTO `tax` (`id`, `type`, `serialized_requirements`, `created_at`, `updated_at`) VALUES - (1, 'PricePercentTaxType', 'eyJwZXJjZW50IjoxOS42fQ==', NOW(), NOW()); + (1, 'PricePercentTaxType', 'eyJwZXJjZW50IjoxOS42fQ==', NOW(), NOW()), + (2, 'PricePercentTaxType', 'eyJwZXJjZW50Ijo1LjV9', NOW(), NOW()); INSERT INTO `tax_i18n` (`id`, `locale`, `title`) VALUES (1, 'fr_FR', 'TVA française à 19.6%'), - (1, 'en_US', 'French 19.6% VAT'); - + (1, 'en_US', 'French 19.6% VAT'), + (2, 'fr_FR', 'TVA française à 5.5%'), + (2, 'en_US', 'French 5.5% VAT'); INSERT INTO `tax_rule` (`id`, `is_default`, `created_at`, `updated_at`) VALUES - (1, 1, NOW(), NOW()); + (1, 1, NOW(), NOW()), + (2, 0, NOW(), NOW()); INSERT INTO `tax_rule_i18n` (`id`, `locale`, `title`) VALUES (1, 'fr_FR', 'TVA française à 19.6%'), - (1, 'en_US', 'French 19.6% VAT'); + (1, 'en_US', 'French 19.6% VAT'), + (2, 'fr_FR', 'TVA française à 5.5%'), + (2, 'en_US', 'French 5.5% VAT'); INSERT INTO `tax_rule_country` (`tax_rule_id`, `country_id`, `tax_id`, `position`, `created_at`, `updated_at`) VALUES - (1, 64, 1, 1, NOW(), NOW()); + (1, 64, 1, 1, NOW(), NOW()), + (2, 64, 2, 1, NOW(), NOW()); INSERT INTO `order_status`(`id`, `code`, `created_at`, `updated_at`) VALUES (1, 'not_paid', NOW(), NOW()), diff --git a/templates/admin/default/assets/less/thelia/thelia.less b/templates/admin/default/assets/less/thelia/thelia.less index 11dc5bd74..4838ddda7 100644 --- a/templates/admin/default/assets/less/thelia/thelia.less +++ b/templates/admin/default/assets/less/thelia/thelia.less @@ -326,3 +326,79 @@ height: 30px; } } + +// -- Drag & drop -- +.take{ + + .draggable{ + border: 2px dashed @gray-light; + margin-bottom: 10px; + padding: 10px; + + &:last-child{ + margin-bottom: 0; + } + } + + .over{ + .drop-message{ + border-color: @brand-primary; + color: @brand-primary; + } + } + +} + +.place{ + + .over{ + .drop-message{ + border-color: @brand-primary; + color: @brand-primary; + } + } + + .panel-body{ + + .draggable, .drag{ + margin: 5px 0; + padding: 10px; + border: 1px dashed @gray-light; + } + + .drop-group{ + padding: 10px; + border: 2px dashed @gray-light; + margin-bottom: 10px; + + &:last-child{ + margin-bottom: 0; + } + } + + } + +} + +.take, .place{ + + .drop-message{ + width: 50%; + margin: 10px auto; + padding: 10px; + color: @gray; + border: 2px dashed @gray; + text-align: center; + .opacity(0.5); + + .glyphicon{ + display: block; + font-size: @font-size-large; + margin-bottom: 10px; + } + } + + .ui-draggable-dragging{ + z-index: 100; + } +} \ No newline at end of file diff --git a/templates/admin/default/configuration.html b/templates/admin/default/configuration.html index 50e9296ac..b5484a9af 100644 --- a/templates/admin/default/configuration.html +++ b/templates/admin/default/configuration.html @@ -58,6 +58,13 @@ {/loop} + {loop type="auth" name="pcc6" roles="ADMIN" permissions="admin.configuration.taxe-rules"} + + {intl l='Taxes rules'} + + + {/loop} + {module_include location='catalog_configuration_bottom'} diff --git a/templates/admin/default/tax-rule-edit.html b/templates/admin/default/tax-rule-edit.html new file mode 100644 index 000000000..c236e7bc4 --- /dev/null +++ b/templates/admin/default/tax-rule-edit.html @@ -0,0 +1,397 @@ +{extends file="admin-layout.tpl"} + +{block name="page-title"}{intl l='Edit a tax rule'}{/block} + +{block name="check-permissions"}admin.configuration.taxes-rules.edit{/block} + +{block name="main-content"} + +{assign oder_tab {$smarty.get.tab|default:$smarty.post.tab|default:'data'}} +{assign asked_country {$smarty.get.country|default:{country ask="default" attr="id"}}} + +
+ +
+ + + + {loop type="tax-rule" name="tax-rule" id=$tax_rule_id backend_context="1" lang=$edit_language_id} + +
+
+ + + +
+
+ +
+ + {form name="thelia.admin.taxrule.modification"} + + + + {include + file = "includes/inner-form-toolbar.html" + hide_submit_buttons = false + + page_url = {url path="/admin/configuration/taxes_rules/update/$tax_rule_id" tab=data} + close_url = {url path="/admin/configuration/taxes_rules"} + } + + {* Be sure to get the product ID, even if the form could not be validated *} + + + {form_hidden_fields form=$form} + + {form_field form=$form field='success_url'} + + {/form_field} + + {form_field form=$form field='locale'} + + {/form_field} + + {if $form_error}
{$form_error_message}
{/if} + + {form_field form=$form field='title'} +
+ + +
+ {/form_field} + + {form_field form=$form field='description'} +
+ + + +
+ {/form_field} + +
+
+
+ +
+

{intl l='Tax rule created on %date_create. Last modification: %date_change' date_create={format_date date=$CREATE_DATE} date_change={format_date date=$UPDATE_DATE}}

+
+
+
+
+ + + {/form} +
+ +
+ +
+ +
+ {intl l="Manage taxes"} +
+ +
+ +
+ + +
+
+ +

{intl l="Countries that have the same tax rule"} :

+

+ + {$matchedCountries.first=$asked_country} + + {loop type="tax-rule-country" name="same-country-list" tax_rule=$ID taxes="1,2,3" country=$asked_country} + {$matchedCountries[]=$COUNTRY} + {$COUNTRY_TITLE} + {/loop} + + {elseloop rel="same-country-list"} + {intl l="NONE"} + {/elseloop} +

+ +
+
+ +
+
+

{intl l="Manage the tax rule taxes appliance order"}

+
+
+ {assign lastPosition 0} + {loop type="tax-rule-country" name="existing-tax-list" tax_rule=$ID country=$asked_country} + {if $POSITION != $lastPosition} + {assign lastPosition $POSITION} + {if $LOOP_COUNT > 1} +
+ {/if} +
+

+ + {intl l="Add tax to this group"} +

+ {/if} + +
{$TAX_TITLE}
+ + {if $LOOP_COUNT == $LOOP_TOTAL} +
+ {/if} + {/loop} + + {elseloop rel="existing-tax-list"} +
+

+ + {intl l="Add tax to this group"} +

+
+ + {/elseloop} + +
+ +
+ + {intl l="Apply"} + +
+
+ +
+
+

Available taxes

+
+
+ {loop type="tax" name="tax-list" exclude_tax_rule=$ID country=$asked_country} +
{$TITLE}
+ {/loop} +
+ +
+ +
+
+ +
+
+ +
+
+ + {/loop} + +
+ + {* Confirmation dialog *} + {form name="thelia.admin.taxrule.taxlistupdate"} + + {if $form_error_message} + {$taxUpdateError = true} + {else} + {$taxUpdateError = false} + {/if} + + {* Capture the dialog body, to pass it to the generic dialog *} + {capture "tax_list_update_dialog"} + + + + + {form_hidden_fields form=$form} + + {form_field form=$form field='country_list'} + +

{intl l="Tax rule taxes will be update for the following countries :"}

+
+ +
+ + {/form_field} + + {/capture} + + {include + file = "includes/generic-create-dialog.html" + + dialog_id = "tax_list_update_dialog" + dialog_title = {intl l="Update tax rule taxes"} + dialog_body = {$smarty.capture.tax_list_update_dialog nofilter} + + dialog_ok_label = {intl l="Edit tax rule taxes"} + dialog_cancel_label = {intl l="Cancel"} + + form_action = {url path="/admin/configuration/taxes_rules/saveTaxes"} + form_enctype = {form_enctype form=$form} + form_error_message = $form_error_message + } + + {/form} + +{/block} + +{block name="javascript-initialization"} + + {javascripts file='assets/js/bootstrap-select/bootstrap-select.js'} + + {/javascripts} + + {javascripts file='assets/js/main.js'} + + {/javascripts} + + + + + +{/block} \ No newline at end of file diff --git a/templates/admin/default/taxes-rules.html b/templates/admin/default/taxes-rules.html new file mode 100644 index 000000000..387f70e08 --- /dev/null +++ b/templates/admin/default/taxes-rules.html @@ -0,0 +1,106 @@ +{extends file="admin-layout.tpl"} + +{block name="page-title"}{intl l='Taxes rules'}{/block} + +{block name="check-permissions"}admin.taxes-rules.view{/block} + +{block name="main-content"} +
+ +
+ + + + {module_include location='taxes_rules_top'} + +
+
+
+
+ + + + + + + + + + + + {loop type="tax-rule" name="taxes-rules"} + + + + + + + + {/loop} + + +
+ {intl l="Taxes rules"} + {loop type="auth" name="can_create" roles="ADMIN" permissions="admin.taxes-rules.create"} + + + + {/loop} +
{intl l="Name"}{intl l="Description"}{intl l="Actions"}
{$TITLE}{$DESCRIPTION} +
+ {loop type="auth" name="can_change" roles="ADMIN" permissions="admin.configuration.taxes-rules.change"} + + {/loop} + + {loop type="auth" name="can_change" roles="ADMIN" permissions="admin.configuration.taxes-rules.change"} + + {/loop} +
+
+
+
+
+
+ + {module_include location='taxes_rules_bottom'} + +
+
+ +{* -- Delete category confirmation dialog ----------------------------------- *} + +{capture "tax_rule_delete_dialog"} + + + {module_include location='tax_rule_delete_form'} + +{/capture} + +{include + file = "includes/generic-confirm-dialog.html" + + dialog_id = "tax_rule_delete_dialog" + dialog_title = {intl l="Delete tax rule"} + dialog_message = {intl l="Do you really want to delete this tax rule ?"} + + form_action = {url path='/admin/configuration/taxes_rules/delete'} + form_content = {$smarty.capture.tax_rule_delete_dialog nofilter} +} + +{/block} + +{block name="javascript-initialization"} + + + +{/block} \ No newline at end of file