diff --git a/.circleci/config.yml b/.circleci/config.yml index d9d60499..f1896f32 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -4,9 +4,15 @@ executors: php-5_6: docker: - image: circleci/php:5.6 + auth: + username: $DOCKERHUB_USERNAME + password: $DOCKERHUB_TOKEN php-7_2: docker: - image: circleci/php:7.2 + auth: + username: $DOCKERHUB_USERNAME + password: $DOCKERHUB_TOKEN jobs: tests-5_6: executor: php-5_6 @@ -42,10 +48,14 @@ workflows: ecommerce_module_core: jobs: - tests-5_6: + context: + - packlink-dockerhub filters: tags: only: /.*/ - tests-7_2: + context: + - packlink-dockerhub filters: tags: only: /.*/ diff --git a/.gitignore b/.gitignore index fe359f21..14d70a79 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ .idea/* # Custom content -vendor \ No newline at end of file +vendor diff --git a/CHANGELOG.md b/CHANGELOG.md index 5cae85ce..2921d8e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,30 +3,71 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). -## Unreleased +## [3.1.1](https://github.com/packlink-dev/ecommerce_module_core/compare/v3.1.0...v3.1.1) - 2021-03-26 ### Added - Added additional ISO codes to the postal code transformer. +- Added missing carrier logos for Italy and Spain. -## [2.3.0](https://github.com/packlink-dev/ecommerce_module_core/compare/v2.2.2...v2.3.0) - 2020-12-17 +### Changed +- Changed setting the language based on user's platform country instead of current shop language during the registration process. + +## [3.1.0](https://github.com/packlink-dev/ecommerce_module_core/compare/v3.0.6...v3.1.0) - 2020-12-11 ### Added - Added postal code transformer service that transforms postal code into supported postal code format for some countries. +- Added missing carrier logo for DPD Portugal. ### Changed +- Changed how the default parcel is validated. - Changed logic in the shipping cost calculator to use postal code transformer for the delivery postal code before retrieving services from the Packlink API. - Separated country service into two services which deal with registration and warehouse countries separately. Separated country DTO into two DTOs, with base country DTO and registration country DTO, which adds additional information (registration link and platform country). - Modified user account service, update shipping services task, and warehouse controller to work with warehouse country service instead of country service. -## [2.2.2](https://github.com/packlink-dev/ecommerce_module_core/compare/v2.2.1...v2.2.2) - 2020-02-28 +## [3.0.6](https://github.com/packlink-dev/ecommerce_module_core/compare/v3.0.5...v3.0.6) - 2020-11-05 +### Added +- Added missing carrier logos. Integrations should refresh their shipping services after updating to this Core version in order to assign these logos to their respective shipping services. +- Fixed setting warehouse postal code and city from the module + +## [3.0.5](https://github.com/packlink-dev/ecommerce_module_core/compare/v3.0.4...v3.0.5) - 2020-10-21 +### Changed +- Fix issue with execution starting logic of multiple non-recurring schedules + +## [3.0.4](https://github.com/packlink-dev/ecommerce_module_core/compare/v3.0.3...v3.0.4) - 2020-10-09 +### Changed +- Fix issue with phone validation. +- Send setup event when first service is activated. + +## [3.0.3](https://github.com/packlink-dev/ecommerce_module_core/compare/v3.0.2...v3.0.3) - 2020-09-23 +### Changed +- Ajax service request headers enhancements + +## [3.0.2](https://github.com/packlink-dev/ecommerce_module_core/compare/v3.0.1...v3.0.2) - 2020-09-10 +### Changed +- Fix get service url. +- Fix issue with adding backup service. +- Fix deserialization of Shipping Method Configuration. + +## [3.0.1](https://github.com/packlink-dev/ecommerce_module_core/compare/v3.0.0...v3.0.1) - 2020-08-31 +### Changed +- Fix origin and destination icon size on services page. +- Fix icons on the settings page. +- Fix issue with adding a query parameter to url in register controller. +- Fix translation issue in italian. + +## [3.0.0](https://github.com/packlink-dev/ecommerce_module_core/compare/v2.2.2...v3.0.0) - 2020-08-25 +### Changed +- Module redesign with new pricing policy. + +## [2.2.2](https://github.com/packlink-dev/ecommerce_module_core/compare/v2.2.1...v2.2.2) - 2020-08-28 ### Changed - Fix bug in weekly schedule for schedules setup to run on Sundays -## [2.2.1](https://github.com/packlink-dev/ecommerce_module_core/compare/v2.2.0...v2.2.1) - 2020-02-28 +## [2.2.1](https://github.com/packlink-dev/ecommerce_module_core/compare/v2.2.0...v2.2.1) - 2020-07-28 ### Changed - Prevent schedule check task from being enqueued if not necessary -## [2.2.0](https://github.com/packlink-dev/ecommerce_module_core/compare/v2.1.3...v2.2.0) - 2020-02-22 +## [2.2.0](https://github.com/packlink-dev/ecommerce_module_core/compare/v2.1.3...v2.2.0) - 2020-07-22 ### Changed -- `UpdateShipmentData` task has been declared as depricated. +- `UpdateShipmentData` task has been declared as deprecated. - `UpdateShipmentData` task will not be scheduled anymore in core. - BREAKING: Methods `isFirstShipmentDraftCreated` and `setFirstShipmentDraftCreated` have been removed from `Configuration` Integration should check if said methods have been utilized and remove them. diff --git a/README.md b/README.md index 46714b93..39718b61 100644 --- a/README.md +++ b/README.md @@ -26,3 +26,7 @@ Also, in commit dialog you must choose at least these options: - Check TODO This will also run analysis on commit but only on changed files. + +### Resource compilation +To compile resources in the root directory execute the following command: +`php cssCompile.php` diff --git a/composer.json b/composer.json index bf84f479..6142cf94 100755 --- a/composer.json +++ b/composer.json @@ -4,7 +4,8 @@ "type": "library", "license": "proprietary", "require": { - "php": ">=5.3.29" + "php": ">=5.3.29", + "ext-json": "*" }, "autoload": { "psr-4": { @@ -20,8 +21,13 @@ }, "require-dev": { "phpunit/phpunit": "^4.8", - "codacy/coverage": "dev-master" + "codacy/coverage": "dev-master", + "leafo/scssphp": "0.0.12" }, + "scripts": { + "post-update-cmd": "php cssCompile.php", + "post-install-cmd": "php cssCompile.php" + }, "config": { "platform": { "php": "5.3.29" diff --git a/composer.lock b/composer.lock index 29d46076..948e2a82 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "e23c2abc6fc4e33b3c1a1124b452c8e4", + "content-hash": "e70fd0300951d4a23480d19750532e6d", "packages": [], "packages-dev": [ { @@ -13,20 +13,21 @@ "source": { "type": "git", "url": "https://github.com/codacy/php-codacy-coverage.git", - "reference": "629d1fd597f91fb072bd822830059fd5145ce49a" + "reference": "656913b35e22ae0d1ec352bc00e3ad90616efb7a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/codacy/php-codacy-coverage/zipball/629d1fd597f91fb072bd822830059fd5145ce49a", - "reference": "629d1fd597f91fb072bd822830059fd5145ce49a", + "url": "https://api.github.com/repos/codacy/php-codacy-coverage/zipball/656913b35e22ae0d1ec352bc00e3ad90616efb7a", + "reference": "656913b35e22ae0d1ec352bc00e3ad90616efb7a", "shasum": "" }, "require": { "gitonomy/gitlib": ">=1.0", "php": ">=5.3.3", - "symfony/console": "~2.5|~3.0|~4.0" + "symfony/console": "~2.5|~3.0|~4.0|~5.0" }, "require-dev": { + "clue/phar-composer": "^1.1", "phpunit/phpunit": "~6.5" }, "bin": [ @@ -50,7 +51,8 @@ ], "description": "Sends PHP test coverage information to Codacy.", "homepage": "https://github.com/codacy/php-codacy-coverage", - "time": "2018-04-30T16:23:12+00:00" + "abandoned": true, + "time": "2020-02-11T15:55:24+00:00" }, { "name": "doctrine/instantiator", @@ -162,6 +164,53 @@ "homepage": "http://gitonomy.com", "time": "2019-06-23T09:49:01+00:00" }, + { + "name": "leafo/scssphp", + "version": "v0.0.12", + "source": { + "type": "git", + "url": "https://github.com/leafo/scssphp.git", + "reference": "ff76df3e45af45e808f3fcd516a2cb5cbc77f45e" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/leafo/scssphp/zipball/ff76df3e45af45e808f3fcd516a2cb5cbc77f45e", + "reference": "ff76df3e45af45e808f3fcd516a2cb5cbc77f45e", + "shasum": "" + }, + "require": { + "php": ">=5.2.0" + }, + "require-dev": { + "php": ">=5.3.0", + "phpunit/phpunit": "3.7.*" + }, + "bin": [ + "pscss" + ], + "type": "library", + "autoload": { + "classmap": [ + "scss.inc.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT", + "GPL-3.0" + ], + "authors": [ + { + "name": "Leaf Corcoran", + "email": "leafot@gmail.com", + "homepage": "http://leafo.net" + } + ], + "description": "scssphp is a compiler for SCSS written in PHP.", + "homepage": "http://leafo.net/scssphp/", + "abandoned": "scssphp/scssphp", + "time": "2014-07-07T01:51:39+00:00" + }, { "name": "phpdocumentor/reflection-docblock", "version": "2.0.5", @@ -213,33 +262,33 @@ }, { "name": "phpspec/prophecy", - "version": "1.8.1", + "version": "v1.10.3", "source": { "type": "git", "url": "https://github.com/phpspec/prophecy.git", - "reference": "1927e75f4ed19131ec9bcc3b002e07fb1173ee76" + "reference": "451c3cd1418cf640de218914901e51b064abb093" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpspec/prophecy/zipball/1927e75f4ed19131ec9bcc3b002e07fb1173ee76", - "reference": "1927e75f4ed19131ec9bcc3b002e07fb1173ee76", + "url": "https://api.github.com/repos/phpspec/prophecy/zipball/451c3cd1418cf640de218914901e51b064abb093", + "reference": "451c3cd1418cf640de218914901e51b064abb093", "shasum": "" }, "require": { "doctrine/instantiator": "^1.0.2", "php": "^5.3|^7.0", - "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0", - "sebastian/comparator": "^1.1|^2.0|^3.0", - "sebastian/recursion-context": "^1.0|^2.0|^3.0" + "phpdocumentor/reflection-docblock": "^2.0|^3.0.2|^4.0|^5.0", + "sebastian/comparator": "^1.2.3|^2.0|^3.0|^4.0", + "sebastian/recursion-context": "^1.0|^2.0|^3.0|^4.0" }, "require-dev": { - "phpspec/phpspec": "^2.5|^3.2", + "phpspec/phpspec": "^2.5 || ^3.2", "phpunit/phpunit": "^4.8.35 || ^5.7 || ^6.5 || ^7.1" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.8.x-dev" + "dev-master": "1.10.x-dev" } }, "autoload": { @@ -272,7 +321,7 @@ "spy", "stub" ], - "time": "2019-06-13T12:50:23+00:00" + "time": "2020-03-05T15:02:03+00:00" }, { "name": "phpunit/php-code-coverage", @@ -653,16 +702,16 @@ }, { "name": "psr/log", - "version": "1.1.0", + "version": "1.1.3", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd" + "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", - "reference": "6c001f1daafa3a3ac1d8ff69ee4db8e799a654dd", + "url": "https://api.github.com/repos/php-fig/log/zipball/0f73288fd15629204f9d42b7055f72dacbe811fc", + "reference": "0f73288fd15629204f9d42b7055f72dacbe811fc", "shasum": "" }, "require": { @@ -671,7 +720,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "1.1.x-dev" } }, "autoload": { @@ -696,7 +745,7 @@ "psr", "psr-3" ], - "time": "2018-11-20T15:27:04+00:00" + "time": "2020-03-23T09:12:05+00:00" }, { "name": "sebastian/comparator", @@ -1072,7 +1121,7 @@ }, { "name": "symfony/console", - "version": "v2.8.50", + "version": "v2.8.52", "source": { "type": "git", "url": "https://github.com/symfony/console.git", @@ -1129,11 +1178,11 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", - "time": "2019-06-05T11:33:52+00:00" + "time": "2018-11-20T15:55:20+00:00" }, { "name": "symfony/debug", - "version": "v2.8.50", + "version": "v2.8.52", "source": { "type": "git", "url": "https://github.com/symfony/debug.git", @@ -1186,20 +1235,20 @@ ], "description": "Symfony Debug Component", "homepage": "https://symfony.com", - "time": "2019-06-18T21:26:03+00:00" + "time": "2018-11-11T11:18:13+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.11.0", + "version": "v1.17.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "82ebae02209c21113908c229e9883c419720738a" + "reference": "e94c8b1bbe2bc77507a1056cdb06451c75b427f9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/82ebae02209c21113908c229e9883c419720738a", - "reference": "82ebae02209c21113908c229e9883c419720738a", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/e94c8b1bbe2bc77507a1056cdb06451c75b427f9", + "reference": "e94c8b1bbe2bc77507a1056cdb06451c75b427f9", "shasum": "" }, "require": { @@ -1211,7 +1260,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.11-dev" + "dev-master": "1.17-dev" } }, "autoload": { @@ -1227,13 +1276,13 @@ "MIT" ], "authors": [ - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - }, { "name": "Gert de Pagter", "email": "BackEndTea@gmail.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" } ], "description": "Symfony polyfill for ctype functions", @@ -1244,20 +1293,20 @@ "polyfill", "portable" ], - "time": "2019-02-06T07:57:58+00:00" + "time": "2020-05-12T16:14:59+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.11.0", + "version": "v1.17.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "fe5e94c604826c35a32fa832f35bd036b6799609" + "reference": "fa79b11539418b02fc5e1897267673ba2c19419c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fe5e94c604826c35a32fa832f35bd036b6799609", - "reference": "fe5e94c604826c35a32fa832f35bd036b6799609", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/fa79b11539418b02fc5e1897267673ba2c19419c", + "reference": "fa79b11539418b02fc5e1897267673ba2c19419c", "shasum": "" }, "require": { @@ -1269,7 +1318,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "1.11-dev" + "dev-master": "1.17-dev" } }, "autoload": { @@ -1303,11 +1352,11 @@ "portable", "shim" ], - "time": "2019-02-06T07:57:58+00:00" + "time": "2020-05-12T16:47:27+00:00" }, { "name": "symfony/process", - "version": "v2.8.50", + "version": "v2.8.52", "source": { "type": "git", "url": "https://github.com/symfony/process.git", @@ -1352,11 +1401,11 @@ ], "description": "Symfony Process Component", "homepage": "https://symfony.com", - "time": "2019-05-30T15:47:52+00:00" + "time": "2018-11-11T11:18:13+00:00" }, { "name": "symfony/yaml", - "version": "v2.8.50", + "version": "v2.8.52", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", @@ -1413,7 +1462,8 @@ "prefer-stable": false, "prefer-lowest": false, "platform": { - "php": ">=5.3" + "php": ">=5.3.29", + "ext-json": "*" }, "platform-dev": [], "platform-overrides": { diff --git a/cssCompile.php b/cssCompile.php new file mode 100644 index 00000000..72c60349 --- /dev/null +++ b/cssCompile.php @@ -0,0 +1,36 @@ +setImportPaths('src/BusinessLogic/Resources/scss/'); + $scss->setFormatter('scss_formatter'); + + createDir($dst); + + $dstFileName = $dst . '/app.css'; + file_put_contents($dstFileName, ''); + + $compiledCss = $scss->compile(file_get_contents("{$src}/app.scss")); + $existingContent = file_get_contents($dstFileName); + file_put_contents($dstFileName, $existingContent . $compiledCss); +} + +/** + * Creates directory. + * + * @param string $destination + */ +function createDir($destination) +{ + if (!file_exists($destination) && !mkdir($destination) && !is_dir($destination)) { + throw new RuntimeException(sprintf('Directory "%s" was not created', $destination)); + } +} diff --git a/phpunit.xml b/phpunit.xml index 0824c83c..e091d15c 100755 --- a/phpunit.xml +++ b/phpunit.xml @@ -10,7 +10,11 @@ - ./src + ./src/BusinessLogic + ./src/Infrastructure + + ./src/DemoUI + diff --git a/src/BusinessLogic/BootstrapComponent.php b/src/BusinessLogic/BootstrapComponent.php index 50798b72..4fb94eaa 100644 --- a/src/BusinessLogic/BootstrapComponent.php +++ b/src/BusinessLogic/BootstrapComponent.php @@ -2,6 +2,7 @@ namespace Packlink\BusinessLogic; +use Logeecom\Infrastructure\AutoTest\AutoTestService; use Logeecom\Infrastructure\Http\HttpClient; use Logeecom\Infrastructure\ServiceRegister; use Logeecom\Infrastructure\TaskExecution\TaskEvents\TickEvent; @@ -17,6 +18,8 @@ use Packlink\BusinessLogic\DTO\ValidationError; use Packlink\BusinessLogic\Http\DTO\ParcelInfo; use Packlink\BusinessLogic\Http\Proxy; +use Packlink\BusinessLogic\Language\Interfaces\TranslationService as TranslationServiceInterface; +use Packlink\BusinessLogic\Language\TranslationService; use Packlink\BusinessLogic\Location\LocationService; use Packlink\BusinessLogic\Order\OrderService; use Packlink\BusinessLogic\OrderShipmentDetails\OrderShipmentDetailsService; @@ -26,6 +29,7 @@ use Packlink\BusinessLogic\Scheduler\ScheduleTickHandler; use Packlink\BusinessLogic\ShipmentDraft\OrderSendDraftTaskMapService; use Packlink\BusinessLogic\ShipmentDraft\ShipmentDraftService; +use Packlink\BusinessLogic\ShippingMethod\Models\ShippingPricePolicy; use Packlink\BusinessLogic\ShippingMethod\PackageTransformer; use Packlink\BusinessLogic\ShippingMethod\ShippingMethodService; use Packlink\BusinessLogic\User\UserAccountService; @@ -165,6 +169,20 @@ function () { return RegistrationService::getInstance(); } ); + + ServiceRegister::registerService( + TranslationServiceInterface::CLASS_NAME, + function () { + return new TranslationService(); + } + ); + + ServiceRegister::registerService( + AutoTestService::CLASS_NAME, + function () { + return new AutoTestService(); + } + ); } /** @@ -202,5 +220,6 @@ protected static function initDtoRegistry() FrontDtoFactory::register(RegistrationCountry::CLASS_KEY, RegistrationCountry::CLASS_NAME); FrontDtoFactory::register(RegistrationRequest::CLASS_KEY, RegistrationRequest::CLASS_NAME); FrontDtoFactory::register(RegistrationLegalPolicy::CLASS_KEY, RegistrationLegalPolicy::CLASS_NAME); + FrontDtoFactory::register(ShippingPricePolicy::CLASS_KEY, ShippingPricePolicy::CLASS_NAME); } } diff --git a/src/BusinessLogic/Controllers/AutoTestController.php b/src/BusinessLogic/Controllers/AutoTestController.php new file mode 100644 index 00000000..e9d8a176 --- /dev/null +++ b/src/BusinessLogic/Controllers/AutoTestController.php @@ -0,0 +1,91 @@ +service = ServiceRegister::getService(AutoTestService::CLASS_NAME); + } + + /** + * Starts autotest. + * + * @return array + */ + public function start() + { + try { + $status = array('success' => true, 'itemId' => $this->service->startAutoTest()); + } catch (Exception $e) { + $status = array('success' => false, 'error' => $e->getMessage()); + } + + return $status; + } + + /** + * Stops autotest mode. + * + * @param callable $loggerInitializer Method that will be used to re-register proper shop logger service. + */ + public function stop(callable $loggerInitializer) + { + $this->service->stopAutoTestMode($loggerInitializer); + } + + /** + * Retrieves autotest status. + * + * @param int $id Auto test task id. + * + * @return array + * + * @throws \Logeecom\Infrastructure\ORM\Exceptions\QueryFilterInvalidParamException + * @throws \Logeecom\Infrastructure\ORM\Exceptions\RepositoryClassException + * @throws \Logeecom\Infrastructure\ORM\Exceptions\RepositoryNotRegisteredException + */ + public function checkStatus($id) + { + $status = $this->service->getAutoTestTaskStatus($id); + + return array( + 'finished' => $status->finished, + 'error' => $status->error, + 'logs' => AutoTestLogger::getInstance()->getLogsArray(), + ); + } + + /** + * Retrieves auto test logs. + * + * @return array + * + * @throws \Logeecom\Infrastructure\ORM\Exceptions\RepositoryNotRegisteredException + */ + public function getLogs() + { + return AutoTestLogger::getInstance()->getLogsArray(); + } +} \ No newline at end of file diff --git a/src/BusinessLogic/Controllers/ConfigurationController.php b/src/BusinessLogic/Controllers/ConfigurationController.php new file mode 100644 index 00000000..97266643 --- /dev/null +++ b/src/BusinessLogic/Controllers/ConfigurationController.php @@ -0,0 +1,40 @@ + 'https://support-pro.packlink.com/hc/en-gb', + 'ES' => 'https://support-pro.packlink.com/hc/es-es', + 'DE' => 'https://support-pro.packlink.com/hc/de', + 'FR' => 'https://support-pro.packlink.com/hc/fr-fr', + 'IT' => 'https://support-pro.packlink.com/hc/it', + ); + + /** + * @return mixed|string + */ + public function getHelpLink() + { + $lang = UrlService::getUrlLocaleKey(); + + if (!array_key_exists($lang, static::$helpUrls)) { + $lang = 'EN'; + } + + return static::$helpUrls[$lang]; + } +} diff --git a/src/BusinessLogic/Controllers/DTO/ModuleState.php b/src/BusinessLogic/Controllers/DTO/ModuleState.php new file mode 100644 index 00000000..32c3f0c6 --- /dev/null +++ b/src/BusinessLogic/Controllers/DTO/ModuleState.php @@ -0,0 +1,47 @@ + $this->id, 'name' => $this->name, - 'pricePolicy' => $this->pricePolicy, 'showLogo' => $this->showLogo, 'taxClass' => $this->taxClass, 'isShipToAllCountries' => $this->isShipToAllCountries, 'shippingCountries' => $this->shippingCountries, + 'usePacklinkPriceIfNotInRange' => $this->usePacklinkPriceIfNotInRange, + 'pricingPolicies' => array(), + 'activated' => $this->activated, ); - if ($this->pricePolicy === ShippingMethod::PRICING_POLICY_PERCENT && $this->percentPricePolicy) { - $result['percentPricePolicy'] = $this->percentPricePolicy->toArray(); - } - - if ($this->pricePolicy === ShippingMethod::PRICING_POLICY_FIXED_PRICE_BY_WEIGHT - && $this->fixedPriceByWeightPolicy - ) { - $this->setFixedPricePolicyToArray($result, 'fixedPriceByWeightPolicy'); - } - - if ($this->pricePolicy === ShippingMethod::PRICING_POLICY_FIXED_PRICE_BY_VALUE - && $this->fixedPriceByValuePolicy - ) { - $this->setFixedPricePolicyToArray($result, 'fixedPriceByValuePolicy'); + if ($this->pricingPolicies) { + foreach ($this->pricingPolicies as $policy) { + $result['pricingPolicies'][] = $policy->toArray(); + } } return $result; @@ -117,17 +101,18 @@ public function toArray() * @param array $raw * * @return ShippingMethodConfiguration + * @throws \Packlink\BusinessLogic\DTO\Exceptions\FrontDtoValidationException */ public static function fromArray(array $raw) { $result = new static(); $result->id = $raw['id']; + $result->activated = (bool)static::getDataValue($raw, 'activated', false); $result->name = $raw['name']; $result->showLogo = $raw['showLogo']; - $result->pricePolicy = $raw['pricePolicy']; - $result->taxClass = isset($raw['taxClass']) ? $raw['taxClass'] : null; + $result->usePacklinkPriceIfNotInRange = (bool)static::getDataValue($raw, 'usePacklinkPriceIfNotInRange', false); if (isset($raw['isShipToAllCountries']) && is_bool($raw['isShipToAllCountries'])) { $result->isShipToAllCountries = $raw['isShipToAllCountries']; @@ -141,53 +126,14 @@ public static function fromArray(array $raw) $result->shippingCountries = array(); } - if ($result->pricePolicy === ShippingMethod::PRICING_POLICY_PERCENT) { - $value = $raw['percentPricePolicy']; - $result->percentPricePolicy = PercentPricePolicy::fromArray($value); - } - - if ($result->pricePolicy === ShippingMethod::PRICING_POLICY_FIXED_PRICE_BY_WEIGHT) { - self::setFixedPricingPolicyFromArray($result, $raw, 'fixedPriceByWeightPolicy'); - } - - if ($result->pricePolicy === ShippingMethod::PRICING_POLICY_FIXED_PRICE_BY_VALUE) { - self::setFixedPricingPolicyFromArray($result, $raw, 'fixedPriceByValuePolicy'); - } - - return $result; - } - - /** - * Transforms fixed price policy to array and sets it to the given array. - * - * @param array $result Resulting array - * @param string $type Type of the pricing policy - */ - protected function setFixedPricePolicyToArray(array &$result, $type) - { - $result[$type] = array(); - /** @var FixedPricePolicy $item */ - foreach ($this->$type as $item) { - $result[$type][] = $item->toArray(); - } - } - - /** - * Transforms fixed price policy from array and sets it to the given instance. - * - * @param static $result - * @param array $raw - * @param string $type - */ - protected static function setFixedPricingPolicyFromArray($result, array $raw, $type) - { - $values = array(); - if (array_key_exists($type, $raw)) { - foreach ($raw[$type] as $policy) { - $values[] = FixedPricePolicy::fromArray($policy); + if (!empty($raw['pricingPolicies']) && is_array($raw['pricingPolicies'])) { + foreach ($raw['pricingPolicies'] as $policy) { + $result->pricingPolicies[] = ShippingPricePolicy::fromArray($policy); } + } else { + $result->pricingPolicies = array(); } - $result->$type = $values; + return $result; } } diff --git a/src/BusinessLogic/Controllers/DTO/ShippingMethodResponse.php b/src/BusinessLogic/Controllers/DTO/ShippingMethodResponse.php index b5158dda..3150ca6a 100644 --- a/src/BusinessLogic/Controllers/DTO/ShippingMethodResponse.php +++ b/src/BusinessLogic/Controllers/DTO/ShippingMethodResponse.php @@ -10,11 +10,11 @@ class ShippingMethodResponse extends ShippingMethodConfiguration { /** - * Shipping method title. + * Shipping method type (national/international). * * @var string */ - public $title; + public $type; /** * Shipping carrier name. * @@ -51,12 +51,6 @@ class ShippingMethodResponse extends ShippingMethodConfiguration * @var string */ public $parcelDestination; - /** - * Selected flag. - * - * @var bool - */ - public $selected = false; /** * Transforms DTO to its array format suitable for http client. @@ -68,14 +62,13 @@ public function toArray() return array_merge( parent::toArray(), array( - 'title' => $this->title, + 'type' => $this->type, 'carrierName' => $this->carrierName, 'deliveryDescription' => $this->deliveryDescription, 'deliveryType' => $this->deliveryType, 'parcelOrigin' => $this->parcelOrigin, 'parcelDestination' => $this->parcelDestination, 'logoUrl' => $this->logoUrl, - 'selected' => $this->selected, ) ); } diff --git a/src/BusinessLogic/Controllers/DebugController.php b/src/BusinessLogic/Controllers/DebugController.php new file mode 100644 index 00000000..3a171d47 --- /dev/null +++ b/src/BusinessLogic/Controllers/DebugController.php @@ -0,0 +1,186 @@ +configService = ServiceRegister::getService(Configuration::CLASS_NAME); + } + + /** + * Returns debug mode status. + * + * @return bool + */ + public function getStatus() + { + return $this->configService->isDebugModeEnabled(); + } + + /** + * Sets debug mode status. + * + * @param bool $status New debug status. + */ + public function setStatus($status) + { + $this->configService->setDebugModeEnabled($status); + } + + /** + * Returns system info file. + * + * @throws \Exception + */ + public function getSystemInfo() + { + if (!defined('JSON_PRETTY_PRINT')) { + define('JSON_PRETTY_PRINT', 128); + } + + if (!defined('JSON_UNESCAPED_SLASHES')) { + define('JSON_UNESCAPED_SLASHES', 64); + } + + $file = tempnam(sys_get_temp_dir(), 'packlink_system_info'); + + $zip = new \ZipArchive(); + $zip->open($file, \ZipArchive::CREATE); + + $zip->addFromString(static::USER_INFO_FILE_NAME, $this->getUserSettings()); + $zip->addFromString(static::QUEUE_INFO_FILE_NAME, $this->getQueue()); + $zip->addFromString(static::SERVICE_INFO_FILE_NAME, $this->getServicesInfo()); + + $this->getIntegrationInfo($zip); + + $zip->close(); + + return $file; + } + + /** + * Returns integration specific information. + * An extension point for integrations to add more data. + * + * @param \ZipArchive $zip + */ + protected function getIntegrationInfo(\ZipArchive $zip) + { + } + + /** + * Returns parcel and warehouse information. + * + * @return string + */ + protected function getUserSettings() + { + $result = array(); + /** @noinspection NullPointerExceptionInspection */ + $result['User'] = $this->configService->getUserInfo()->toArray(); + $result['User']['API key'] = $this->configService->getAuthorizationToken(); + $result['Parcel'] = $this->configService->getDefaultParcel() ?: array(); + $result['Warehouse'] = $this->configService->getDefaultWarehouse() ?: array(); + $result['Order Status Mappings'] = $this->configService->getOrderStatusMappings() ?: array(); + + return $this->jsonEncode($result); + } + + /** + * Returns service info. + * + * @return string + */ + protected function getServicesInfo() + { + $result = array(); + + try { + $repository = RepositoryRegistry::getRepository(RepositoryInterface::CLASS_NAME); + $result = $repository->select(); + } catch (RepositoryNotRegisteredException $e) { + } + + return $this->formatJsonOutput($result); + } + + /** + * Returns current queue for current tenant. + * + * @return string + */ + protected function getQueue() + { + $result = array(); + + try { + $repository = RepositoryRegistry::getQueueItemRepository(); + + $query = new QueryFilter(); + $query->where('context', Operators::EQUALS, $this->configService->getContext()); + + $result = $repository->select($query); + } catch (RepositoryNotRegisteredException $e) { + } catch (QueryFilterInvalidParamException $e) { + } catch (RepositoryClassException $e) { + } + + return $this->formatJsonOutput($result); + } + + /** + * Encodes the given data. + * + * @param $data + * + * @return string + */ + protected function jsonEncode($data) + { + return json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); + } + + /** + * Formats json output. + * + * @param \Logeecom\Infrastructure\ORM\Entity[] $items Entities. + * + * @return string + */ + protected function formatJsonOutput(array $items) + { + $response = array(); + foreach ($items as $item) { + $response[] = $item->toArray(); + } + + return $this->jsonEncode($response); + } +} diff --git a/src/BusinessLogic/Controllers/DefaultParcelController.php b/src/BusinessLogic/Controllers/DefaultParcelController.php new file mode 100644 index 00000000..96963a96 --- /dev/null +++ b/src/BusinessLogic/Controllers/DefaultParcelController.php @@ -0,0 +1,71 @@ +getConfigService()->getDefaultParcel(); + } + + /** + * Sets default parcel and enqueues the Update shipping services task if needed. + * + * @param array $rawData + * + * @throws \Logeecom\Infrastructure\TaskExecution\Exceptions\QueueStorageUnavailableException + * @throws \Packlink\BusinessLogic\DTO\Exceptions\FrontDtoValidationException + */ + public function setDefaultParcel(array $rawData) + { + $rawData['default'] = true; + $oldParcel = $this->getConfigService()->getDefaultParcel(); + + $parcelInfo = ParcelInfo::fromArray($rawData); + $this->getConfigService()->setDefaultParcel($parcelInfo); + + if ($oldParcel === null || array_diff($oldParcel->toArray(), $parcelInfo->toArray())) { + /** @var QueueService $queueService */ + $queueService = ServiceRegister::getService(QueueService::CLASS_NAME); + $defaultQueueName = $this->getConfigService()->getDefaultQueueName(); + + $queueService->enqueue( + $defaultQueueName, + new UpdateShippingServicesTask(), + $this->getConfigService()->getContext() + ); + } + } + + /** + * Returns an instance of configuration service. + * + * @return ConfigurationService + */ + private function getConfigService() + { + if ($this->configService === null) { + $this->configService = ServiceRegister::getService(Configuration::CLASS_NAME); + } + + return $this->configService; + } +} diff --git a/src/BusinessLogic/Controllers/LocationsController.php b/src/BusinessLogic/Controllers/LocationsController.php new file mode 100644 index 00000000..4ec5f724 --- /dev/null +++ b/src/BusinessLogic/Controllers/LocationsController.php @@ -0,0 +1,55 @@ +service = ServiceRegister::getService(LocationService::CLASS_NAME); + } + + /** + * Retrieves locations. + * + * @param array $query Associative array in the following format ['query' => '...', 'country' => '...']. + * + * @return \Packlink\BusinessLogic\Http\DTO\LocationInfo[] + */ + public function searchLocations(array $query) + { + if (empty($query['query']) || empty($query['country'])) { + return array(); + } + + try { + $result = $this->service->searchLocations($query['country'], $query['query']); + } catch (Exception $e) { + Logger::logError('Location search failed.', 'Core', $e->getTrace()); + + $result = array(); + } + + return $result; + } +} \ No newline at end of file diff --git a/src/BusinessLogic/Controllers/LoginController.php b/src/BusinessLogic/Controllers/LoginController.php new file mode 100644 index 00000000..1a4649f5 --- /dev/null +++ b/src/BusinessLogic/Controllers/LoginController.php @@ -0,0 +1,42 @@ +login($apiKey); + } catch (Exception $e) { + /** @var ConfigurationService $configService */ + $configService = ServiceRegister::getService(Configuration::CLASS_NAME); + if ($configService->getAuthorizationToken() !== null) { + $configService->setAuthorizationToken(null); + } + } + + return $result; + } +} \ No newline at end of file diff --git a/src/BusinessLogic/Controllers/ModuleStateController.php b/src/BusinessLogic/Controllers/ModuleStateController.php new file mode 100644 index 00000000..4042670c --- /dev/null +++ b/src/BusinessLogic/Controllers/ModuleStateController.php @@ -0,0 +1,51 @@ +getAuthorizationToken(); + + /** @var ParcelInfo|null $defaultParcel */ + $defaultParcel = $configService->getDefaultParcel(); + + /** @var Warehouse|null $defaultWarehouse */ + $defaultWarehouse = $configService->getDefaultWarehouse(); + + $result = new ModuleState(); + + if (empty($apiToken)) { + $result->state = ModuleState::LOGIN_STATE; + + } else if ($defaultParcel === null || $defaultWarehouse === null) { + $result->state = ModuleState::ONBOARDING_STATE; + + } else { + $result->state = ModuleState::SERVICES_STATE; + } + + return $result; + } +} diff --git a/src/BusinessLogic/Controllers/OnboardingController.php b/src/BusinessLogic/Controllers/OnboardingController.php new file mode 100644 index 00000000..b0523d4b --- /dev/null +++ b/src/BusinessLogic/Controllers/OnboardingController.php @@ -0,0 +1,35 @@ +getDefaultParcel(); + $warehouse = $configService->getDefaultWarehouse(); + + if ($parcel === null && $warehouse === null) { + $result->state = OnboardingState::WELCOME_STATE; + + } else { + $result->state = OnboardingState::OVERVIEW_STATE; + } + + return $result; + } +} diff --git a/src/BusinessLogic/Controllers/OrderStatusMappingController.php b/src/BusinessLogic/Controllers/OrderStatusMappingController.php new file mode 100644 index 00000000..aed08e94 --- /dev/null +++ b/src/BusinessLogic/Controllers/OrderStatusMappingController.php @@ -0,0 +1,62 @@ +getOrderStatusMappings(); + + if (empty($mappings)) { + foreach ($this->getPacklinkStatuses() as $status => $label) { + $mappings[$status] = ''; + } + } + + return $mappings; + } + + /** + * Returns Packlink shipment statuses. + * + * @return array Packlink shipment statuses + */ + public function getPacklinkStatuses() + { + $result = array(); + foreach (ShipmentStatus::getPossibleStatuses() as $status) { + $result[$status] = Translator::translate('orderStatusMapping.' . $status); + } + + return $result; + } + + /** + * Saves order status mappings. + * + * @param array $data + */ + public function setMappings($data) + { + /** @var \Packlink\BusinessLogic\Configuration $configService */ + $configService = ServiceRegister::getService(Configuration::CLASS_NAME); + $configService->setOrderStatusMappings($data); + } +} diff --git a/src/BusinessLogic/Controllers/RegistrationController.php b/src/BusinessLogic/Controllers/RegistrationController.php new file mode 100644 index 00000000..89216b05 --- /dev/null +++ b/src/BusinessLogic/Controllers/RegistrationController.php @@ -0,0 +1,179 @@ + 'https://support-pro.packlink.com/hc/en-gb/articles/360010011480', + 'ES' => 'https://pro.packlink.es/terminos-y-condiciones/', + 'DE' => 'https://pro.packlink.de/agb/', + 'FR' => 'https://pro.packlink.fr/conditions-generales/', + 'IT' => 'https://pro.packlink.it/termini-condizioni/', + 'AT' => 'https://support-pro.packlink.com/hc/de/articles/360010011480', + 'NL' => 'https://support-pro.packlink.com/hc/nl/articles/360010011480', + 'BE' => 'https://support-pro.packlink.com/hc/nl/articles/360010011480', + 'PT' => 'https://support-pro.packlink.com/hc/pt/articles/360010011480', + 'TR' => 'https://support-pro.packlink.com/hc/tr/articles/360010011480', + 'IE' => 'https://support-pro.packlink.com/hc/en-gb/articles/360010011480', + 'GB' => 'https://support-pro.packlink.com/hc/en-gb/articles/360010011480', + 'HU' => 'https://support-pro.packlink.com/hc/hu/articles/360010011480', + ); + /** + * List of terms and conditions URLs for different country codes. + * + * @var array + */ + private static $privacyPolicyUrls = array( + 'EN' => 'https://support-pro.packlink.com/hc/en-gb/articles/360010011560', + 'ES' => 'https://support-pro.packlink.com/hc/es-es/articles/360010011560-Pol%C3%ADtica-de-Privacidad', + 'DE' => 'https://support-pro.packlink.com/hc/de/articles/360010011560-Datenschutzerkl%C3%A4rung-der-Packlink-Shipping-S-L-', + 'FR' => 'https://support-pro.packlink.com/hc/fr-fr/articles/360010011560-Politique-de-confidentialit%C3%A9', + 'IT' => 'https://support-pro.packlink.com/hc/it/articles/360010011560-Politica-di-Privacy', + 'AT' => 'https://support-pro.packlink.com/hc/de/articles/360010011480', + 'NL' => 'https://support-pro.packlink.com/hc/nl/articles/360010011560', + 'BE' => 'https://support-pro.packlink.com/hc/nl/articles/360010011560', + 'PT' => 'https://support-pro.packlink.com/hc/pt/articles/360010011560', + 'TR' => 'https://support-pro.packlink.com/hc/tr/articles/360010011560', + 'IE' => 'https://support-pro.packlink.com/hc/en-gb/articles/360010011560', + 'GB' => 'https://support-pro.packlink.com/hc/en-gb/articles/360010011560', + 'HU' => 'https://support-pro.packlink.com/hc/hu/articles/360010011560', + ); + + /** + * Gets the data needed for a registration page. + * + * @param string $country + * + * @return array + */ + public function getRegisterData($country) + { + /** @var RegistrationInfoService $registrationInfoService */ + $registrationInfoService = ServiceRegister::getService(RegistrationInfoService::CLASS_NAME); + $registrationData = $registrationInfoService->getRegistrationInfoData(); + + return array( + 'context' => $this->getConfigService()->getContext(), + 'email' => $registrationData->getEmail(), + 'phone' => $registrationData->getPhone(), + 'source' => $registrationData->getSource(), + 'termsAndConditionsUrl' => !empty(self::$termsAndConditionsUrls[$country]) ? + self::$termsAndConditionsUrls[$country] : self::$termsAndConditionsUrls[self::DEFAULT_COUNTRY], + 'privacyPolicyUrl' => !empty(self::$privacyPolicyUrls[$country]) ? + self::$privacyPolicyUrls[$country] : self::$privacyPolicyUrls[self::DEFAULT_COUNTRY], + ); + } + + /** + * Registers the user to the Packlink system. + * + * @param array $payload + * + * @return bool A flag indicating whether the registration was successful. + * + * @throws \Logeecom\Infrastructure\ORM\Exceptions\RepositoryNotRegisteredException + * @throws \Logeecom\Infrastructure\TaskExecution\Exceptions\QueueStorageUnavailableException + * @throws \Packlink\BusinessLogic\DTO\Exceptions\FrontDtoNotRegisteredException + * @throws \Packlink\BusinessLogic\DTO\Exceptions\FrontDtoValidationException + * @throws \Packlink\BusinessLogic\Registration\Exceptions\UnableToRegisterAccountException + */ + public function register(array $payload) + { + $payload['platform'] = 'PRO'; + $payload['language'] = $this->getLanguage($payload['platform_country']); + + if (isset($payload['source'])) { + $payload['source'] = 'https://' . str_replace(array('http://', 'https://'), '', $payload['source']); + } + + $acceptedTermsAndConditions = isset($payload['terms_and_conditions']) && $payload['terms_and_conditions']; + $acceptedMarketingEmails = isset($payload['marketing_emails']) && $payload['marketing_emails']; + + $payload['policies'] = array( + 'terms_and_conditions' => $acceptedTermsAndConditions, + 'data_processing' => $acceptedTermsAndConditions, + 'marketing_emails' => $acceptedMarketingEmails, + 'marketing_calls' => $acceptedMarketingEmails, + ); + + /** @var RegistrationRequest $request */ + $registrationRequest = FrontDtoFactory::get(RegistrationRequest::CLASS_KEY, $payload); + + /** @var RegistrationService $registrationService */ + $registrationService = ServiceRegister::getService(RegistrationService::CLASS_NAME); + + /** @noinspection PhpParamsInspection */ + $token = $registrationService->register($registrationRequest); + + if (!empty($token)) { + /** @var UserAccountService $userAccountService */ + $userAccountService = ServiceRegister::getService(UserAccountService::CLASS_NAME); + if ($userAccountService->login($token)) { + return true; + } + } + + return false; + } + + /** + * Returns an instance of configuration service. + * + * @return Configuration + */ + protected function getConfigService() + { + if ($this->configService === null) { + $this->configService = ServiceRegister::getService(Configuration::CLASS_NAME); + } + + return $this->configService; + } + + /** + * Returns shop language in format which Packlink expects, based on user's platform country. + * + * @param string $platformCountry + * + * @return string + */ + private function getLanguage($platformCountry) + { + $supportedLanguages = array( + 'ES' => 'es_ES', + 'DE' => 'de_DE', + 'FR' => 'fr_FR', + 'IT' => 'it_IT', + ); + + $language = 'en_GB'; + + if (array_key_exists($platformCountry, $supportedLanguages)) { + $language = $supportedLanguages[$platformCountry]; + } + + return $language; + } +} diff --git a/src/BusinessLogic/Controllers/RegistrationRegionsController.php b/src/BusinessLogic/Controllers/RegistrationRegionsController.php new file mode 100644 index 00000000..bcda6bec --- /dev/null +++ b/src/BusinessLogic/Controllers/RegistrationRegionsController.php @@ -0,0 +1,34 @@ +service = ServiceRegister::getService(CountryService::CLASS_NAME); + } + + public function getRegions() + { + return $this->service->getSupportedCountries(false); + } +} \ No newline at end of file diff --git a/src/BusinessLogic/Controllers/ShippingMethodController.php b/src/BusinessLogic/Controllers/ShippingMethodController.php index b2fa6765..8219e7ca 100644 --- a/src/BusinessLogic/Controllers/ShippingMethodController.php +++ b/src/BusinessLogic/Controllers/ShippingMethodController.php @@ -2,10 +2,13 @@ namespace Packlink\BusinessLogic\Controllers; +use Exception; use Logeecom\Infrastructure\Logger\Logger; use Logeecom\Infrastructure\ServiceRegister; use Packlink\BusinessLogic\Controllers\DTO\ShippingMethodConfiguration; use Packlink\BusinessLogic\Controllers\DTO\ShippingMethodResponse; +use Packlink\BusinessLogic\Language\Translator; +use Packlink\BusinessLogic\ShippingMethod\Interfaces\ShopShippingMethodService; use Packlink\BusinessLogic\ShippingMethod\Models\ShippingMethod; use Packlink\BusinessLogic\ShippingMethod\ShippingMethodService; @@ -28,10 +31,14 @@ class ShippingMethodController * Pickup constant */ const PICKUP = 'pickup'; + /** + * Collection constant + */ + const COLLECTION = 'collection'; /** * Home constant */ - const HOME = 'home'; + const DELIVERY = 'delivery'; /** * Shipping type: national */ @@ -48,23 +55,18 @@ class ShippingMethodController * Shipping delivery type: economic */ const ECONOMIC = 'economic'; - /** - * Allowed policies. - * - * @var array - */ - private static $policies = array( - ShippingMethod::PRICING_POLICY_PACKLINK, - ShippingMethod::PRICING_POLICY_PERCENT, - ShippingMethod::PRICING_POLICY_FIXED_PRICE_BY_WEIGHT, - ShippingMethod::PRICING_POLICY_FIXED_PRICE_BY_VALUE, - ); /** * Shipping method service. * * @var ShippingMethodService */ private $shippingMethodService; + /** + * Shop shipping method service. + * + * @var \Packlink\BusinessLogic\ShippingMethod\Interfaces\ShopShippingMethodService + */ + private $shopShippingService; /** * DashboardController constructor. @@ -72,6 +74,7 @@ class ShippingMethodController public function __construct() { $this->shippingMethodService = ServiceRegister::getService(ShippingMethodService::CLASS_NAME); + $this->shopShippingService = ServiceRegister::getService(ShopShippingMethodService::CLASS_NAME); } /** @@ -81,13 +84,46 @@ public function __construct() */ public function getAll() { - $all = $this->shippingMethodService->getAllMethods(); - $result = array(); - foreach ($all as $item) { - $result[] = $this->transformShippingMethodModelToDto($item); + return $this->getResponse($this->shippingMethodService->getAllMethods()); + } + + /** + * Returns all shipping methods. + * + * @return ShippingMethodResponse[] Array of shipping methods. + */ + public function getActive() + { + return $this->getResponse($this->shippingMethodService->getActiveMethods()); + } + + /** + * Returns all shipping methods. + * + * @return ShippingMethodResponse[] Array of shipping methods. + */ + public function getInactive() + { + return $this->getResponse($this->shippingMethodService->getInactiveMethods()); + } + + /** + * Returns shipping method with the given ID. + * + * @param int $id Shipping method ID. + * + * @return ShippingMethodResponse|null Shipping method. + */ + public function getShippingMethod($id) + { + $model = $this->shippingMethodService->getShippingMethod($id); + if (!$model) { + Logger::logWarning("Shipping method with id {$id} not found!"); + + return null; } - return $result; + return $this->transformShippingMethodModelToDto($model); } /** @@ -99,10 +135,6 @@ public function getAll() */ public function save(ShippingMethodConfiguration $shippingMethod) { - if (!$this->isValid($shippingMethod)) { - return null; - } - $model = $this->shippingMethodService->getShippingMethod($shippingMethod->id); if (!$model) { Logger::logError("Shipping method with id {$shippingMethod->id} not found!"); @@ -111,16 +143,24 @@ public function save(ShippingMethodConfiguration $shippingMethod) } try { + $isFirstServiceActivated = $shippingMethod->activated && !$this->shippingMethodService->isAnyMethodActive(); + $this->updateModelData($shippingMethod, $model); $this->shippingMethodService->save($model); - return $this->transformShippingMethodModelToDto($model); - } catch (\Exception $e) { + $result = $this->transformShippingMethodModelToDto($model); + + if ($isFirstServiceActivated) { + AnalyticsController::sendSetupEvent(); + $this->shopShippingService->addBackupShippingMethod(ShippingMethod::fromArray($model->toArray())); + } + + return $result; + } catch (Exception $e) { Logger::logError($e->getMessage(), 'Core', $shippingMethod->toArray()); - $result = null; } - return $result; + return null; } /** @@ -153,6 +193,23 @@ public function deactivate($id) return $this->shippingMethodService->deactivate($id); } + /** + * Transforms shipping methods to the response. + * + * @param ShippingMethod[] $methods Shipping methods to transform. + * + * @return ShippingMethodResponse[] Array of shipping methods. + */ + protected function getResponse($methods) + { + $result = array(); + foreach ($methods as $item) { + $result[] = $this->transformShippingMethodModelToDto($item); + } + + return $result; + } + /** * Transforms ShippingMethod model class to ShippingMethod DTO. * @@ -164,49 +221,28 @@ private function transformShippingMethodModelToDto(ShippingMethod $item) { $shippingMethod = new ShippingMethodResponse(); $shippingMethod->id = $item->getId(); - $shippingMethod->selected = $item->isActivated(); + $shippingMethod->activated = $item->isActivated(); + $shippingMethod->name = $item->getTitle(); $shippingMethod->logoUrl = $item->getLogoUrl(); $shippingMethod->showLogo = $item->isDisplayLogo(); - $shippingMethod->title = $item->isNational() ? static::NATIONAL : static::INTERNATIONAL; + $shippingMethod->type = $item->isNational() ? static::NATIONAL : static::INTERNATIONAL; $shippingMethod->carrierName = $item->getCarrierName(); - $shippingMethod->deliveryDescription = ($item->isExpressDelivery() ? 'Express' : 'Economic') . ' ' - . $item->getDeliveryTime(); + $shippingMethod->deliveryDescription = mb_strtolower($item->getDeliveryTime()) . ' - ' + . Translator::translate( + 'shippingServices.' . ($item->isExpressDelivery() ? static::EXPRESS : static::ECONOMIC) + ); $shippingMethod->deliveryType = $item->isExpressDelivery() ? static::EXPRESS : static::ECONOMIC; - $shippingMethod->name = $item->getTitle(); - $shippingMethod->parcelDestination = $item->isDestinationDropOff() ? static::DROP_OFF : static::HOME; - $shippingMethod->parcelOrigin = $item->isDepartureDropOff() ? static::DROP_OFF : static::PICKUP; + $shippingMethod->parcelOrigin = $item->isDepartureDropOff() ? static::DROP_OFF : static::COLLECTION; + $shippingMethod->parcelDestination = $item->isDestinationDropOff() ? static::PICKUP : static::DELIVERY; $shippingMethod->taxClass = $item->getTaxClass(); $shippingMethod->shippingCountries = $item->getShippingCountries(); $shippingMethod->isShipToAllCountries = $item->isShipToAllCountries(); - - $shippingMethod->pricePolicy = $item->getPricingPolicy(); - $shippingMethod->percentPricePolicy = $item->getPercentPricePolicy(); - $shippingMethod->fixedPriceByWeightPolicy = $item->getFixedPriceByWeightPolicy(); - $shippingMethod->fixedPriceByValuePolicy = $item->getFixedPriceByValuePolicy(); - - $shippingMethod->logoUrl = $item->getLogoUrl(); + $shippingMethod->pricingPolicies = $item->getPricingPolicies(); + $shippingMethod->usePacklinkPriceIfNotInRange = $item->isUsePacklinkPriceIfNotInRange(); return $shippingMethod; } - /** - * Validates shipping method data. - * - * @param ShippingMethodConfiguration $data Shipping method data object. - * - * @return bool Returns true if shipping method data is valid, false otherwise. - */ - private function isValid(ShippingMethodConfiguration $data) - { - return !(!isset($data->id, $data->name, $data->showLogo, $data->pricePolicy) - || ($data->pricePolicy === ShippingMethod::PRICING_POLICY_PERCENT && !isset($data->percentPricePolicy)) - || ($data->pricePolicy === ShippingMethod::PRICING_POLICY_FIXED_PRICE_BY_WEIGHT - && empty($data->fixedPriceByWeightPolicy)) - || ($data->pricePolicy === ShippingMethod::PRICING_POLICY_FIXED_PRICE_BY_VALUE - && empty($data->fixedPriceByValuePolicy)) - || (!is_bool($data->showLogo) || !in_array($data->pricePolicy, static::$policies, false))); - } - /** * Updates model data from data transfer object. * @@ -220,19 +256,11 @@ private function updateModelData(ShippingMethodConfiguration $configuration, Shi $model->setTaxClass($configuration->taxClass); $model->setShipToAllCountries($configuration->isShipToAllCountries); $model->setShippingCountries($configuration->shippingCountries); - switch ($configuration->pricePolicy) { - case ShippingMethod::PRICING_POLICY_PACKLINK: - $model->setPacklinkPricePolicy(); - break; - case ShippingMethod::PRICING_POLICY_PERCENT: - $model->setPercentPricePolicy($configuration->percentPricePolicy); - break; - case ShippingMethod::PRICING_POLICY_FIXED_PRICE_BY_WEIGHT: - $model->setFixedPriceByWeightPolicy($configuration->fixedPriceByWeightPolicy); - break; - case ShippingMethod::PRICING_POLICY_FIXED_PRICE_BY_VALUE: - $model->setFixedPriceByValuePolicy($configuration->fixedPriceByValuePolicy); - break; + $model->setActivated($configuration->activated); + $model->resetPricingPolicies(); + $model->setUsePacklinkPriceIfNotInRange($configuration->usePacklinkPriceIfNotInRange); + foreach ($configuration->pricingPolicies as $policy) { + $model->addPricingPolicy($policy); } } } diff --git a/src/BusinessLogic/Controllers/WarehouseController.php b/src/BusinessLogic/Controllers/WarehouseController.php new file mode 100644 index 00000000..115bb4ba --- /dev/null +++ b/src/BusinessLogic/Controllers/WarehouseController.php @@ -0,0 +1,70 @@ +service = ServiceRegister::getService(WarehouseService::CLASS_NAME); + } + + /** + * Provides warehouse data. + * + * @return \Packlink\BusinessLogic\Warehouse\Warehouse | null + */ + public function getWarehouse() + { + return $this->service->getWarehouse(); + } + + /** + * Updates warehouse. + * + * @param array $data + * + * @return \Packlink\BusinessLogic\Warehouse\Warehouse + * + * @throws \Logeecom\Infrastructure\TaskExecution\Exceptions\QueueStorageUnavailableException + * @throws \Packlink\BusinessLogic\DTO\Exceptions\FrontDtoNotRegisteredException + * @throws \Packlink\BusinessLogic\DTO\Exceptions\FrontDtoValidationException + */ + public function updateWarehouse(array $data) + { + return $this->service->updateWarehouseData($data); + } + + /** + * Returns available countries for warehouse location. + * + * @return Country[] + */ + public function getWarehouseCountries() + { + /** @var WarehouseCountryService $countryService */ + $countryService = ServiceRegister::getService(WarehouseCountryService::CLASS_NAME); + + return $countryService->getSupportedCountries(false); + } +} \ No newline at end of file diff --git a/src/BusinessLogic/Country/CountryService.php b/src/BusinessLogic/Country/CountryService.php index 8b960f01..1f7166d2 100644 --- a/src/BusinessLogic/Country/CountryService.php +++ b/src/BusinessLogic/Country/CountryService.php @@ -6,6 +6,7 @@ use Packlink\BusinessLogic\BaseService; use Packlink\BusinessLogic\Configuration; use Packlink\BusinessLogic\DTO\FrontDtoFactory; +use Packlink\BusinessLogic\Language\Translator; /** * Class CountryProvider @@ -40,70 +41,70 @@ class CountryService extends BaseService 'name' => 'Spain', 'code' => 'ES', 'postal_code' => '28001', - 'registration_link' => 'https://auth.packlink.com/es-ES/{integration}/registro?platform_country=ES', + 'registration_link' => 'https://auth.packlink.com/es-ES/{integration}/registro', 'platform_country' => 'ES', ), 'DE' => array( 'name' => 'Germany', 'code' => 'DE', 'postal_code' => '10115', - 'registration_link' => 'https://auth.packlink.com/de-DE/{integration}/registrieren?platform_country=DE', + 'registration_link' => 'https://auth.packlink.com/de-DE/{integration}/registrieren', 'platform_country' => 'DE', ), 'FR' => array( 'name' => 'France', 'code' => 'FR', 'postal_code' => '75001', - 'registration_link' => 'https://auth.packlink.com/fr-FR/{integration}/inscription?platform_country=FR', + 'registration_link' => 'https://auth.packlink.com/fr-FR/{integration}/inscription', 'platform_country' => 'FR', ), 'IT' => array( 'name' => 'Italy', 'code' => 'IT', 'postal_code' => '00118', - 'registration_link' => 'https://auth.packlink.com/it-IT/{integration}/registro?platform_country=IT', + 'registration_link' => 'https://auth.packlink.com/it-IT/{integration}/registro', 'platform_country' => 'IT', ), 'AT' => array( 'name' => 'Austria', 'code' => 'AT', 'postal_code' => '1010', - 'registration_link' => 'https://auth.packlink.com/de-DE/{integration}/registrieren?platform_country=UN', + 'registration_link' => 'https://auth.packlink.com/de-DE/{integration}/registrieren', 'platform_country' => 'UN', ), 'NL' => array( 'name' => 'Netherlands', 'code' => 'NL', 'postal_code' => '1011', - 'registration_link' => 'https://auth.packlink.com/nl-NL/{integration}/registrieren?platform_country=UN', + 'registration_link' => 'https://auth.packlink.com/nl-NL/{integration}/registrieren', 'platform_country' => 'UN', ), 'BE' => array( 'name' => 'Belgium', 'code' => 'BE', 'postal_code' => '1000', - 'registration_link' => 'https://auth.packlink.com/nl-NL/{integration}/registrieren?platform_country=UN', + 'registration_link' => 'https://auth.packlink.com/nl-NL/{integration}/registrieren', 'platform_country' => 'UN', ), 'PT' => array( 'name' => 'Portugal', 'code' => 'PT', 'postal_code' => '1000-017', - 'registration_link' => 'https://auth.packlink.com/pt-PT/{integration}/registo?platform_country=UN', + 'registration_link' => 'https://auth.packlink.com/pt-PT/{integration}/registo', 'platform_country' => 'UN', ), 'TR' => array( 'name' => 'Turkey', 'code' => 'TR', 'postal_code' => '06010', - 'registration_link' => 'https://auth.packlink.com/tr-TR/{integration}/kayıt-yap?platform_country=UN', + 'registration_link' => 'https://auth.packlink.com/tr-TR/{integration}/kayıt-yap', 'platform_country' => 'UN', ), 'IE' => array( 'name' => 'Ireland', 'code' => 'IE', 'postal_code' => 'D1', - 'registration_link' => 'https://auth.packlink.com/en-GB/{integration}/register?platform_country=UN', + 'registration_link' => 'https://auth.packlink.com/en-GB/{integration}/register', 'platform_country' => 'UN', ), 'GB' => array( @@ -137,12 +138,14 @@ public function isBaseCountry($countryCode) /** * Returns a list of supported country DTOs. * + * @param bool $associative Indicates whether the result should be an associative array. + * * @return RegistrationCountry[] * * @noinspection PhpUnhandledExceptionInspection * @noinspection PhpDocMissingThrowsInspection */ - public function getSupportedCountries() + public function getSupportedCountries($associative = true) { $countries = array(); $configuration = ServiceRegister::getService(Configuration::CLASS_NAME); @@ -153,11 +156,12 @@ public function getSupportedCountries() '{integration}', $integration, $country['registration_link'] - ) . '&platform=PRO'; + ) . '?platform=PRO&platform_country=' . $country['platform_country']; + $country['name'] = Translator::translate('countries.' . $country['code']); $countries[$country['code']] = FrontDtoFactory::get(RegistrationCountry::CLASS_KEY, $country); } - return $countries; + return $associative ? $countries : array_values($countries); } } diff --git a/src/BusinessLogic/Country/WarehouseCountryService.php b/src/BusinessLogic/Country/WarehouseCountryService.php index e509e9a1..6cd1435a 100644 --- a/src/BusinessLogic/Country/WarehouseCountryService.php +++ b/src/BusinessLogic/Country/WarehouseCountryService.php @@ -3,6 +3,7 @@ namespace Packlink\BusinessLogic\Country; use Packlink\BusinessLogic\DTO\FrontDtoFactory; +use Packlink\BusinessLogic\Language\Translator; /** * Class WarehouseCountryService @@ -91,11 +92,12 @@ class WarehouseCountryService extends CountryService */ public function getSupportedCountries($associative = true) { - /** @var Country[] $countries */ - $countries = FrontDtoFactory::getFromBatch( - Country::CLASS_KEY, - array_merge(static::$supportedCountries, static::$additionalWarehouseCountries) - ); + $countries = array_merge(static::$supportedCountries, static::$additionalWarehouseCountries); + + foreach ($countries as $country) { + $country['name'] = Translator::translate('countries.' . $country['code']); + $countries[$country['code']] = FrontDtoFactory::get(Country::CLASS_KEY, $country); + } return $associative ? $countries : array_values($countries); } diff --git a/src/BusinessLogic/DTO/FrontDto.php b/src/BusinessLogic/DTO/FrontDto.php index 9e5f238e..785fea97 100644 --- a/src/BusinessLogic/DTO/FrontDto.php +++ b/src/BusinessLogic/DTO/FrontDto.php @@ -4,6 +4,7 @@ use Logeecom\Infrastructure\Data\DataTransferObject; use Packlink\BusinessLogic\DTO\Exceptions\FrontDtoValidationException; +use Packlink\BusinessLogic\Language\Translator; /** * Class FrontDto. @@ -72,7 +73,6 @@ public function toArray() * * @param array $payload The payload in key-value format. * - * @return void * @throws \Packlink\BusinessLogic\DTO\Exceptions\FrontDtoValidationException * When fields are not registered for DTO class or payload contains unknown fields. */ @@ -92,7 +92,7 @@ protected static function validate(array $payload) /** * Validates whether a DTO has a definition of its fields. * - * @param array $validationErrors + * @param ValidationError[] $validationErrors The array of errors to populate. */ protected static function validateDefinition(array &$validationErrors) { @@ -114,14 +114,41 @@ protected static function validateDefinition(array &$validationErrors) protected static function validateRequiredFields(array $payload, array &$validationErrors) { foreach (static::$requiredFields as $field) { - if (empty($payload[$field])) { - $validationErrors[] = static::getValidationError( - ValidationError::ERROR_REQUIRED_FIELD, - $field, - 'Field is required.' - ); - } + self::validateRequiredField($payload, $field, $validationErrors); + } + } + + /** + * Validates a required field. + * + * @param array $payload The payload. + * @param string $field The field code. + * @param ValidationError[] $validationErrors The array of errors to populate. + * + * @return bool Returns the result of validation. + */ + protected static function validateRequiredField(array $payload, $field, array &$validationErrors) + { + if (!static::isFieldSet($payload, $field)) { + static::setRequiredFieldError($field, $validationErrors); + + return false; } + + return true; + } + + /** + * Checks if a required field is set in payload. + * + * @param array $payload The input payload. + * @param string $field Field code. + * + * @return bool TRUE if field is set; otherwise, false; + */ + protected static function isFieldSet(array $payload, $field) + { + return isset($payload[$field]); } /** @@ -134,6 +161,37 @@ protected static function doValidate(array $payload, array &$validationErrors) { } + /** + * Sets the required field validation error. + * + * @param string $code The field code. + * @param ValidationError[] $validationErrors The array of errors to populate. + */ + protected static function setRequiredFieldError($code, array &$validationErrors) + { + $validationErrors[] = static::getValidationError( + ValidationError::ERROR_REQUIRED_FIELD, + $code, + Translator::translate('validation.requiredField') + ); + } + + /** + * Sets the invalid field validation error. + * + * @param string $code The field code. + * @param ValidationError[] $validationErrors The array of errors to populate. + * @param string $message Optional field message + */ + protected static function setInvalidFieldError($code, array &$validationErrors, $message = '') + { + $validationErrors[] = static::getValidationError( + ValidationError::ERROR_INVALID_FIELD, + $code, + $message ?: Translator::translate('validation.invalidField') + ); + } + /** * Get the instance of the ValidationError class. * diff --git a/src/BusinessLogic/Http/DTO/ParcelInfo.php b/src/BusinessLogic/Http/DTO/ParcelInfo.php index d73dab16..19e2ec15 100644 --- a/src/BusinessLogic/Http/DTO/ParcelInfo.php +++ b/src/BusinessLogic/Http/DTO/ParcelInfo.php @@ -5,6 +5,7 @@ use Packlink\BusinessLogic\DTO\FrontDto; use Packlink\BusinessLogic\DTO\FrontDtoFactory; use Packlink\BusinessLogic\DTO\ValidationError; +use Packlink\BusinessLogic\Language\Translator; /** * Class ParcelInfo. @@ -107,24 +108,36 @@ protected static function doValidate(array $payload, array &$validationErrors) { parent::doValidate($payload, $validationErrors); - $options = array('options' => array('min_range' => 0)); foreach (array('width', 'length', 'height') as $field) { - if (!empty($payload[$field]) && filter_var($payload[$field], FILTER_VALIDATE_INT, $options) === false) { - $validationErrors[] = static::getValidationError( - ValidationError::ERROR_INVALID_FIELD, - $field, - ucfirst($field) . ' must be a positive integer.' - ); - } + static::validateNumber($payload, $field, $validationErrors, true); } - if (!empty($payload['weight']) - && (filter_var($payload['weight'], FILTER_VALIDATE_FLOAT) === false || $payload['weight'] <= 0) - ) { - $validationErrors[] = static::getValidationError( - ValidationError::ERROR_INVALID_FIELD, - 'weight', - 'Weight must be a positive decimal number.' + static::validateNumber($payload, 'weight', $validationErrors, false); + } + + /** + * Validates if the given value is a number. + * + * @param array $payload + * @param string $field The field key. + * @param ValidationError[] $validationErrors The list of validation errors to alter. + * @param boolean $isIntValue If true, value must be an integer. If false, value can be integer or float. + */ + private static function validateNumber(array $payload, $field, array &$validationErrors, $isIntValue) + { + if (!static::isFieldSet($payload, $field)) { + // required field validation already happened + return; + } + + $value = $payload[$field]; + if ($isIntValue && !is_int($value) || !$isIntValue && !(is_float($value) || is_int($value))) { + static::setInvalidFieldError($field, $validationErrors, Translator::translate('validation.integer')); + } elseif ($value <= 0) { + static::setInvalidFieldError( + $field, + $validationErrors, + Translator::translate('validation.greaterThanZero') ); } } diff --git a/src/BusinessLogic/Language/Interfaces/TranslationService.php b/src/BusinessLogic/Language/Interfaces/TranslationService.php new file mode 100644 index 00000000..8dcae2ab --- /dev/null +++ b/src/BusinessLogic/Language/Interfaces/TranslationService.php @@ -0,0 +1,32 @@ +String to be translated. If the key is nested it should be sent separated by '.'. + * For example: + * Translation file has next definition: {"parent": {"child": "childTranslation"}}. + * The key for a parent is: "parent", the key for a child is: "parent.child"

+ * @param array $arguments A list of arguments for translation if needed. + * + * @return string A translated string if translation is found; otherwise, the input key. + */ + public function translate($key, array $arguments = array()); +} diff --git a/src/BusinessLogic/Language/TranslationService.php b/src/BusinessLogic/Language/TranslationService.php new file mode 100644 index 00000000..a78efbf4 --- /dev/null +++ b/src/BusinessLogic/Language/TranslationService.php @@ -0,0 +1,153 @@ + ['translationKey' => 'translation']] + * @var array + */ + protected static $translations = array(); + + /** + * @var string $translationsFileBasePath + */ + protected $translationsFileBasePath; + + /** + * @var string + */ + private $currentLanguage; + + /** + * TranslationService constructor. + * + * @param string|null $translationsFileBasePath + */ + public function __construct($translationsFileBasePath = null) + { + $this->translationsFileBasePath = $translationsFileBasePath; + + if (empty($this->translationsFileBasePath)) { + $this->translationsFileBasePath = __DIR__ . '/../Resources/lang'; + } + } + + /** + * Translates a key to a value defined for that key for a current language. + * If the key can not be found for the current language, translation fallback + * value will be returned (translation in English). + * If the key is not found in fallback passed key will be returned. + * + * @param string $key

String to be translated. If the key is nested it should be sent separated by '.'. + * For example: + * Translation file has next definition: {"parent": {"child": "childTranslation"}}. + * The key for a parent is: "parent", the key for a child is: "parent.child"

+ * @param array $arguments A list of arguments for translation if needed. + * + * @return string A translated string if translation is found; otherwise, the input key. + */ + public function translate($key, array $arguments = array()) + { + $this->currentLanguage = Configuration::getCurrentLanguage() ?: static::DEFAULT_LANG; + + if (empty(static::$translations[$this->currentLanguage])) { + $this->initializeTranslations(); + } + + $result = $this->getTranslation($key, $this->currentLanguage); + + if ($result === null) { + $result = $this->getTranslation($key, static::DEFAULT_LANG); + } + + if ($result === null) { + $result = $key; + } + + return vsprintf($result, $arguments); + } + + /** + * Initializes the translations from a file to in-memory map. + */ + protected function initializeTranslations() + { + $languageLowerCase = strtolower($this->currentLanguage); + $this->translationsFileBasePath = rtrim($this->translationsFileBasePath, '/') . '/'; + $translationFilePath = "{$this->translationsFileBasePath}{$languageLowerCase}.json"; + $this->initializeLanguage($translationFilePath, $this->currentLanguage); + $this->initializeFallbackLanguage(); + } + + /** + * Initializes the language to translations dictionary. + * + * @param $translationFilePath + * @param $language + */ + protected function initializeLanguage($translationFilePath, $language) + { + try { + $serializedJson = file_get_contents($translationFilePath); + } catch (Exception $ex) { + $serializedJson = false; + Logger::logWarning($ex->getMessage()); + } + + if ($serializedJson !== false) { + $translations = json_decode($serializedJson, true); + foreach ($translations as $groupKey => $group) { + if (is_array($group)) { + foreach ($group as $key => $value) { + static::$translations[$language][$groupKey . '.' . $key] = $value; + } + } else { + static::$translations[$language][$groupKey] = $group; + } + } + } + } + + /** + * Initializes the fallback language. + */ + protected function initializeFallbackLanguage() + { + if (strtolower($this->currentLanguage) !== static::DEFAULT_LANG) { + $defaultLang = static::DEFAULT_LANG; + $translationFilePath = "{$this->translationsFileBasePath}{$defaultLang}.json"; + $this->initializeLanguage($translationFilePath, static::DEFAULT_LANG); + } + } + + /** + * Gets the translation for given key and language. + * + * @param string $key The translation key. + * @param string $language The translation language. + * + * @return string|null The translation. + */ + protected function getTranslation($key, $language) + { + return isset(static::$translations[$language][$key]) ? static::$translations[$language][$key] : null; + } +} diff --git a/src/BusinessLogic/Language/Translator.php b/src/BusinessLogic/Language/Translator.php new file mode 100644 index 00000000..152add01 --- /dev/null +++ b/src/BusinessLogic/Language/Translator.php @@ -0,0 +1,46 @@ +translate($key, $arguments); + } + + /** + * Retrieves translation service. + * + * @return TranslationServiceInterface + */ + protected static function getTranslationService() + { + if (static::$translationService === null) { + static::$translationService = ServiceRegister::getService(TranslationServiceInterface::CLASS_NAME); + } + + return static::$translationService; + } +} diff --git a/src/BusinessLogic/Order/OrderService.php b/src/BusinessLogic/Order/OrderService.php index 260993ff..78ecb301 100644 --- a/src/BusinessLogic/Order/OrderService.php +++ b/src/BusinessLogic/Order/OrderService.php @@ -180,7 +180,10 @@ public function updateTrackingInfo(Shipment $shipment) $trackingHistory ); } catch (HttpBaseException $e) { - Logger::logError($e->getMessage(), 'Core', array('referenceId' => $shipment->reference)); + Logger::logWarning($e->getMessage(), 'Core', array( + 'referenceId' => $shipment->reference, + 'trace' => $e->getTraceAsString(), + )); } catch (OrderShipmentDetailsNotFound $e) { Logger::logInfo($e->getMessage(), 'Core', array('referenceId' => $shipment->reference)); } catch (OrderNotFound $e) { diff --git a/src/BusinessLogic/Registration/RegistrationInfo.php b/src/BusinessLogic/Registration/RegistrationInfo.php new file mode 100644 index 00000000..de81621d --- /dev/null +++ b/src/BusinessLogic/Registration/RegistrationInfo.php @@ -0,0 +1,57 @@ +email = $email !== null ? $email : ''; + $this->phone = $phone !== null ? $phone : ''; + $this->source = $source !== null ? $source : ''; + } + + /** + * @return string + */ + public function getEmail() + { + return $this->email; + } + + /** + * @return string + */ + public function getPhone() + { + return $this->phone; + } + + /** + * @return string + */ + public function getSource() + { + return $this->source; + } +} diff --git a/src/BusinessLogic/Registration/RegistrationInfoService.php b/src/BusinessLogic/Registration/RegistrationInfoService.php new file mode 100644 index 00000000..f046acf9 --- /dev/null +++ b/src/BusinessLogic/Registration/RegistrationInfoService.php @@ -0,0 +1,18 @@ + 200" * * @var string @@ -88,7 +88,7 @@ class RegistrationRequest extends FrontDto * * @var array */ - public $marketplaces; + public $marketplaces = array(); /** * Fields for this DTO. * @@ -210,70 +210,58 @@ protected static function doValidate(array $payload, array &$validationErrors) parent::doValidate($payload, $validationErrors); if (!empty($payload['email']) && !DtoValidator::isEmailValid($payload['email'])) { - $validationErrors[] = static::getValidationError( - ValidationError::ERROR_INVALID_FIELD, - 'email', - 'Field must be a valid email.' - ); + static::setInvalidFieldError('email', $validationErrors, Translator::translate('validation.invalidEmail')); } if (!empty($payload['password']) && strlen($payload['password']) < 6) { - $validationErrors[] = static::getValidationError( - ValidationError::ERROR_INVALID_FIELD, + static::setInvalidFieldError( 'password', - 'The password must be at least 6 characters long.' + $validationErrors, + Translator::translate('validation.shortPassword', array(6)) ); } if (!empty($payload['estimated_delivery_volume']) && !in_array($payload['estimated_delivery_volume'], static::$supportedDeliveryOptions, true) ) { - $validationErrors[] = static::getValidationError( - ValidationError::ERROR_INVALID_FIELD, + static::setInvalidFieldError( 'estimated_delivery_volume', - 'Field is not a valid delivery volume.' + $validationErrors, + Translator::translate('register.invalidDeliveryVolume') ); } if (!empty($payload['phone']) && !DtoValidator::isPhoneValid($payload['phone'])) { - $validationErrors[] = static::getValidationError( - ValidationError::ERROR_INVALID_FIELD, - 'phone', - 'Field must be a valid phone number.' - ); + static::setInvalidFieldError('phone', $validationErrors, Translator::translate('validation.invalidPhone')); } if (!empty($payload['language']) && !in_array($payload['language'], static::$supportedLanguages, true)) { - $validationErrors[] = static::getValidationError( - ValidationError::ERROR_INVALID_FIELD, + static::setInvalidFieldError( 'language', - 'Field is not a valid language.' + $validationErrors, + Translator::translate('validation.invalidLanguage') ); } if (!empty($payload['platform_country']) && !in_array($payload['platform_country'], static::$supportedPlatformCountries, true) ) { - $validationErrors[] = static::getValidationError( - ValidationError::ERROR_INVALID_FIELD, + static::setInvalidFieldError( 'platform_country', - 'Field is not a valid platform country.' + $validationErrors, + Translator::translate('validation.invalidPlatformCountry') ); } if (!empty($payload['source']) && filter_var($payload['source'], FILTER_VALIDATE_URL) === false) { - $validationErrors[] = static::getValidationError( - ValidationError::ERROR_INVALID_FIELD, - 'source', - 'Field must be a valid URL.' - ); + static::setInvalidFieldError('source', $validationErrors, Translator::translate('validation.invalidUrl')); } if (!empty($payload['platform']) && $payload['platform'] !== 'PRO') { - $validationErrors[] = static::getValidationError( - ValidationError::ERROR_INVALID_FIELD, + static::setInvalidFieldError( 'platform', - 'Field must be set to "PRO".' + $validationErrors, + Translator::translate('validation.invalidFieldValue', array('PRO')) ); } } diff --git a/src/BusinessLogic/Resources/css/README.md b/src/BusinessLogic/Resources/css/README.md new file mode 100644 index 00000000..ad872eda --- /dev/null +++ b/src/BusinessLogic/Resources/css/README.md @@ -0,0 +1 @@ +# DO NOT EDIT FILES IN THIS FOLDER SINCE THEY ARE GENERATED FROM THE SASS FILES. diff --git a/src/BusinessLogic/Resources/css/app.css b/src/BusinessLogic/Resources/css/app.css new file mode 100644 index 00000000..557c1b7b --- /dev/null +++ b/src/BusinessLogic/Resources/css/app.css @@ -0,0 +1,1274 @@ +/* Material icons */ +@font-face { + font-weight: 700; + font-family: pl-proxima-nova; + font-style: normal; + src: url("https://cdn.packlink.com/apps/giger/fonts/ProximaNova/ProximaNova-BoldWeb.woff") format("woff"); +} +@font-face { + font-weight: 600; + font-family: pl-proxima-nova; + font-style: normal; + src: url("https://cdn.packlink.com/apps/giger/fonts/ProximaNova/ProximaNova-SemiboldWeb.woff") format("woff"); +} +@font-face { + font-weight: 400; + font-family: pl-proxima-nova; + font-style: normal; + src: url("https://cdn.packlink.com/apps/giger/fonts/ProximaNova/ProximaNova-RegularWeb.woff") format("woff"); +} +#pl-page { + display: flex; + flex-direction: column; + background-color: #fff; + flex-grow: 1; + position: relative; +} +#pl-page template { + display: none; +} +#pl-page * { + position: relative; + margin: 0; + padding: 0; + font-family: pl-proxima-nova, sans-serif; + font-size: 14px; + line-height: 16px; + font-weight: 400; + font-style: normal; + color: #3a3d46; + box-sizing: border-box; + scrollbar-color: #2095f2 #d7d7d7; +} +#pl-page *::-webkit-scrollbar { + height: 10px; + width: 10px; +} +#pl-page *::-webkit-scrollbar-thumb { + border-radius: 2px; + background-color: #2095f2; +} +#pl-page *::-webkit-scrollbar-track { + background-color: #d7d7d7; +} +#pl-page *::before, #pl-page *::after { + box-sizing: border-box; +} +#pl-page main { + background-color: #fff; +} +#pl-page br { + margin-bottom: 10px; +} +#pl-page h1, #pl-page h2, #pl-page h3, #pl-page h4, #pl-page h5, #pl-page h6 { + color: #5e6972; + font-weight: bold; +} +#pl-page h1 br, #pl-page h2 br, #pl-page h3 br, #pl-page h4 br, #pl-page h5 br, #pl-page h6 br { + margin: 0; +} +#pl-page h1 { + line-height: 22px; + font-size: 18px; +} +#pl-page ul, #pl-page ol { + list-style: none; +} +#pl-page img { + height: auto; + max-width: 100%; +} +#pl-page strong { + font-weight: 700; + font-style: normal; +} +#pl-page table { + border-collapse: collapse; + border-spacing: 0; +} +#pl-page a { + color: #1a77c2; +} +#pl-page a, #pl-page :link, #pl-page :visited { + text-decoration: none; +} +#pl-page div { + display: flex; + flex-direction: column; +} +#pl-page button, #pl-page .pl-button, #pl-page input, #pl-page select, #pl-page textarea { + outline: none; +} +#pl-page button:focus, #pl-page .pl-button:focus, #pl-page input:focus, #pl-page select:focus, #pl-page textarea:focus, #pl-page button:active, #pl-page .pl-button:active, #pl-page input:active, #pl-page select:active, #pl-page textarea:active { + outline: none; +} +@keyframes pl-rotate { + 100% { + transform: rotate(360deg); + } +} +#pl-page .pl-inline { + display: inline; +} +#pl-page .pl-full-width { + width: 100%; +} +#pl-page .pl-half-width { + display: inline-flex; + width: 50%; +} +#pl-page .pl-small-page, #pl-page .pl-medium-page { + margin-left: auto; + margin-right: auto; +} +#pl-page .pl-medium-page { + max-width: 540px; +} +#pl-page .pl-small-page { + max-width: 410px; +} +#pl-page .pl-small-width { + max-width: 410px; +} +#pl-page .pl-hidden { + display: none !important; +} +#pl-page .pl-pull-left { + float: left; +} +#pl-page .pl-text-center { + text-align: center; +} +#pl-page .pl-text-left { + text-align: left; +} +#pl-page .pl-separate-vertically { + margin-top: 26px; + margin-bottom: 26px; +} +#pl-page .pl-separate-top-small { + margin-top: 2px; +} +#pl-page .pl-separate-horizontally { + margin-left: 26px; + margin-right: 26px; +} +#pl-page .pl-top-separate { + margin-top: 20px; +} +#pl-page .pl-bottom-separate { + margin-bottom: 36px; +} +#pl-page .pl-left-separate { + margin-left: 20px; +} +#pl-page .pl-right-separate { + margin-right: 20px; +} +#pl-page .pl-page-content { + overflow: hidden auto; + margin-bottom: 10px; +} +#pl-page .pl-page-padding { + padding: 20px; +} +#pl-page .pl-flex-expand { + flex-grow: 1; +} +#pl-page .pl-no-shrink { + flex-shrink: 0; +} +#pl-page .pl-row-orientation { + display: flex; + flex-direction: row; + align-items: center; +} +#pl-page .pl-flex-left { + align-items: flex-start; + justify-content: flex-start; +} +#pl-page .pl-justify-f-start { + justify-content: flex-start; +} +#pl-page .pl-separate-content { + justify-content: space-between; +} +#pl-page .pl-no-margin { + margin: 0 !important; +} +#pl-page .pl-no-border { + border-color: transparent !important; +} +#pl-page .pl-center { + display: flex; + justify-content: center; + align-items: center; + text-align: center; +} +#pl-page #pl-origin-collection svg { + width: 24px; + height: 23px; +} +#pl-page #pl-origin-dropoff svg { + width: 25px; + height: 23px; +} +#pl-page #pl-destination-pickup svg { + width: 24px; + height: 22px; +} +#pl-page #pl-destination-delivery svg { + width: 27px; + height: 22px; +} +#pl-page #pl-navigate-warehouse svg { + width: 22px; + height: 22px; +} +#pl-page #pl-navigate-parcel svg { + width: 23px; + height: 22px; +} +#pl-page .pl-flex-start { + align-items: flex-start; +} +#pl-page .pl-wrap-items { + flex-wrap: wrap; +} +#pl-page .pl-header-text-wrapper { + line-height: 22px; + font-size: 22px; +} +#pl-page .pl-small { + padding: 5px; + width: 100px; + min-width: 100px; + margin: 0; +} +#pl-page .pl-no-wrap { + white-space: nowrap; + width: auto; +} +#pl-page .pl-page-buttons { + display: block; + padding: 0 20px 20px; +} +#pl-page .pl-error-text { + color: #e63f3f; +} +#pl-page .pl-icon-text { + color: #2095f2; +} +#pl-page .pl-clickable { + cursor: pointer; +} +#pl-page .pl-all-caps { + text-transform: uppercase; +} +#pl-page .pl-block { + display: block; +} +#pl-page .pl-split-content { + justify-content: space-between; + flex-direction: row; +} +#pl-page.pl-disable-selection { + /* Firefox */ + -moz-user-select: none; + /* Internet Explorer */ + -ms-user-select: none; + /* KHTML browsers (e.g. Konqueror) */ + -khtml-user-select: none; + /* Chrome, Safari, and Opera */ + -webkit-user-select: none; + /* Disable Android and iOS callouts*/ + -webkit-touch-callout: none; +} +@media (max-width: 768px) { + #pl-page .pl-desktop-only { + display: none !important; + } + #pl-page .pl-page-content { + margin-bottom: 100px; + } + #pl-page .pl-page-buttons { + position: absolute; + bottom: 0; + width: 100%; + padding: 20px; + background-color: #fff; + } + #pl-page .pl-medium-page, #pl-page .pl-small-page { + width: 100%; + max-width: 100%; + } +} +#pl-page header { + border-bottom: 1px solid #d7d7d7; + padding: 20px; + display: flex; + align-items: center; + flex-shrink: 0; +} +#pl-page header .pl-header-holder { + justify-content: space-between; + flex-direction: row; + flex-grow: 1; + align-items: center; + margin-left: 20px; +} +#pl-page header .pl-header-holder .pl-button { + padding: 10px 13px; + text-transform: none; + margin: 0; +} +#pl-page header.pl-sub-header { + border-color: #f5f5f5; + flex-direction: row; +} +#pl-page header.pl-sub-header button { + z-index: 1; +} +#pl-page header.pl-sub-header h1 { + margin: 0 0 0 -24px; +} +#pl-page > main { + display: flex; + flex-grow: 1; + overflow: auto; +} +#pl-page button, #pl-page .pl-button { + overflow: visible; + display: inline-flex; + padding: 16px; + vertical-align: middle; + -webkit-appearance: none; + -moz-appearance: none; + cursor: pointer; + color: #2095f2; + font-style: normal; + font-weight: 600; + text-align: center; + text-decoration: none; + text-shadow: none; + text-transform: uppercase; + border: 1px solid transparent; + border-radius: 4px; + box-shadow: none; + transition: background 0.4s, border-color 0.4s, color 0.4s; + align-items: center; + justify-content: center; + margin: 10px 0 0; + height: auto; + min-width: 170px; +} +#pl-page button.pl-button-primary, #pl-page .pl-button.pl-button-primary { + background-color: #2095f2; + color: #fff; +} +#pl-page button.pl-button-secondary, #pl-page .pl-button.pl-button-secondary { + border: 1px solid #2095f2; + background-color: #fff; +} +#pl-page button.pl-button-secondary:hover, #pl-page .pl-button.pl-button-secondary:hover { + border: 1px solid rgba(32, 149, 242, .7); +} +#pl-page button.pl-button-inverted, #pl-page .pl-button.pl-button-inverted { + background-color: #fff; + color: #2e4265; +} +#pl-page button:hover, #pl-page .pl-button:hover { + color: #fff; + background: rgba(32, 149, 242, .7); + box-shadow: none; +} +#pl-page button:disabled, #pl-page .pl-button:disabled { + color: #9b9b9b; + background-color: #e1e1e1; + pointer-events: none; +} +#pl-page button.pl-small, #pl-page .pl-button.pl-small { + padding: 5px; + width: 100px; + min-width: 100px; + margin: 5px 0; +} +#pl-page .pl-icon-button { + border: none; + background: transparent; + padding: 0; + width: 24px; + height: 24px; + min-width: 24px; + margin: 0; +} +#pl-page .pl-icon-button i { + font-size: 20px; + width: auto; + color: #2095f2; +} +#pl-page .pl-icon-button:focus { + outline: none; +} +#pl-page .pl-icon-button:hover { + cursor: pointer; +} +#pl-page .pl-icon-button:hover i { + color: #fff; +} +#pl-page section { + border-bottom: 1px solid #f5f5f5; + padding: 20px 0; +} +#pl-page section:first-child { + padding-top: 0; +} +#pl-page section .pl-section-title { + font-weight: 600; + text-transform: uppercase; + display: block; + margin-bottom: 8px; +} +#pl-page section .pl-section-subtitle { + display: block; +} +#pl-page section .pl-button { + padding: 8px 12px; +} +#pl-page .pl-form-group-wrapper { + display: flex; + flex-direction: row; + margin-top: 15px; +} +#pl-page .pl-form-group-wrapper .pl-form-group + .pl-form-group { + margin-top: 0; + margin-left: -1px; +} +#pl-page .pl-button-group { + flex-direction: row; +} +#pl-page .pl-button-group button { + border-radius: 0; +} +#pl-page .pl-button-group button:first-child { + border-radius: 4px 0 0 4px; +} +#pl-page .pl-button-group button:last-child { + border-radius: 0 4px 4px 0; +} +#pl-page .pl-form-group { + position: relative; + border: none; + width: 100%; + align-items: start; + flex-shrink: 0; +} +#pl-page .pl-form-group.pl-half-width, #pl-page .pl-form-group .pl-half-width { + width: 50%; + min-width: 50%; +} +#pl-page .pl-form-group:not(:first-child) { + margin-top: 12px; +} +#pl-page .pl-form-group label { + position: absolute; + top: 8px; + left: 16px; + color: #9b9b9b; + text-transform: uppercase; + font-size: 12px; +} +#pl-page .pl-form-group input, #pl-page .pl-form-group select { + height: 56px; + background: #fff; + border-radius: 0; + border: 1px solid #d7d7d7; + color: #5d5d5d; + line-height: 22px; + font-size: 14px; + width: 100%; + margin: 0; + box-shadow: unset; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + padding: 21px 15px 13px; + max-width: none; +} +#pl-page .pl-form-group input.compact, #pl-page .pl-form-group select.compact { + padding-top: 13px; +} +#pl-page .pl-form-group input:focus, #pl-page .pl-form-group select:focus { + border: 1px solid #2095f2; + outline: none; +} +#pl-page .pl-form-group input::placeholder, #pl-page .pl-form-group select::placeholder, #pl-page .pl-form-group input::-webkit-input-placeholder, #pl-page .pl-form-group select::-webkit-input-placeholder { + color: #cbcbcb; +} +#pl-page .pl-form-group select { + -moz-padding-start: 11px; +} +#pl-page .pl-form-group i { + position: absolute; + right: 10px; + top: 28px; + margin-top: -12px; + color: #d7d7d7; + width: 24px; + height: 24px; +} +#pl-page .pl-form-group select + i { + cursor: default; + pointer-events: none; +} +#pl-page .pl-form-group .pl-autocomplete-list { + background-color: #fff; + border: 1px solid #d7d7d7; + width: 100%; + margin-top: 56px; + position: absolute; + z-index: 1; + max-height: 200px; + overflow: auto; +} +#pl-page .pl-form-group .pl-autocomplete-list .pl-autocomplete-list-item { + width: 100%; + padding: 5px; + font-size: 15px; + cursor: pointer; +} +#pl-page .pl-form-group .pl-autocomplete-list .pl-autocomplete-list-item:hover, #pl-page .pl-form-group .pl-autocomplete-list .pl-autocomplete-list-item.pl-focus { + background-color: rgba(216, 216, 216, 0.8); + color: #2095f2; +} +#pl-page input[type=checkbox] { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + width: 20px; + min-width: 20px; + height: 20px; + border: 1px solid #d7d7d7; + position: relative; +} +#pl-page input[type=checkbox] ~ label { + margin-left: 10px; +} +#pl-page input[type=checkbox]:hover { + border-color: #2095f2; +} +#pl-page input[type=checkbox]:checked { + background-color: #2095f2; + border-color: #2095f2; + text-align: center; +} +#pl-page input[type=checkbox]:checked:before { + color: #fff; + content: '\2713'; + margin: 0; + font: bold 20px/24px pl-proxima-nova; + width: 20px; + min-width: 20px; + height: 20px; +} +#pl-page .pl-alert-wrapper { + position: absolute; + width: 100%; + padding: 10px; +} +#pl-page .pl-alert { + position: relative; + cursor: pointer; + padding: 0.75rem 1.25rem; + margin: auto; + border: 1px solid transparent; + border-radius: 0.25rem; + flex-direction: row; + align-items: center; +} +#pl-page .pl-alert.pl-alert-danger { + color: #e63f3f; + background-color: #f8d7da; + border-color: #f5c6cb; +} +#pl-page .pl-alert.pl-alert-success { + color: #155724; + background-color: #d4edda; + border-color: #c3e6cb; +} +#pl-page .pl-alert .pl-alert-text { + color: inherit; +} +#pl-page .pl-alert i { + color: inherit; + margin-left: 20px; +} +#pl-page .pl-checkbox { + margin-top: 15px; + display: flex; + flex-direction: row; +} +#pl-page .pl-checkbox label { + cursor: pointer; + text-align: left; + align-items: center; + display: flex; +} +#pl-page .pl-checkbox.pl-error .pl-error-message { + display: none; +} +#pl-page .pl-switch { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; +} +#pl-page .pl-switch .pl-switch-button { + cursor: pointer; +} +#pl-page .pl-switch .pl-switch-button .pl-switch-on, #pl-page .pl-switch .pl-switch-button .pl-switch-off { + font-size: 56px; +} +#pl-page .pl-switch .pl-switch-button .pl-switch-on { + color: #2095f2; + display: none; +} +#pl-page .pl-switch .pl-switch-button.pl-selected .pl-switch-on { + display: block; +} +#pl-page .pl-switch .pl-switch-button.pl-selected .pl-switch-off { + display: none; +} +#pl-page .pl-spinner { + position: absolute; + z-index: 500; + background-color: rgba(255, 255, 255, 0.5); + display: flex; + justify-content: center; + align-items: center; + top: 0; + left: 0; + right: 0; + bottom: 0; +} +#pl-page .pl-spinner div { + width: 100px; + height: 100px; + border: 3px solid transparent; + border-right-color: #2095f2; + border-left-color: #2095f2; + border-radius: 50%; + animation: pl-rotate 1s linear 0s infinite; +} +#pl-page .pl-modal-mask { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + background-color: rgba(216, 216, 216, 0.8); + z-index: 100; + display: flex; + align-items: center; + justify-content: center; +} +#pl-page .pl-modal-mask .pl-modal { + background: #fff; + display: flex; + max-height: 90%; + max-width: 90%; + flex-flow: column; + align-items: center; + position: relative; + margin: 0 auto; + box-shadow: 0 4px 4px 0 rgba(0, 0, 0, 0.2); +} +#pl-page .pl-modal-mask .pl-modal .pl-modal-close-button { + position: absolute; + width: 24px; + height: 24px; + top: 15px; + right: 12px; + font-size: 3rem; + font-weight: 600; + color: #2095f2; + cursor: pointer; + z-index: 1; +} +#pl-page .pl-modal-mask .pl-modal .pl-modal-close-button i { + color: #2095f2; + font-weight: bold; +} +#pl-page .pl-modal-mask .pl-modal .pl-modal-title { + color: #5e6972; + font-size: 21px; + font-weight: bold; + line-height: 22px; + text-align: center; + padding: 20px 40px 0; +} +#pl-page .pl-modal-mask .pl-modal .pl-modal-subtitle { + color: #737373; + font-size: 16px; + line-height: 19px; +} +#pl-page .pl-modal-mask .pl-modal .pl-modal-body { + padding: 20px 40px; + align-items: flex-start; + justify-content: flex-start; + width: 100%; + overflow: hidden auto; +} +#pl-page .pl-modal-mask .pl-modal .pl-modal-body.pl-full-width { + padding: 20px 0; +} +#pl-page .pl-modal-mask .pl-modal .pl-modal-footer { + padding: 0 40px 20px; + display: flex; + flex-direction: row; + justify-content: center; + width: 100%; + flex-shrink: 0; +} +#pl-page .pl-modal-mask .pl-modal .pl-modal-footer .pl-button { + margin-top: 0; +} +#pl-page .pl-modal-mask .pl-modal .pl-modal-footer .pl-button + .pl-button { + margin-left: 10px; +} +#pl-page .pl-error input, #pl-page .pl-error select { + border: 1px solid #e63f3f !important; +} +#pl-page .pl-error label, #pl-page .pl-error i { + color: #e63f3f; +} +#pl-page .pl-error .pl-error-message { + margin: 8px 16px 0; + color: #e63f3f; +} +#pl-page .pl-shipping-services-table { + width: 100%; +} +#pl-page .pl-shipping-services-table thead tr th { + background-color: #f5f5f5; + z-index: 1; +} +#pl-page .pl-shipping-services-table thead tr th .pl-table-resize-handle { + width: 20px; + cursor: col-resize; + position: absolute; + right: -11px; + top: 50%; + transform: rotate(90deg); + font-size: 20px; + transform-origin: top; + color: #9b9b9b; + z-index: 2; +} +#pl-page .pl-shipping-services-table th { + font-size: 12px; + font-weight: bold; + text-transform: uppercase; + position: sticky; + top: -1px; +} +#pl-page .pl-shipping-services-table th.pl-text-center { + text-align: center; +} +#pl-page .pl-shipping-services-table td { + font-weight: 600; +} +#pl-page .pl-shipping-services-table td .pl-service-name { + font-weight: 600; + margin-bottom: 10px; +} +#pl-page .pl-shipping-services-table tr { + border-top: solid 1px #f5f5f5; + border-bottom: solid 1px #f5f5f5; +} +#pl-page .pl-shipping-services-table th, #pl-page .pl-shipping-services-table td { + padding: 15px 20px; + text-align: left; +} +#pl-page .pl-shipping-services-list { + display: none; +} +#pl-page .pl-shipping-services-list-item { + border: 1px solid #d7d7d7; + border-radius: 4px; + background-color: #fff; + box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.1); + flex-direction: column; + margin-bottom: 10px; + padding: 13px; +} +#pl-page .pl-shipping-services-list-item .pl-service-header { + border-bottom: 1px dashed #d7d7d7; + flex-direction: row; + padding-bottom: 13px; +} +#pl-page .pl-shipping-services-list-item .pl-service-header .pl-service-name { + flex-grow: 1; + padding-left: 15px; + align-items: flex-start; + justify-content: center; +} +#pl-page .pl-shipping-services-list-item .pl-service-header .pl-service-name #pl-service-name { + font-weight: 600; + margin-bottom: 8px; +} +#pl-page .pl-shipping-services-list-item .pl-wrap-items { + flex-direction: row; + justify-content: space-between; + padding: 13px 0 0; +} +#pl-page .pl-shipping-services-list-item .pl-wrap-items .pl-service-property { + min-width: 170px; + margin-bottom: 13px; +} +#pl-page .pl-shipping-services-list-item .pl-wrap-items .pl-service-property .pl-property-title { + font-weight: 600; + text-transform: uppercase; + color: #9b9b9b; + font-size: 12px; + letter-spacing: 0; + line-height: 16px; +} +#pl-page .pl-shipping-services-list-item .pl-wrap-items .pl-service-property .pl-property-value { + color: #5d5d5d; +} +#pl-page .pl-shipping-services-list-item .pl-service-buttons { + flex-direction: row; +} +#pl-page .pl-shipping-services-list-item .pl-service-buttons .pl-button { + padding: 5px 15px; + min-width: auto; +} +#pl-page .pl-shipping-services-list-item .pl-service-buttons .pl-button:not(.pl-hidden) + .pl-button { + margin-left: 10px; +} +#pl-page .pl-button-group { + padding-right: 10px; +} +#pl-page .pl-button-group button { + height: 56px; + min-width: 110px; +} +#pl-page pre { + white-space: normal; +} +@media (max-width: 768px) { + #pl-page button, #pl-page .pl-button { + width: 100%; + } + #pl-page section .pl-button { + width: auto; + } + #pl-page header .pl-header-holder { + justify-content: flex-end; + } + #pl-page header .pl-header-holder .pl-button { + display: none; + } + #pl-page .pl-shipping-services-list-item .pl-wrap-items .pl-service-property { + min-width: 50%; + } + #pl-page .pl-shipping-services-list-item .pl-button { + width: 100%; + } + #pl-page .pl-shipping-services-list-item .pl-button.pl-small { + padding: 11px; + } + #pl-page .pl-modal-mask .pl-modal .pl-modal-title { + padding: 20px 20px 0; + max-width: 90%; + } + #pl-page .pl-modal-mask .pl-modal .pl-modal-body { + padding: 20px; + } + #pl-page .pl-modal-mask .pl-modal .pl-modal-footer { + padding: 0 20px 20px; + } + #pl-page .pl-modal-mask .pl-modal .pl-modal-footer .pl-button { + min-width: 50%; + } +} +@media (max-width: 1280px) { + #pl-page .pl-shipping-services-table { + display: none; + } + #pl-page .pl-shipping-services-list { + display: flex; + flex: 0 0 auto; + padding: 10px; + } +} +@media (min-width: 769px) and (max-width: 1280px) { +} +#pl-page .material-icons { + font-family: 'Material Icons Outlined'; + font-weight: normal; + font-style: normal; + font-size: 24px; + line-height: 1; + letter-spacing: normal; + text-transform: none; + display: inline-block; + white-space: nowrap; + word-wrap: normal; + direction: ltr; + -webkit-font-feature-settings: 'liga'; + -webkit-font-smoothing: antialiased; +} +#pl-page #pl-main-header { + height: 90px; +} +#pl-page .pl-onboarding-list { + width: 100%; + justify-content: flex-start; +} +#pl-page .pl-onboarding-overview-list { + width: 100%; +} +#pl-page .pl-onboarding-overview-list .pl-list-item { + flex-direction: row; + align-items: center; + padding-bottom: 20px; +} +#pl-page .pl-onboarding-overview-list .pl-list-item i { + margin-right: 10px; +} +#pl-page .pl-onboarding-overview-list .pl-list-item + .pl-list-item { + border-top: 1px solid #e1e1e1; + padding-top: 20px; +} +#pl-page .pl-onboarding-overview-list .pl-list-item .pl-item-details { + text-align: left; +} +#pl-page .pl-onboarding-overview-list .pl-list-item .pl-onboarding-list-title { + color: #9b9b9b; + text-align: left; +} +#pl-page .pl-parcel-wrapper { + align-items: flex-start; + min-width: 100%; +} +#pl-page .pl-main-logo img { + width: 200px; +} +#pl-page .pl-register-country-list-wrapper { + margin: 15px 0 0; + min-width: 300px; + width: 100%; + display: flex; + flex-direction: column; +} +#pl-page .pl-register-country-list-wrapper .pl-country { + display: flex; + flex-direction: row; + align-items: center; + width: 100%; + padding: 5px 0; + color: #2095f2; + font-size: 15px; + text-transform: uppercase; + cursor: pointer; + flex-shrink: 0; +} +#pl-page .pl-register-country-list-wrapper .pl-country:hover { + background-color: rgba(216, 216, 216, 0.8); +} +#pl-page .pl-register-country-list-wrapper .pl-country:hover .pl-country-name { + color: #2095f2; +} +#pl-page .pl-register-country-list-wrapper .pl-country .pl-country-logo { + width: 60px; +} +#pl-page .pl-register-country-list-wrapper .pl-country .pl-country-name { + white-space: nowrap; + margin-left: 15px; + align-items: flex-start; + flex-grow: 1; +} +#pl-page .pl-register-country-list-wrapper .pl-country img { + width: 35px; + height: 35px; +} +#pl-page .pl-login-footer { + padding: 24px 30px 32px; + background-color: #2e4265; + color: #fff; + flex-shrink: 0; +} +#pl-page .pl-login-footer * { + color: #fff; +} +#pl-page .pl-onboarding-welcome-steps { + align-items: flex-start; + width: 100%; +} +#pl-page .pl-onboarding-welcome-steps .pl-list-item { + padding: 20px 0; + width: 100%; + border-bottom: 1px solid #e1e1e1; +} +#pl-page .pl-onboarding-welcome-steps .pl-list-item:last-child { + border-bottom: none; +} +#pl-page .pl-configuration-menu { + padding: 5px 20px; + margin-right: -20px; + font-weight: bold; +} +#pl-page .pl-configuration-menu i { + margin-left: 10px; +} +#pl-page .pl-configuration-page { + justify-content: space-between; +} +#pl-page .pl-configuration-page .pl-config-list { + flex-direction: column; + width: 300px; +} +#pl-page .pl-configuration-page .pl-config-list .pl-config-list-item { + border-bottom: 1px solid #f5f5f5; + padding: 17px 0 17px 26px; + flex-direction: row; + align-items: center; + width: 100%; +} +#pl-page .pl-configuration-page .pl-config-list .pl-config-list-item:hover { + background-color: #f5f5f5; + cursor: pointer; +} +#pl-page .pl-configuration-page .pl-config-list .pl-config-list-item a { + display: flex; + flex-direction: row; + flex-grow: 1; + align-items: center; +} +#pl-page .pl-configuration-page .pl-footer { + flex-direction: row; + justify-content: space-between; + padding: 20px; +} +#pl-page .pl-configuration-page .pl-footer span { + color: #717c87; + margin-right: 5px; +} +#pl-page .pl-page-onboarding .pl-page-buttons { + text-align: center; +} +#pl-page .pl-page-config { + width: 100%; + flex-grow: 1; +} +#pl-page .pl-page-config header.pl-sub-header h1 { + text-align: left; + margin-left: 10px; +} +#pl-page .pl-page-config .pl-small-page, #pl-page .pl-page-config .pl-medium-page { + margin-left: 0; + margin-right: 0; +} +#pl-page .pl-page-config .pl-page-info { + text-align: left; +} +#pl-page .pl-status-mapping { + width: 100%; + flex-direction: row; + border-top: 1px solid #f5f5f5; + border-bottom: 1px solid #f5f5f5; +} +#pl-page .pl-status-mapping.pl-order-status-mappings-header { + border: none; +} +#pl-page .pl-status-mapping .pl-half-width { + flex-direction: row; + max-width: 50%; + min-width: 50%; + align-items: center; + margin: 0; + padding: 5px 0; +} +#pl-page .pl-status-mapping .pl-half-width .pl-separation-indicator { + font-size: 32px; + margin-right: 36px; +} +#pl-page .pl-services-filter-wrapper { + flex-direction: row; + width: 100%; + justify-content: flex-start; + padding: 0 20px; + flex: 0 0 auto; +} +#pl-page .pl-services-filter-wrapper .pl-filter:not(:last-child) { + margin-right: 32px; +} +#pl-page .pl-pick-shipping-services-page #pl-open-filter-button { + display: none; +} +#pl-page .pl-modal .pl-filters-wrapper { + width: 300px; +} +#pl-page .pl-filter { + padding: 20px 0; +} +#pl-page .pl-filter.pl-filter-selected { + display: none; +} +#pl-page .pl-filter .pl-filter-title { + text-transform: uppercase; + margin-bottom: 12px; + text-align: left; + color: #5e6972; + font-weight: bold; +} +#pl-page .pl-filter .pl-filter-options { + flex-direction: row; + justify-content: flex-start; +} +#pl-page .pl-filter .pl-filter-options .pl-filter-option { + padding: 6px 13px; + border: 1px solid #d7d7d7; + border-radius: 13px; + cursor: pointer; + color: #5d5d5d; + margin-right: 12px; +} +#pl-page .pl-filter .pl-filter-options .pl-filter-option.pl-selected { + border-color: #2095f2; + background-color: #2095f2; + color: #fff; +} +#pl-page .pl-form-group:not(:first-child).pl-change-percent-wrapper { + margin-top: 0; +} +#pl-page .pl-my-shipping-services-page header { + background-color: #1a77c2; + color: #fff; + font-size: 18px; + font-weight: bold; + padding: 25px 21px; +} +#pl-page .pl-my-shipping-services-page .pl-page-buttons { + display: none; +} +#pl-page .pl-no-services img { + height: 176px; +} +#pl-page .pl-no-services .pl-no-services-description { + max-width: 420px; + margin-left: 60px; + text-align: left; +} +#pl-page .pl-no-services .pl-no-services-description p { + margin: 20px 0; +} +#pl-page .pl-no-services .pl-no-services-description button { + padding: 16px; + margin: 0; + max-width: 200px; +} +#pl-page .pl-country-checkbox-wrapper { + padding: 15px; +} +#pl-page .pl-shipping-country-selection-wrapper { + max-height: 320px; + overflow: auto; +} +#pl-page .pl-shipping-country-selection-wrapper > * { + border-bottom: 1px solid #f5f5f5; +} +#pl-page .pl-shipping-country-selected { + background-color: #f5f5f5; +} +#pl-page #pl-countries-alert-wrapper { + position: relative; + display: none; +} +#pl-page #pl-countries-alert-wrapper.visible { + display: flex; +} +@media (max-width: 768px) { + #pl-page .pl-main-logo { + width: 100%; + } + #pl-page .pl-register-country-list-wrapper { + min-width: 100%; + } + #pl-page .pl-login-footer { + padding: 16px 20px; + } + #pl-page .pl-list-item .pl-row-orientation { + flex-direction: column; + } + #pl-page .pl-configuration-page .pl-config-list { + width: 100%; + } + #pl-page .pl-configuration-page .pl-config-list .pl-config-list-item { + padding-right: 15px; + padding-left: 20px; + } + #pl-page .pl-configuration-page .pl-footer { + flex-direction: column; + } + #pl-page .pl-configuration-page .pl-footer > :first-child { + padding-bottom: 20px; + } + #pl-page .pl-my-shipping-services-page .pl-no-services { + flex-direction: column; + justify-content: flex-start; + padding-bottom: 0; + } + #pl-page .pl-my-shipping-services-page .pl-no-services img { + max-height: 40%; + } + #pl-page .pl-my-shipping-services-page .pl-no-services .pl-no-services-description { + margin: 20px 0 0; + } + #pl-page .pl-my-shipping-services-page .pl-no-services .pl-no-services-description h1 { + text-align: center; + } + #pl-page .pl-my-shipping-services-page .pl-no-services .pl-no-services-description button { + display: none; + } + #pl-page .pl-my-shipping-services-page .pl-no-services .pl-no-services-description p { + margin-bottom: 0; + } + #pl-page .pl-my-shipping-services-page .pl-page-buttons { + display: flex; + } +} +@media (max-width: 1280px) { + #pl-page .pl-services-filter-wrapper { + padding: 0 10px; + } + #pl-page .pl-services-filter-wrapper .pl-filter:not(.pl-filter-selected) { + display: none; + } + #pl-page .pl-services-filter-wrapper .pl-filter-selected { + flex-direction: row; + display: flex; + padding: 0; + } + #pl-page .pl-services-filter-wrapper .pl-filter-selected .pl-filter-options { + flex-wrap: wrap; + } + #pl-page .pl-services-filter-wrapper .pl-filter-selected .pl-filter-option { + flex-direction: row; + } + #pl-page .pl-services-filter-wrapper .pl-filter-selected .pl-filter-option.pl-selected { + margin-top: 10px; + } + #pl-page .pl-services-filter-wrapper .pl-filter-selected .pl-filter-option::after { + content: 'X'; + margin-left: 5px; + margin-top: 1px; + } + #pl-page .pl-pick-shipping-services-page #pl-open-filter-button { + display: flex; + } +} +@media (min-width: 769px) and (max-width: 1280px) { +} diff --git a/src/BusinessLogic/Resources/img/carriers/artoni.png b/src/BusinessLogic/Resources/images/carriers/artoni.png similarity index 100% rename from src/BusinessLogic/Resources/img/carriers/artoni.png rename to src/BusinessLogic/Resources/images/carriers/artoni.png diff --git a/src/BusinessLogic/Resources/img/carriers/bartolini.png b/src/BusinessLogic/Resources/images/carriers/bartolini.png similarity index 100% rename from src/BusinessLogic/Resources/img/carriers/bartolini.png rename to src/BusinessLogic/Resources/images/carriers/bartolini.png diff --git a/src/BusinessLogic/Resources/images/carriers/brt.png b/src/BusinessLogic/Resources/images/carriers/brt.png new file mode 100644 index 00000000..6e422955 Binary files /dev/null and b/src/BusinessLogic/Resources/images/carriers/brt.png differ diff --git a/src/BusinessLogic/Resources/img/carriers/cacesa.png b/src/BusinessLogic/Resources/images/carriers/cacesa.png similarity index 100% rename from src/BusinessLogic/Resources/img/carriers/cacesa.png rename to src/BusinessLogic/Resources/images/carriers/cacesa.png diff --git a/src/BusinessLogic/Resources/img/carriers/chronopost.png b/src/BusinessLogic/Resources/images/carriers/chronopost.png similarity index 100% rename from src/BusinessLogic/Resources/img/carriers/chronopost.png rename to src/BusinessLogic/Resources/images/carriers/chronopost.png diff --git a/src/BusinessLogic/Resources/img/carriers/colissimo.png b/src/BusinessLogic/Resources/images/carriers/colissimo.png similarity index 100% rename from src/BusinessLogic/Resources/img/carriers/colissimo.png rename to src/BusinessLogic/Resources/images/carriers/colissimo.png diff --git a/src/BusinessLogic/Resources/img/carriers/correos-express.png b/src/BusinessLogic/Resources/images/carriers/correos-express.png similarity index 100% rename from src/BusinessLogic/Resources/img/carriers/correos-express.png rename to src/BusinessLogic/Resources/images/carriers/correos-express.png diff --git a/src/BusinessLogic/Resources/img/carriers/correos.png b/src/BusinessLogic/Resources/images/carriers/correos.png similarity index 100% rename from src/BusinessLogic/Resources/img/carriers/correos.png rename to src/BusinessLogic/Resources/images/carriers/correos.png diff --git a/src/BusinessLogic/Resources/img/carriers/dhl-express.png b/src/BusinessLogic/Resources/images/carriers/dhl-express.png similarity index 100% rename from src/BusinessLogic/Resources/img/carriers/dhl-express.png rename to src/BusinessLogic/Resources/images/carriers/dhl-express.png diff --git a/src/BusinessLogic/Resources/img/carriers/dhl.png b/src/BusinessLogic/Resources/images/carriers/dhl-parcel-es.png similarity index 100% rename from src/BusinessLogic/Resources/img/carriers/dhl.png rename to src/BusinessLogic/Resources/images/carriers/dhl-parcel-es.png diff --git a/src/BusinessLogic/Resources/images/carriers/dhl.png b/src/BusinessLogic/Resources/images/carriers/dhl.png new file mode 100644 index 00000000..30151275 Binary files /dev/null and b/src/BusinessLogic/Resources/images/carriers/dhl.png differ diff --git a/src/BusinessLogic/Resources/img/carriers/dmm.png b/src/BusinessLogic/Resources/images/carriers/dmm.png similarity index 100% rename from src/BusinessLogic/Resources/img/carriers/dmm.png rename to src/BusinessLogic/Resources/images/carriers/dmm.png diff --git a/src/BusinessLogic/Resources/img/carriers/dpd.png b/src/BusinessLogic/Resources/images/carriers/dpd-pt.png similarity index 100% rename from src/BusinessLogic/Resources/img/carriers/dpd.png rename to src/BusinessLogic/Resources/images/carriers/dpd-pt.png diff --git a/src/BusinessLogic/Resources/images/carriers/dpd-uk.png b/src/BusinessLogic/Resources/images/carriers/dpd-uk.png new file mode 100755 index 00000000..8ab6aa70 Binary files /dev/null and b/src/BusinessLogic/Resources/images/carriers/dpd-uk.png differ diff --git a/src/BusinessLogic/Resources/images/carriers/dpd.png b/src/BusinessLogic/Resources/images/carriers/dpd.png new file mode 100755 index 00000000..8ab6aa70 Binary files /dev/null and b/src/BusinessLogic/Resources/images/carriers/dpd.png differ diff --git a/src/BusinessLogic/Resources/img/carriers/gel.png b/src/BusinessLogic/Resources/images/carriers/gel.png similarity index 100% rename from src/BusinessLogic/Resources/img/carriers/gel.png rename to src/BusinessLogic/Resources/images/carriers/gel.png diff --git a/src/BusinessLogic/Resources/img/carriers/gls.png b/src/BusinessLogic/Resources/images/carriers/gls.png similarity index 100% rename from src/BusinessLogic/Resources/img/carriers/gls.png rename to src/BusinessLogic/Resources/images/carriers/gls.png diff --git a/src/BusinessLogic/Resources/img/carriers/happy-post.png b/src/BusinessLogic/Resources/images/carriers/happy-post.png similarity index 100% rename from src/BusinessLogic/Resources/img/carriers/happy-post.png rename to src/BusinessLogic/Resources/images/carriers/happy-post.png diff --git a/src/BusinessLogic/Resources/images/carriers/hermes-uk.png b/src/BusinessLogic/Resources/images/carriers/hermes-uk.png new file mode 100644 index 00000000..47ff4504 Binary files /dev/null and b/src/BusinessLogic/Resources/images/carriers/hermes-uk.png differ diff --git a/src/BusinessLogic/Resources/images/carriers/hermes.png b/src/BusinessLogic/Resources/images/carriers/hermes.png new file mode 100644 index 00000000..47ff4504 Binary files /dev/null and b/src/BusinessLogic/Resources/images/carriers/hermes.png differ diff --git a/src/BusinessLogic/Resources/img/carriers/keavo.png b/src/BusinessLogic/Resources/images/carriers/keavo.png similarity index 100% rename from src/BusinessLogic/Resources/img/carriers/keavo.png rename to src/BusinessLogic/Resources/images/carriers/keavo.png diff --git a/src/BusinessLogic/Resources/img/carriers/mondial-relay.png b/src/BusinessLogic/Resources/images/carriers/mondial-relay.png similarity index 100% rename from src/BusinessLogic/Resources/img/carriers/mondial-relay.png rename to src/BusinessLogic/Resources/images/carriers/mondial-relay.png diff --git a/src/BusinessLogic/Resources/img/carriers/nexive.png b/src/BusinessLogic/Resources/images/carriers/nexive.png similarity index 100% rename from src/BusinessLogic/Resources/img/carriers/nexive.png rename to src/BusinessLogic/Resources/images/carriers/nexive.png diff --git a/src/BusinessLogic/Resources/img/carriers/packlink-selection.png b/src/BusinessLogic/Resources/images/carriers/packlink-selection.png similarity index 100% rename from src/BusinessLogic/Resources/img/carriers/packlink-selection.png rename to src/BusinessLogic/Resources/images/carriers/packlink-selection.png diff --git a/src/BusinessLogic/Resources/img/carriers/poste-italiane.png b/src/BusinessLogic/Resources/images/carriers/poste-italiane.png similarity index 100% rename from src/BusinessLogic/Resources/img/carriers/poste-italiane.png rename to src/BusinessLogic/Resources/images/carriers/poste-italiane.png diff --git a/src/BusinessLogic/Resources/img/carriers/poste-italiane_crono-express_door-door_it.png b/src/BusinessLogic/Resources/images/carriers/poste-italiane_crono-express_door-door_it.png similarity index 100% rename from src/BusinessLogic/Resources/img/carriers/poste-italiane_crono-express_door-door_it.png rename to src/BusinessLogic/Resources/images/carriers/poste-italiane_crono-express_door-door_it.png diff --git a/src/BusinessLogic/Resources/img/carriers/sda.png b/src/BusinessLogic/Resources/images/carriers/sda.png similarity index 100% rename from src/BusinessLogic/Resources/img/carriers/sda.png rename to src/BusinessLogic/Resources/images/carriers/sda.png diff --git a/src/BusinessLogic/Resources/img/carriers/seur.png b/src/BusinessLogic/Resources/images/carriers/seur.png similarity index 100% rename from src/BusinessLogic/Resources/img/carriers/seur.png rename to src/BusinessLogic/Resources/images/carriers/seur.png diff --git a/src/BusinessLogic/Resources/img/carriers/spring.png b/src/BusinessLogic/Resources/images/carriers/spring.png similarity index 100% rename from src/BusinessLogic/Resources/img/carriers/spring.png rename to src/BusinessLogic/Resources/images/carriers/spring.png diff --git a/src/BusinessLogic/Resources/img/carriers/starpack.png b/src/BusinessLogic/Resources/images/carriers/starpack.png similarity index 100% rename from src/BusinessLogic/Resources/img/carriers/starpack.png rename to src/BusinessLogic/Resources/images/carriers/starpack.png diff --git a/src/BusinessLogic/Resources/img/carriers/tnt.png b/src/BusinessLogic/Resources/images/carriers/tnt.png similarity index 100% rename from src/BusinessLogic/Resources/img/carriers/tnt.png rename to src/BusinessLogic/Resources/images/carriers/tnt.png diff --git a/src/BusinessLogic/Resources/img/carriers/ups.png b/src/BusinessLogic/Resources/images/carriers/ups.png similarity index 100% rename from src/BusinessLogic/Resources/img/carriers/ups.png rename to src/BusinessLogic/Resources/images/carriers/ups.png diff --git a/src/BusinessLogic/Resources/img/carriers/zeleris.png b/src/BusinessLogic/Resources/images/carriers/zeleris.png similarity index 100% rename from src/BusinessLogic/Resources/img/carriers/zeleris.png rename to src/BusinessLogic/Resources/images/carriers/zeleris.png diff --git a/src/BusinessLogic/Resources/images/checklist.png b/src/BusinessLogic/Resources/images/checklist.png new file mode 100644 index 00000000..634f014e Binary files /dev/null and b/src/BusinessLogic/Resources/images/checklist.png differ diff --git a/src/BusinessLogic/Resources/images/flags/AT.svg b/src/BusinessLogic/Resources/images/flags/AT.svg new file mode 100644 index 00000000..166d8e2c --- /dev/null +++ b/src/BusinessLogic/Resources/images/flags/AT.svg @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/src/BusinessLogic/Resources/images/flags/BE.svg b/src/BusinessLogic/Resources/images/flags/BE.svg new file mode 100644 index 00000000..4ad19930 --- /dev/null +++ b/src/BusinessLogic/Resources/images/flags/BE.svg @@ -0,0 +1,10 @@ + + + + + + + diff --git a/src/BusinessLogic/Resources/images/flags/DE.svg b/src/BusinessLogic/Resources/images/flags/DE.svg new file mode 100644 index 00000000..a6f4e17b --- /dev/null +++ b/src/BusinessLogic/Resources/images/flags/DE.svg @@ -0,0 +1,11 @@ + + + + + + + diff --git a/src/BusinessLogic/Resources/images/flags/ES.svg b/src/BusinessLogic/Resources/images/flags/ES.svg new file mode 100644 index 00000000..79d28965 --- /dev/null +++ b/src/BusinessLogic/Resources/images/flags/ES.svg @@ -0,0 +1,14 @@ + + + + + + + + + diff --git a/src/BusinessLogic/Resources/images/flags/FR.svg b/src/BusinessLogic/Resources/images/flags/FR.svg new file mode 100644 index 00000000..70954eef --- /dev/null +++ b/src/BusinessLogic/Resources/images/flags/FR.svg @@ -0,0 +1,11 @@ + + + + + + + diff --git a/src/BusinessLogic/Resources/images/flags/GB.svg b/src/BusinessLogic/Resources/images/flags/GB.svg new file mode 100644 index 00000000..9e5c6575 --- /dev/null +++ b/src/BusinessLogic/Resources/images/flags/GB.svg @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/BusinessLogic/Resources/images/flags/HU.svg b/src/BusinessLogic/Resources/images/flags/HU.svg new file mode 100644 index 00000000..c2c67a02 --- /dev/null +++ b/src/BusinessLogic/Resources/images/flags/HU.svg @@ -0,0 +1,11 @@ + + + + + + + \ No newline at end of file diff --git a/src/BusinessLogic/Resources/images/flags/IE.svg b/src/BusinessLogic/Resources/images/flags/IE.svg new file mode 100644 index 00000000..617cd7a8 --- /dev/null +++ b/src/BusinessLogic/Resources/images/flags/IE.svg @@ -0,0 +1,10 @@ + + + + + + + diff --git a/src/BusinessLogic/Resources/images/flags/IT.svg b/src/BusinessLogic/Resources/images/flags/IT.svg new file mode 100644 index 00000000..25e2964b --- /dev/null +++ b/src/BusinessLogic/Resources/images/flags/IT.svg @@ -0,0 +1,11 @@ + + + + + + + diff --git a/src/BusinessLogic/Resources/images/flags/NL.svg b/src/BusinessLogic/Resources/images/flags/NL.svg new file mode 100644 index 00000000..faac0374 --- /dev/null +++ b/src/BusinessLogic/Resources/images/flags/NL.svg @@ -0,0 +1,10 @@ + + + + + + + diff --git a/src/BusinessLogic/Resources/images/flags/PT.svg b/src/BusinessLogic/Resources/images/flags/PT.svg new file mode 100644 index 00000000..6d7ddae1 --- /dev/null +++ b/src/BusinessLogic/Resources/images/flags/PT.svg @@ -0,0 +1,14 @@ + + + + + + + + + diff --git a/src/BusinessLogic/Resources/images/flags/TR.svg b/src/BusinessLogic/Resources/images/flags/TR.svg new file mode 100644 index 00000000..98707672 --- /dev/null +++ b/src/BusinessLogic/Resources/images/flags/TR.svg @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/src/BusinessLogic/Resources/images/logo-pl.svg b/src/BusinessLogic/Resources/images/logo-pl.svg new file mode 100644 index 00000000..29f5b586 --- /dev/null +++ b/src/BusinessLogic/Resources/images/logo-pl.svg @@ -0,0 +1,97 @@ + +image/svg+xml + + + + + + + + + \ No newline at end of file diff --git a/src/BusinessLogic/Resources/images/logo.png b/src/BusinessLogic/Resources/images/logo.png new file mode 100644 index 00000000..8bc79007 Binary files /dev/null and b/src/BusinessLogic/Resources/images/logo.png differ diff --git a/src/BusinessLogic/Resources/images/logo.svg b/src/BusinessLogic/Resources/images/logo.svg new file mode 100644 index 00000000..4e9642e1 --- /dev/null +++ b/src/BusinessLogic/Resources/images/logo.svg @@ -0,0 +1,17 @@ + + + + + Created by potrace 1.15, written by Peter Selinger 2001-2017 + + + + + diff --git a/src/BusinessLogic/Resources/images/service_truck.png b/src/BusinessLogic/Resources/images/service_truck.png new file mode 100644 index 00000000..7a1055de Binary files /dev/null and b/src/BusinessLogic/Resources/images/service_truck.png differ diff --git a/src/BusinessLogic/Resources/js/AjaxService.js b/src/BusinessLogic/Resources/js/AjaxService.js index 3f1682d5..95dcd07b 100644 --- a/src/BusinessLogic/Resources/js/AjaxService.js +++ b/src/BusinessLogic/Resources/js/AjaxService.js @@ -1,4 +1,6 @@ -var Packlink = window.Packlink || {}; +if (!window.Packlink) { + window.Packlink = {}; +} (function () { /** @@ -43,7 +45,12 @@ var Packlink = window.Packlink || {}; * @param {function} [onError] */ this.call = function (method, url, data, onSuccess, onError) { - let request = getRequest(); + const request = getRequest(); + const callUUID = Packlink.StateUUIDService.getStateUUID(); + + if (!onError) { + onError = Packlink.responseService.errorHandler; + } url = url.replace('https:', ''); url = url.replace('http:', ''); @@ -53,22 +60,31 @@ var Packlink = window.Packlink || {}; request.onreadystatechange = function () { // "this" is XMLHttpRequest if (this.readyState === 4) { + if (callUUID !== Packlink.StateUUIDService.getStateUUID()) { + // Obsolete response. The app has changed the original state that issued the call. + + return; + } + if (this.status >= 200 && this.status < 300) { - onSuccess(JSON.parse(this.responseText || '{}')); - } else { - if (typeof onError !== 'undefined') { - let response = this.responseText; - try { - response = JSON.parse(this.responseText || '{}'); - } catch (e) { - } - - onError(response); + if (onSuccess) { + onSuccess(JSON.parse(this.responseText || '{}')); } + } else if (onError) { + let response = this.responseText; + try { + response = JSON.parse(this.responseText || '{}'); + } catch (e) { + } + + onError(response); + } } }; + request.setRequestHeader('Accept', 'application/json'); + if (method === 'POST') { this.internalPerformPost(request, data); } else { diff --git a/src/BusinessLogic/Resources/js/ConfigurationController.js b/src/BusinessLogic/Resources/js/ConfigurationController.js new file mode 100644 index 00000000..a911fb7f --- /dev/null +++ b/src/BusinessLogic/Resources/js/ConfigurationController.js @@ -0,0 +1,76 @@ +if (!window.Packlink) { + window.Packlink = {}; +} + +(function () { + /** + * Handles configuration static page. + * + * @constructor + * + * @param {{getDataUrl: string}} config + */ + function ConfigurationController(config) { + + const templateService = Packlink.templateService, + state = Packlink.state, + ajaxService = Packlink.ajaxService, + utilityService = Packlink.utilityService, + templateId = 'pl-configuration-page'; + + /** + * + * @param {{helpUrl: string, version: string}} response + */ + const setConfigParams = (response) => { + const version = templateService.getComponent('pl-version-number'), + helpLink = templateService.getComponent('pl-navigate-help'); + + version.innerHTML = 'v' + response.version; + helpLink.href = response.helpUrl; + + templateService.getComponent('pl-open-system-info').addEventListener('click', () => { + state.goToState('system-info'); + }); + + utilityService.hideSpinner(); + }; + + /** + * Displays page content. + */ + this.display = () => { + templateService.setCurrentTemplate(templateId); + const mainPage = templateService.getMainPage(), + backButton = mainPage.querySelector('.pl-sub-header button'); + + backButton.addEventListener('click', () => { + state.goToState('my-shipping-services'); + }); + + mainPage.querySelector('#pl-navigate-order-status').addEventListener('click', () => { + state.goToState('order-status-mapping'); + }); + + mainPage.querySelector('#pl-navigate-warehouse').addEventListener('click', () => { + state.goToState('default-warehouse', { + 'code': 'config', + 'prevState': 'configuration', + 'nextState': 'configuration', + }); + }); + + mainPage.querySelector('#pl-navigate-parcel').addEventListener('click', () => { + state.goToState('default-parcel', { + 'code': 'config', + 'prevState': 'configuration', + 'nextState': 'configuration', + }); + }); + + ajaxService.get(config.getDataUrl, setConfigParams); + }; + } + + Packlink.ConfigurationController = ConfigurationController; +})(); diff --git a/src/BusinessLogic/Resources/js/CountrySelectorController.js b/src/BusinessLogic/Resources/js/CountrySelectorController.js deleted file mode 100644 index e510fb18..00000000 --- a/src/BusinessLogic/Resources/js/CountrySelectorController.js +++ /dev/null @@ -1,86 +0,0 @@ -var Packlink = window.Packlink || {}; - -(function () { - /** - * Controller that displays shipping country selector pop up. - * - * @constructor - */ - function CountrySelectorController() { - let templateService = Packlink.templateService; - let extensionPoint = templateService.getComponent('pl-allowed-shipping-countries-ep'); - - this.display = display; - this.destroy = destroy; - - /** - * Displays country selector. - * - * @param {array} availableCountries List of available countries in system. - * @param {array} selectedCountries List of selected countries. - * @param {function} saveCallback callback when save button is clicked. - * @param {function} cancelCallback callback when cancel button is clicked. - */ - function display(availableCountries, selectedCountries, saveCallback, cancelCallback) { - templateService.setTemplate('pl-allowed-countries-modal-template', 'pl-allowed-shipping-countries-ep', true); - - let saveBtn = templateService.getComponent('pl-countries-selector-save-btn', extensionPoint); - saveBtn.addEventListener('click', onSaveButtonClicked); - - if (selectedCountries.length === 0) { - saveBtn.disabled = true; - } - - let closeBtn = templateService.getComponent('pl-close-modal-btn', extensionPoint); - closeBtn.addEventListener('click', onCancelClicked); - - let cancelBtn = templateService.getComponent('pl-countries-selector-cancel-btn'); - cancelBtn.addEventListener('click', onCancelClicked); - - let selector = templateService.getComponent('pl-countries-selector', extensionPoint); - - for (let country of availableCountries) { - let option = document.createElement('option'); - option.value = country.value; - option.innerHTML = country.label; - - if (selectedCountries.indexOf(country.value) !== -1) { - option.selected = true; - } - - selector.appendChild(option); - } - - selector.addEventListener('change', onChangeSelector); - - function onCancelClicked() { - cancelCallback(); - } - - function onSaveButtonClicked() { - if (selector.selectedOptions.length === 0) { - saveBtn.disabled = true; - } else { - let result = []; - for (let option of selector.selectedOptions) { - result.push(option.value); - } - - saveCallback(result); - } - } - - function onChangeSelector() { - saveBtn.disabled = selector.selectedOptions.length === 0; - } - } - - function destroy() { - while (extensionPoint.firstChild) { - extensionPoint.firstChild.remove(); - } - } - } - - Packlink.CountrySelectorController = CountrySelectorController; -})(); diff --git a/src/BusinessLogic/Resources/js/DefaultParcelController.js b/src/BusinessLogic/Resources/js/DefaultParcelController.js index ba2768c5..cb79d123 100644 --- a/src/BusinessLogic/Resources/js/DefaultParcelController.js +++ b/src/BusinessLogic/Resources/js/DefaultParcelController.js @@ -1,194 +1,145 @@ -var Packlink = window.Packlink || {}; +if (!window.Packlink) { + window.Packlink = {}; +} (function () { + /** + * @typedef {{weight: number, length: number, width: number, height: number}} Parcel + */ + + /** + * @param {{getUrl: string, submitUrl: string}} configuration + * @constructor + */ function DefaultParcelController(configuration) { - const defaultParcelFields = [ + const templateService = Packlink.templateService, + utilityService = Packlink.utilityService, + validationService = Packlink.validationService, + ajaxService = Packlink.ajaxService, + state = Packlink.state, + translationService = Packlink.translationService; + + this.modelFields = [ 'weight', 'width', 'length', 'height' ]; - const numericInputs = [ - 'weight', - 'width', - 'length', - 'height' - ]; - - const integers = [ - 'width', - 'length', - 'height' - ]; - - let templateService = Packlink.templateService; - let utilityService = Packlink.utilityService; - let ajaxService = Packlink.ajaxService; - let state = Packlink.state; - - let parcelData; - let page; + this.config = {}; + this.pageId = 'pl-default-parcel-page'; + this.pageKey = 'defaultParcel'; /** - * Displays page content. + * Handles Back button navigation. */ - this.display = function () { - utilityService.showSpinner(); - ajaxService.get(configuration.getUrl, constructPage); + const goToPreviousPage = () => { + state.goToState(this.config.prevState); }; /** - * Constructs default parcel page by filling form fields - * with existing data and also adds event handler to submit button. - * - * @param {object} response + * Handles Save button navigation. */ - function constructPage(response) { - parcelData = response; - page = templateService.setTemplate('pl-default-parcel-template'); - - for (let field of defaultParcelFields) { - let input = templateService.getComponent('pl-default-parcel-' + field, page); - input.addEventListener('blur', onBlurHandler, true); - if (parcelData[field]) { - input.value = parcelData[field]; - } - } - - let submitButton = templateService.getComponent( - 'pl-default-parcel-submit-btn', - page - ); - - submitButton.addEventListener('click', handleDefaultParcelSubmitButtonClickedEvent, true); - - utilityService.configureInputElements(); - utilityService.hideSpinner(); - } + const goToNextPage = () => { + state.goToState(this.config.nextState, { + 'code': this.config.code, + 'prevState': this.config.prevState, + 'nextState': 'onboarding-overview', + }); + }; /** - * Handles on blur action. + * Displays page content. * - * @param event + * @param {{code:string, prevState: string, nextState: string}} displayConfig */ - function onBlurHandler(event) { - validateField(event.target.id.substr('pl-default-parcel-'.length), event.target.value, event.target); - } + this.display = (displayConfig) => { + this.config = displayConfig; + ajaxService.get(configuration.getUrl, this.constructPage); + }; /** - * Submits default parcel form. + * Constructs default parcel page. * - * @param event + * @param {Parcel} response */ - function handleDefaultParcelSubmitButtonClickedEvent(event) { - let model = getFormattedParcelFormInput(); - - let isValid = true; - for (let field of defaultParcelFields) { - if (!model[field]) { - isValid = false; - } else { - templateService.removeError( - templateService.getComponent('pl-default-parcel-' + field, page) - ); + this.constructPage = (response) => { + templateService.setCurrentTemplate(this.pageId); + + const form = templateService.getMainPage().querySelector('form'); + validationService.setFormValidation(form, this.modelFields); + + for (let field of this.modelFields) { + if (response[field]) { + form[field].value = response[field]; } } - if (isValid) { - utilityService.showSpinner(); - ajaxService.post( - configuration.submitUrl, - model, - function () { - utilityService.hideSpinner(); - - if (configuration.fromStep) { - state.stepFinished(); - } - }, - function (response) { - for (let field in response) { - if (response.hasOwnProperty(field)) { - let input = templateService.getComponent('pl-default-parcel-' + field, page); - if (input) { - templateService.setError(input, response[field]); - } - } - } - - utilityService.hideSpinner(); - }); - } - } + const submitButton = templateService.getComponent('pl-page-submit-btn'); + submitButton.addEventListener('click', submitPage, true); - /** - * Retrieves formatted input from default parcel form. - * - * @return {object} - */ - function getFormattedParcelFormInput() { - let model = {}; + setTemplateBasedOnState(); - for (let field of defaultParcelFields) { - let value = getInputValue('pl-default-parcel-' + field), - element = templateService.getComponent('pl-default-parcel-' + field, page), - error = validateField(field, value, element); + utilityService.hideSpinner(); + }; - model[field] = error ? null : value; + const setTemplateBasedOnState = () => { + let mainPage = templateService.getMainPage(), + page = mainPage.querySelector('.' + this.pageId), + backButton = mainPage.querySelector('.pl-sub-header button'), + headerEl = mainPage.querySelector('.pl-sub-header h1'), + pageDescription = mainPage.querySelector('p.pl-page-info'), + submitButton = mainPage.querySelector('.pl-page-buttons button'); + + page.classList.add('pl-page-' + this.config.code); + backButton.addEventListener('click', goToPreviousPage); + headerEl.innerHTML = translationService.translate(this.pageKey + '.title-' + this.config.code); + pageDescription.innerHTML = translationService.translate(this.pageKey + '.description-' + this.config.code); + + if (state.getPreviousState() === 'onboarding-welcome') { + submitButton.innerText = translationService.translate('general.continue'); + } else if (state.getPreviousState() === 'onboarding-overview') { + submitButton.innerText = translationService.translate('general.save'); + } else { + submitButton.innerText = translationService.translate('general.saveChanges'); } - - return model; - } + }; /** - * Retrieves input field's value. - * - * @param input - * @return {string} + * Submits the form. */ - function getInputValue(input) { - return templateService.getComponent(input, page).value; - } + const submitPage = () => { + const form = templateService.getMainPage().querySelector('form'); + + if (!validationService.validateForm(form)) { + return false; + } + + utilityService.showSpinner(); + ajaxService.post( + configuration.submitUrl, + this.getFormFields(form), + goToNextPage, + Packlink.responseService.errorHandler + ); + }; /** - * Validates if the field value is correct and displays the error on element. + * Gets the form field values model. * - * @param {string} field The name of the field to validate. - * @param {string} value The value to validate. - * @param {Element} element The element to display error on. - * @returns {string} + * @param {HTMLElement} form + * @return {{}} */ - function validateField(field, value, element) { - let error = ''; - - if (value === '') { - error = Packlink.errorMsgs.required; - } else if (numericInputs.indexOf(field) !== -1) { - let numericValue = parseFloat(value); - // noinspection EqualityComparisonWithCoercionJS Because it checks parsing. - if (value == numericValue) { - if (numericValue <= 0) { - error = Packlink.errorMsgs.greaterThanZero; - } else { - // noinspection EqualityComparisonWithCoercionJS Cannot compare float and int with !==. - if (integers.indexOf(field) !== -1 && numericValue != parseInt(value)) { - error = Packlink.errorMsgs.integer; - } - } - } else { - error = Packlink.errorMsgs.numeric; - } - } + this.getFormFields = (form) => { + let model = {}; - if (error) { - templateService.setError(element, error); - } else { - templateService.removeError(element); + for (let field of this.modelFields) { + // + is to convert a string to a number + model[field] = form[field].value !== '' ? +form[field].value : null; } - return error; - } + return model; + }; } Packlink.DefaultParcelController = DefaultParcelController; diff --git a/src/BusinessLogic/Resources/js/DefaultWarehouseController.js b/src/BusinessLogic/Resources/js/DefaultWarehouseController.js index 15920dba..18b33c4a 100644 --- a/src/BusinessLogic/Resources/js/DefaultWarehouseController.js +++ b/src/BusinessLogic/Resources/js/DefaultWarehouseController.js @@ -1,30 +1,45 @@ -var Packlink = window.Packlink || {}; +if (!window.Packlink) { + window.Packlink = {}; +} (function () { + /** + * @typedef {{ + * id: string, + * alias: string, + * name: string, + * surname: string, + * city: string, + * phone: string, + * country: string, + * company: string, + * postal_code: string, + * address: string}} Warehouse + */ + + /** + * @param {{getUrl: string, submitUrl: string, getSupportedCountriesUrl: string, searchPostalCodesUrl: string}} configuration + * + * @constructor + */ function DefaultWarehouseController(configuration) { - const warehouseFields = [ - 'alias', - 'name', - 'surname', - 'company', - 'address', - 'phone', - 'email' - ]; + const templateService = Packlink.templateService, + utilityService = Packlink.utilityService, + ajaxService = Packlink.ajaxService, + pageId = 'pl-default-warehouse-page'; - const requiredFields = [ + const modelFields = [ 'alias', 'name', 'surname', + 'company', + 'country', + 'postal_code', 'address', 'phone', 'email' ]; - let templateService = Packlink.templateService; - let utilityService = Packlink.utilityService; - let ajaxService = Packlink.ajaxService; - let state = Packlink.state; let page; let currentCountry; @@ -36,104 +51,74 @@ var Packlink = window.Packlink || {}; let countryInput = null; let postalCodeInput = null; - /** - * Displays page content. - */ - this.display = function () { - page = templateService.setTemplate('pl-default-warehouse-template'); - utilityService.showSpinner(); - ajaxService.get(configuration.getUrl, constructPage); + // change parent's properties and methods + const parent = new Packlink.DefaultParcelController(configuration); + parent.modelFields = modelFields; + parent.pageId = pageId; + parent.pageKey = 'defaultWarehouse'; + + const parentConstruct = parent.constructPage; + + parent.constructPage = (response) => { + page = templateService.getMainPage(); + parentConstruct(response); + setSpecificFields(response); }; /** - * Attaches event handler to submit button. - * Fills form with existing warehouse data retrieved from server. + * Gets the form field values model. * - * @param response + * @param {HTMLElement} form + * @return {{}} */ - function constructPage(response) { - currentCountry = response['country']; - - for (let field of warehouseFields) { - let input = templateService.getComponent('pl-default-warehouse-' + field, page); - input.addEventListener('blur', onBlurHandler, true); - input.addEventListener('focus', onPostalCodeBlur); + parent.getFormFields = (form) => { + let model = {}; - if (response[field]) { - input.value = response[field]; - } + for (let field of modelFields) { + model[field] = form[field].value; } - constructPostalCodeInput(response['postal_code'], response['city']); - - let submitButton = templateService.getComponent( - 'pl-default-warehouse-submit-btn', - page - ); - - submitButton.addEventListener('click', handleSubmitButtonClicked, true); - utilityService.configureInputElements(); - utilityService.hideSpinner(); + return model; + }; - ajaxService.get(configuration.getSupportedCountriesUrl, constructCountryDropdown); - } + this.display = parent.display; /** - * Constructs postal code input and attaches event handlers to it. + * Sets up specific fields. * - * @param {string} postalCode - * @param {string} city + * @param {Warehouse} warehouse */ - function constructPostalCodeInput(postalCode, city) { - postalCodeInput = templateService.getComponent('pl-default-warehouse-postal_code', page); - if (postalCode && city) { - currentPostalCode = postalCode; - currentCity = city; - postalCodeInput.value = currentPostalCode + ' - ' + currentCity; - } + const setSpecificFields = (warehouse) => { + currentCountry = warehouse.country; - postalCodeInput.addEventListener('focus', onPostalCodeFocus); - postalCodeInput.addEventListener( - 'click', - function (event) { - event.stopPropagation(); - } - ); - document.addEventListener('click', onPostalCodeBlur); - postalCodeInput.addEventListener('keyup', utilityService.debounce(250, onPostalCodeSearch)); - postalCodeInput.addEventListener('keyup', autocompleteNavigate); - postalCodeInput.addEventListener( - 'focusout', - function () { - postalCodeInput.value = ' '; - }, - true); - - templateService.getComponent('data-pl-id', page, 'search-icon').addEventListener( - 'click', - function (event) { - event.stopPropagation(); - postalCodeInput.focus(); - } - ); - } + constructPostalCodeInput(warehouse.postal_code, warehouse.city); + + utilityService.hideSpinner(); + + ajaxService.get(configuration.getSupportedCountriesUrl, constructCountryDropdown); + }; /** * Builds a warehouse country dropdown and populates it with all supported countries. * - * @param response + * @param {{}} response */ - function constructCountryDropdown(response) { + const constructCountryDropdown = (response) => { countryInput = templateService.getComponent('pl-default-warehouse-country', page); let defaultOption = document.createElement('option'); - defaultOption.value = 'UN'; + defaultOption.value = ''; defaultOption.innerText = ' '; countryInput.appendChild(defaultOption); for (let code in response) { - let supportedCountry = response[code], - optionElement = document.createElement('option'); + if (!response.hasOwnProperty(code)) { + continue; + } + + /** @var {{name: string, code: string, postal_code: string, platform_country: string}} */ + const supportedCountry = response[code]; + const optionElement = document.createElement('option'); optionElement.value = supportedCountry.code; optionElement.innerText = supportedCountry.name; @@ -146,20 +131,61 @@ var Packlink = window.Packlink || {}; } countryInput.addEventListener('change', onCountryChange); - } + postalCodeInput.disabled = countryInput.value === ''; + }; - function onCountryChange() { + /** + * Resets the postal code input. + */ + const onCountryChange = () => { currentCountry = countryInput.value; currentPostalCode = ''; currentCity = ''; - } + postalCodeInput.value = '-'; + postalCodeInput.disabled = countryInput.value === ''; + }; + + /** + * Constructs postal code input and attaches event handlers to it. + * + * @param {string} postalCode + * @param {string} city + */ + const constructPostalCodeInput = (postalCode, city) => { + postalCodeInput = templateService.getComponent('pl-default-warehouse-postal_code', page); + if (postalCode && city) { + currentPostalCode = postalCode; + currentCity = city; + postalCodeInput.value = currentPostalCode + ' - ' + currentCity; + } - function onPostalCodeFocus() { - postalCodeInput.value = ''; + postalCodeInput.addEventListener('focus', onPostalCodeFocus); + postalCodeInput.addEventListener('click', (event) => { + event.stopPropagation(); + }); + + page.addEventListener('click', onPostalCodeBlur); + postalCodeInput.addEventListener('keyup', utilityService.debounce(250, onPostalCodeSearch)); + postalCodeInput.addEventListener('keyup', autocompleteNavigate); + postalCodeInput.addEventListener('focusout', () => { + if (postalCodeInput.value) { + // reset the value + postalCodeInput.value = currentPostalCode + ' - ' + currentCity; + } + }, true); + + postalCodeInput.parentElement.querySelector('i').addEventListener('click', (event) => { + event.stopPropagation(); + postalCodeInput.focus(); + }); + }; + + const onPostalCodeFocus = () => { + postalCodeInput.value = currentPostalCode; searchTerm = ''; - } + }; - function onPostalCodeBlur(event) { + const onPostalCodeBlur = (event) => { if (event) { event.stopPropagation(); } @@ -170,16 +196,9 @@ var Packlink = window.Packlink || {}; if (autocompleteList) { autocompleteList.remove(); } + }; - postalCodeInput.value = currentPostalCode + ' - ' + currentCity; - if (currentPostalCode !== '' && currentCity !== '') { - templateService.removeError(postalCodeInput); - } - - utilityService.configureInputElements(); - } - - function onPostalCodeSearch(event) { + const onPostalCodeSearch = (event) => { searchTerm = event.target.value; if (searchTerm.length < 3 || [13, 27, 38, 40].indexOf(event.keyCode) !== -1) { return; @@ -189,11 +208,10 @@ var Packlink = window.Packlink || {}; query: searchTerm, country: countryInput.value }, renderPostalCodesAutocomplete); - } + }; - function renderPostalCodesAutocomplete(response) { + const renderPostalCodesAutocomplete = (response) => { let oldAutocomplete = templateService.getComponent('pl-postal-codes-autocomplete', page); - if (oldAutocomplete) { oldAutocomplete.remove(); } @@ -207,21 +225,21 @@ var Packlink = window.Packlink || {}; createAutoCompleteListElements(newAutoComplete, response); postalCodeInput.after(newAutoComplete); - } + }; - function createAutoCompleteNode() { + const createAutoCompleteNode = () => { let node = document.createElement('ul'); node.classList.add('pl-autocomplete-list'); node.setAttribute('id', 'pl-postal-codes-autocomplete'); return node; - } + }; - function createAutoCompleteListElements(autoCompleteList, data) { + const createAutoCompleteListElements = (autoCompleteList, data) => { for (let elem of data) { let listElement = document.createElement('li'); - listElement.classList.add('pl-autocomplete-element'); + listElement.classList.add('pl-autocomplete-list-item'); listElement.setAttribute('data-pl-postal_code', elem['zipcode']); listElement.setAttribute('data-pl-city', elem['city']); @@ -238,30 +256,32 @@ var Packlink = window.Packlink || {}; let firstElem = autoCompleteList.firstChild; if (firstElem) { - firstElem.classList.add('focus'); + firstElem.classList.add('pl-focus'); } - } + }; - function onAutoCompleteFocusChange(event, autoCompleteList) { + const onAutoCompleteFocusChange = (event, autoCompleteList) => { for (let listElement of autoCompleteList.childNodes) { - if (listElement.classList && listElement.classList.contains('focus')) { - listElement.classList.remove('focus'); + if (listElement.classList && listElement.classList.contains('pl-focus')) { + listElement.classList.remove('pl-focus'); } } - event.target.classList.add('focus'); - } + event.target.classList.add('pl-focus'); + }; - function onPostalCodeSelected(event) { + const onPostalCodeSelected = (event) => { currentCity = event.target.getAttribute('data-pl-city'); currentPostalCode = event.target.getAttribute('data-pl-postal_code'); postalCodeInput.value = currentPostalCode + ' - ' + currentCity; - } + }; - function autocompleteNavigate(event) { + const autocompleteNavigate = (event) => { + // noinspection JSDeprecatedSymbols + const keyCode = event.keyCode; //esc - if (event.keyCode === 27) { + if (keyCode === 27) { postalCodeInput.blur(); page.click(); @@ -273,185 +293,40 @@ var Packlink = window.Packlink || {}; return true; } - let focused = autocomplete.querySelector('.focus'); - + let focused = autocomplete.querySelector('.pl-focus'); if (!focused) { return true; } - //enter - if (event.keyCode === 13) { - postalCodeInput.blur(); - focused.click(); - - return true; - } - - // up arrow - if (event.keyCode === 38) { - let prevSibling = focused.previousSibling; - if (prevSibling) { - prevSibling.scrollIntoView({ - behavior: 'auto', - block: 'center', - inline: 'center' - }); - prevSibling.classList.add('focus'); - focused.classList.remove('focus'); - } - - return true; - } - - // down arrow - if (event.keyCode === 40) { - let nextSibling = focused.nextSibling; - if (nextSibling) { - nextSibling.scrollIntoView({ - behavior: 'auto', - block: 'center', - inline: 'center' - }); - nextSibling.classList.add('focus'); - focused.classList.remove('focus'); - } - } - } - - /** - * Handles on blur action. - * - * @param event - */ - function onBlurHandler(event) { - let value = event.target.value; - let field = event.target.getAttribute('id').split('-')[3]; - - if (!value && requiredFields.indexOf(field) !== -1) { - templateService.setError(event.target, Packlink.errorMsgs.required); - } else { - if (field === 'phone') { - if (!isPhoneValid(value)) { - templateService.setError(event.target, Packlink.errorMsgs.phone); - } else { - templateService.removeError(event.target); - } - } else { - templateService.removeError(event.target); - } - } - } - - /** - * Handles event when submit button is clicked. - */ - function handleSubmitButtonClicked() { - let model = getFormattedWarehouseInput(); - let isValid = true; - - for (let field of warehouseFields) { - if (model[field] === null) { - templateService.setError( - templateService.getComponent('pl-default-warehouse-' + field, page), - Packlink.errorMsgs.required - ); - isValid = false; - } else { - templateService.removeError(templateService.getComponent('pl-default-warehouse-' + field, page)); - } - } - - if (!currentCity || !currentPostalCode) { - isValid = false; - templateService.setError(postalCodeInput, Packlink.errorMsgs.required); - } else { - templateService.removeError(postalCodeInput); - } - - if (isValid) { - utilityService.showSpinner(); - model['country'] = currentCountry; - model['postal_code'] = currentPostalCode; - model['city'] = currentCity; - - ajaxService.post( - configuration.submitUrl, - model, - function () { - utilityService.hideSpinner(); - - if (configuration.fromStep) { - state.stepFinished(); - } - }, - function (response) { - for (let field in response) { - if (response.hasOwnProperty(field)) { - let input = templateService.getComponent('pl-default-warehouse-' + field, page); - if (input) { - templateService.setError(input, response[field]); - } - } - } - - utilityService.hideSpinner(); - }); - } - } - - /** - * Retrieves formatted input from default warehouse form. - * - * @return {object} - */ - function getFormattedWarehouseInput() { - let model = {}; - - for (let field of warehouseFields) { - let value = getInputValue('pl-default-warehouse-' + field); - if (value === '' && requiredFields.indexOf(field) !== -1) { - value = null; - } - - if (value && field === 'phone' && !isPhoneValid(value)) { - value = null; - } - - model[field] = value; + switch (keyCode) { + case 13: + //enter + postalCodeInput.blur(); + focused.click(); + break; + case 38: + // up arrow + focusAutocompleteItem(focused.previousSibling, focused); + break; + case 40: + // down arrow + focusAutocompleteItem(focused.nextSibling, focused); + break; } + }; - return model; - } - - /** - * Retrieves input field's value. - * - * @param {string} input - * @return {string} - */ - function getInputValue(input) { - return templateService.getComponent(input, page).value; - } - - /** - * Validates phone number. - * - * @param {string} value - * @return {boolean} - */ - function isPhoneValid(value) { - let regex = /^(\+|\/|\.|-|\(|\)|\d)+$/gm; - - if (!regex.test(value)) { - return false; + const focusAutocompleteItem = (nextItem, prevItem) => { + if (nextItem) { + nextItem.scrollIntoView({ + behavior: 'auto', + block: 'center', + inline: 'center' + }); + nextItem.classList.add('pl-focus'); + prevItem.classList.remove('pl-focus'); } - - let number = /\d/gm; - - return (value.match(number) || []).length > 2; - } + }; } - Packlink.DefaultWarehouseController = DefaultWarehouseController; -})(); \ No newline at end of file +})(); diff --git a/src/BusinessLogic/Resources/js/EditServiceController.js b/src/BusinessLogic/Resources/js/EditServiceController.js new file mode 100644 index 00000000..f6783e18 --- /dev/null +++ b/src/BusinessLogic/Resources/js/EditServiceController.js @@ -0,0 +1,459 @@ +if (!window.Packlink) { + window.Packlink = {}; +} + +(function () { + /** + * @typedef EditServiceControllerConfiguration + * @property {string} getServiceUrl + * @property {string} saveServiceUrl + * @property {string} getTaxClassesUrl + * @property {string} getCountriesListUrl + * @property {boolean} hasTaxConfiguration + * @property {boolean} hasCountryConfiguration + * @property {boolean} canDisplayCarrierLogos + * @property {int} [maxTitleLength] + */ + + /** + * @typedef ShippingPricingPolicy + * @property {int} range_type + * @property {float} from_weight + * @property {float} to_weight + * @property {float} from_price + * @property {float} to_price + * @property {int} pricing_policy + * @property {boolean} increase + * @property {float} change_percent + * @property {float} fixed_price + */ + + /** + * @param {EditServiceControllerConfiguration} configuration + * @constructor + */ + function EditServiceController(configuration) { + const templateService = Packlink.templateService, + ajaxService = Packlink.ajaxService, + utilityService = Packlink.utilityService, + translator = Packlink.translationService, + validationService = Packlink.validationService, + state = Packlink.state, + templateId = 'pl-edit-service-page'; + + /** + * @type ShippingService + */ + let serviceModel = {}; + /** + * @type ShippingService|null + */ + let originalServiceModel = null; + let newService = false; + let fromPick = false; + + const modelFields = [ + 'name', + 'showLogo', + 'tax', + ]; + + const rangeTypes = { + 'price': '0', + 'weight': '1', + 'weightAndPrice': '2' + }; + + const pricingPolicies = { + 'packlink': '0', + 'percent': '1', + 'fixed': '2' + }; + + /** + * Displays page content. + * + * @param {{id: string, fromPick: boolean}} config + */ + this.display = (config) => { + fromPick = config.fromPick; + templateService.setCurrentTemplate(templateId); + let getServiceUrl = new URL(configuration.getServiceUrl); + getServiceUrl.searchParams.append('id', config.id); + ajaxService.get(getServiceUrl.toString(), bindService); + + const mainPage = templateService.getMainPage(), + backButton = mainPage.querySelector('.pl-sub-header button'), + policySwitchButton = templateService.getComponent('pl-configure-prices-button'), + addServiceButton = document.querySelector('#pl-add-price-section button'); + + backButton.addEventListener('click', () => { + goBack(config.fromPick); + }); + + policySwitchButton.addEventListener('click', () => { + policySwitchButton.classList.toggle('pl-selected'); + handlePolicySwitchButton(policySwitchButton); + }); + + addServiceButton.addEventListener('click', initializePricingPolicyModal); + }; + + /** + * Navigates to the previous page. + * + * @param {boolean} fromPickServicesPage + */ + const goBack = (fromPickServicesPage) => { + const prevState = fromPickServicesPage ? 'pick-shipping-service' : 'my-shipping-services'; + + if (JSON.stringify(serviceModel) !== JSON.stringify(originalServiceModel)) { + const modal = new Packlink.modalService({ + content: '
' + + '

' + + translator.translate('shippingServices.discardChangesQuestion') + '

' + + '
', + canClose: false, + buttons: [ + { + title: translator.translate('general.discard'), + onClick: () => { + modal.close(); + state.goToState(prevState); + } + }, + { + title: translator.translate('general.cancel'), + primary: true, + onClick: () => { + modal.close(); + } + } + ] + }); + + modal.open(); + } else { + state.goToState(prevState); + } + }; + + /** + * Binds service. + * + * @param {ShippingService} service + */ + const bindService = (service) => { + const form = templateService.getComponent('pl-edit-service-form'); + serviceModel = service; + if (!originalServiceModel) { + originalServiceModel = utilityService.cloneObject(service); + } + + newService = !service.activated; + + validationService.setFormValidation(form, modelFields); + + form['name'].value = service.name; + form['name'].addEventListener('blur', () => { + service.name = form['name'].value; + }); + + if (configuration.canDisplayCarrierLogos) { + utilityService.showElement(templateService.getComponent('pl-show-logo-group')); + form['showLogo'].checked = service.showLogo; + form['showLogo'].addEventListener('change', () => { + serviceModel.showLogo = form['showLogo'].checked; + }); + } + + if (configuration.hasTaxConfiguration) { + utilityService.showElement(templateService.getComponent('pl-tax-class-section')); + ajaxService.get(configuration.getTaxClassesUrl, populateTaxClasses); + } + + if (configuration.hasCountryConfiguration) { + setCountrySelection(); + } + + if (serviceModel.pricingPolicies.length > 0) { + setPricingPolicies(); + } else { + const policySwitchButton = templateService.getComponent('pl-configure-prices-button'); + handlePolicySwitchButton(policySwitchButton); + } + + if (form['usePacklinkPriceIfNotInRange']) { + form['usePacklinkPriceIfNotInRange'].checked = serviceModel.usePacklinkPriceIfNotInRange; + form['usePacklinkPriceIfNotInRange'].addEventListener('change', () => { + serviceModel.usePacklinkPriceIfNotInRange = form['usePacklinkPriceIfNotInRange'].checked; + }); + } + + templateService.getComponent('pl-page-submit-btn').addEventListener('click', save); + + utilityService.hideSpinner(); + }; + + /** + * Fills tax select box. + * + * @param {{label: string, value: string}[]} taxClasses + */ + const populateTaxClasses = (taxClasses) => { + const taxSelector = templateService.getComponent('pl-tax-class-select'); + + templateService.clearComponent(taxSelector); + + taxClasses.forEach(taxClass => { + const option = document.createElement('option'); + option.value = taxClass.value; + option.innerHTML = taxClass.label; + taxSelector.appendChild(option); + }); + + taxSelector.value = serviceModel.taxClass || taxClasses[0].value; + + taxSelector.addEventListener('change', () => { + serviceModel.taxClass = taxSelector.value; + }); + }; + + /** + * Handles a click to a Custom pricing policy enable button. + * + * @param {HTMLElement} btn + */ + const handlePolicySwitchButton = (btn) => { + const pricingSection = templateService.getComponent('pl-add-price-section'), + pricingPoliciesSection = templateService.getComponent('pl-pricing-policies'), + firstServiceDescription = templateService.getComponent('pl-first-service-description'), + addServiceButton = pricingSection.querySelector('button'); + + if (btn.classList.contains('pl-selected')) { + utilityService.showElement(pricingSection); + if (serviceModel.pricingPolicies.length > 0) { + setPricingPolicies(); + } else { + utilityService.showElement(firstServiceDescription); + utilityService.hideElement(pricingPoliciesSection); + addServiceButton.innerHTML = translator.translate('shippingServices.addFirstPolicy'); + } + } else { + utilityService.hideElement(templateService.getComponent('pl-use-packlink-price-wrapper')); + utilityService.hideElement(pricingSection); + utilityService.hideElement(pricingPoliciesSection); + } + }; + + /** + * Sets countries selection labels. + */ + const setCountrySelection = () => { + const section = templateService.getComponent('pl-countries-section'), + button = templateService.getComponent('pl-select-countries'), + label = templateService.getComponent('pl-selected-countries'), + selectedCountries = serviceModel.shippingCountries.length; + + utilityService.showElement(section); + button.innerHTML = translator.translate('shippingServices.openCountries'); + + button.addEventListener('click', openCountriesSelectionModal); + + if (selectedCountries === 0 || serviceModel.isShipToAllCountries) { + label.innerHTML = translator.translate('shippingServices.allCountriesSelected'); + } else if (selectedCountries === 1) { + label.innerHTML = translator.translate('shippingServices.oneCountrySelected'); + } else { + label.innerHTML = translator.translate('shippingServices.selectedCountries', [selectedCountries]); + } + }; + + /** + * Sets pricing policies section. + */ + const setPricingPolicies = () => { + const pricingPolicies = templateService.getComponent('pl-pricing-policies'), + addServiceButton = document.querySelector('#pl-add-price-section button'), + policySwitchButton = templateService.getComponent('pl-configure-prices-button'), + pricingSection = templateService.getComponent('pl-add-price-section'); + + utilityService.showElement(pricingSection); + policySwitchButton.classList.add('pl-selected'); + + utilityService.hideElement(templateService.getComponent('pl-first-service-description')); + utilityService.showElement(pricingPolicies); + addServiceButton.innerHTML = translator.translate('shippingServices.addAnotherPolicy'); + + renderPricingPolicies(); + + let editButtons = pricingPolicies.getElementsByClassName('pl-edit-pricing-policy'); + utilityService.toArray(editButtons).forEach((button, index) => { + button.addEventListener('click', (event) => { + initializePricingPolicyModal(event, index); + }); + }); + + let clearButtons = pricingPolicies.getElementsByClassName('pl-clear-pricing-policy'); + utilityService.toArray(clearButtons).forEach((button, index) => { + button.addEventListener('click', (event) => { + deletePricingPolicy(event, index); + }); + }); + + utilityService.showElement(templateService.getComponent('pl-use-packlink-price-wrapper')); + }; + + /** + * Sets initial state to pricing policy form in modal. + * + * @param {Event} event + * @param {int | null} policyIndex + * + * @returns {boolean} + */ + const initializePricingPolicyModal = (event, policyIndex = null) => { + event.preventDefault(); + + const ctrl = new Packlink.PricePolicyController(); + // noinspection JSCheckFunctionSignatures + ctrl.display({ + service: serviceModel, + policyIndex: policyIndex, + onSave: bindService + }); + + return false; + }; + + /** + * Deletes pricing policy from memory. + * + * @param {Event} event + * @param {number} i + * @returns {boolean} + */ + const deletePricingPolicy = (event, i) => { + event.preventDefault(); + serviceModel.pricingPolicies.splice(i, 1); + bindService(serviceModel); + return false; + }; + + /** + * Saves the service. + */ + const save = () => { + const form = templateService.getComponent('pl-edit-service-form'); + let excludedElementNames = []; + + if (!configuration.hasTaxConfiguration) { + excludedElementNames.push('tax'); + } + + if (validationService.validateForm(form, excludedElementNames)) { + serviceModel.activated = true; + + Packlink.utilityService.showSpinner(); + ajaxService.post( + configuration.saveServiceUrl, + serviceModel, + () => { + if (fromPick) { + state.goToState('pick-shipping-service', {from: 'edit', newService: newService}); + } else { + state.goToState('my-shipping-services'); + } + }, + Packlink.responseService.errorHandler + ); + } + }; + + /** + * Opens countries selection modal. + * + * @param {Event} event + * @returns {boolean} + */ + const openCountriesSelectionModal = (event) => { + event.preventDefault(); + const ctrl = new Packlink.ServiceCountriesModalController({getCountriesListUrl: configuration.getCountriesListUrl}); + // noinspection JSCheckFunctionSignatures + ctrl.display({ + service: serviceModel, + onSave: bindService + }); + + return false; + }; + + const renderPricingPolicies = () => { + const pricingPolicies = templateService.getComponent('pl-pricing-policies'); + const parent = pricingPolicies.querySelector('.pl-pricing-policies'); + parent.innerHTML = ''; + + serviceModel.pricingPolicies.forEach((policy, index) => { + const template = templateService.getComponent('pl-pricing-policy-list-item'), + itemEl = document.createElement('div'); + + itemEl.innerHTML = template.innerHTML; + + parent.appendChild(itemEl); + + itemEl.querySelector('#pl-price-range-title').innerHTML = + translator.translate('shippingServices.singlePricePolicy', [index + 1]); + + itemEl.querySelector('#pl-price-range-wrapper span').innerHTML = + getPolicyRangeTypeLabel(policy); + + itemEl.querySelector('#pl-price-policy-range-wrapper span').innerHTML = + getPricingPolicyLabel(policy); + }); + }; + + /** + * Gets range type label. + * @param {ShippingPricingPolicy} policy + * @returns {string} + */ + const getPolicyRangeTypeLabel = (policy) => { + const toWeight = policy.to_weight || '-'; + const toPrice = policy.to_price || '-'; + + let rangeType = translator.translate('shippingServices.priceRangeWithData', [policy.from_price, toPrice]); + + if (policy.range_type.toString() === rangeTypes.weight) { + rangeType = translator.translate('shippingServices.weightRangeWithData', [policy.from_weight, toWeight]); + } else if (policy.range_type.toString() === rangeTypes.weightAndPrice) { + rangeType = translator.translate( + 'shippingServices.weightAndPriceRangeWithData', + [policy.from_weight, toWeight, policy.from_price, toPrice] + ); + } + + return rangeType; + }; + + /** + * Gets range type label. + * @param {ShippingPricingPolicy} policy + * @returns {string} + */ + const getPricingPolicyLabel = (policy) => { + let result = translator.translate('shippingServices.packlinkPrice'); + if (policy.pricing_policy.toString() === pricingPolicies.percent) { + result = translator.translate('' + + 'shippingServices.percentagePacklinkPricesWithData', + [translator.translate('shippingServices.' + (policy.increase ? 'increase' : 'reduce')), policy.change_percent] + ); + } else if (policy.pricing_policy.toString() === pricingPolicies.fixed) { + result = translator.translate('shippingServices.fixedPricesWithData', [policy.fixed_price]); + } + + return result; + }; + } + + Packlink.EditServiceController = EditServiceController; +})(); diff --git a/src/BusinessLogic/Resources/js/FooterController.js b/src/BusinessLogic/Resources/js/FooterController.js deleted file mode 100644 index afa13e1f..00000000 --- a/src/BusinessLogic/Resources/js/FooterController.js +++ /dev/null @@ -1,96 +0,0 @@ -var Packlink = window.Packlink || {}; - -(function () { - function FooterControllerConstructor(config) { - let footer; - let templateService = Packlink.templateService; - let isSystemInfoOpen = false; - let systemInfoPanel; - let debugModeCheckbox; - - let ajaxService = Packlink.ajaxService; - - let debugStatus = false; - - this.display = function () { - footer = templateService.getComponent('pl-footer-extension-point'); - let templateComponents = templateService.getTemplate('pl-footer-template'); - for (let component of templateComponents) { - footer.appendChild(component); - } - - systemInfoPanel = templateService.getComponent('pl-system-info-panel', footer); - templateService.getComponent('pl-system-info-open-btn', footer).addEventListener( - 'click', - openSystemInfo - ); - templateService.getComponent('pl-system-info-close-btn', footer).addEventListener( - 'click', - closeSystemInfo - ); - - document.addEventListener('keydown', closeSystemInfoOnEscape); - - debugModeCheckbox = templateService.getComponent('pl-debug-mode-checkbox', footer); - debugModeCheckbox.addEventListener('click', debugModeCheckboxClickedHandler); - - ajaxService.get(config.getDebugStatusUrl, getDebugStatusHandler); - }; - - /** - * Handles retrieving debug status. - */ - function getDebugStatusHandler(response) { - debugStatus = response.status; - debugModeCheckbox.checked = debugStatus; - systemInfoPanel.classList.remove('loading'); - } - - /** - * Handles click event on debug mode checkbox. - */ - function debugModeCheckboxClickedHandler() { - systemInfoPanel.classList.add('loading'); - debugStatus = !debugStatus; - - ajaxService.post(config.setDebugStatusUrl, {status: debugStatus}, function (response) { - debugStatus = response.status; - debugModeCheckbox.checked = debugStatus; - systemInfoPanel.classList.remove('loading'); - }); - } - - /** - * Closes system info panel. - */ - function closeSystemInfo() { - if (isSystemInfoOpen) { - systemInfoPanel.classList.add('hidden'); - isSystemInfoOpen = false; - } - } - - /** - * Closes system info panel. - * - * @var {Event} event - */ - function closeSystemInfoOnEscape(event) { - if (event.key === 'Escape') { - closeSystemInfo(); - } - } - - /** - * Closes system info panel. - */ - function openSystemInfo() { - if (!isSystemInfoOpen) { - systemInfoPanel.classList.remove('hidden'); - isSystemInfoOpen = true; - } - } - } - - Packlink.FooterController = FooterControllerConstructor; -})(); \ No newline at end of file diff --git a/src/BusinessLogic/Resources/js/GridResizerService.js b/src/BusinessLogic/Resources/js/GridResizerService.js new file mode 100644 index 00000000..f7a7f699 --- /dev/null +++ b/src/BusinessLogic/Resources/js/GridResizerService.js @@ -0,0 +1,121 @@ +if (!window.Packlink) { + window.Packlink = {}; +} + +(function (window, document, localStorage) { + /** + * + * @constructor + */ + function GridResizerService() { + const templateService = Packlink.templateService, + page = templateService.getComponent('pl-page'); + + let columns = [], + table, + pageX, + currentColumn, + nextColumn, + currentColumnWidth, + nextColumnWidth; + + /** + * Initializes the given table if exists. + * @param {HTMLTableElement} tableEl Element + */ + this.init = (tableEl) => { + table = tableEl; + + if (table === null || !table.id) { + return; + } + + columns = localStorage.getItem(table.id) ? JSON.parse(localStorage.getItem(table.id)) : []; + + let initialColumns = []; + const headers = table.querySelectorAll('th:not(:last-child)'); + + headers.forEach((header, index) => { + let cellWidth = header.offsetWidth + 'px'; + + if (columns.length > 0) { + const column = columns[index]; + cellWidth = column.size; + } + + header.style.width = cellWidth; + initialColumns.push({ + header, + size: cellWidth, + }); + + if (index === headers.length - 1) { + return; + } + + const resizeHandler = header.querySelector('.pl-table-resize-handle'); + if (resizeHandler) { + resizeHandler.parentNode.removeChild(resizeHandler); + } + header.innerHTML += 'vertical_align_center'; + + header.addEventListener('mousedown', onMouseDown); + document.addEventListener('mousemove', onMouseMove); + document.addEventListener('mouseup',onMouseUp ); + }); + + columns = initialColumns; + } + + /** + * Handles mouse down event. + * + * @param {MouseEvent} event + */ + const onMouseDown = (event) => { + currentColumn = event.target.parentElement; + nextColumn = currentColumn.nextElementSibling; + pageX = event.pageX; + + currentColumnWidth = currentColumn.offsetWidth; + if (nextColumn) { + nextColumnWidth = nextColumn.offsetWidth; + } + }; + + /** + * Handles mouse move event. + * + * @param {MouseEvent} event + */ + const onMouseMove = (event) => { + if (currentColumn) { + page.classList.add('pl-disable-selection'); + + const diffX = event.pageX - pageX; + + if (nextColumn) { + const next = columns.find(({ header }) => header === nextColumn); + next.size = (nextColumnWidth - (diffX)) + 'px'; + nextColumn.style.width = next.size; + } + + const current = columns.find(({ header }) => header === currentColumn); + current.size = (currentColumnWidth + diffX) + 'px'; + currentColumn.style.width = current.size; + } + }; + + const onMouseUp = () => { + page.classList.remove('pl-disable-selection'); + localStorage.setItem(table.id, JSON.stringify(columns)); + currentColumn = undefined; + nextColumn = undefined; + pageX = undefined; + nextColumnWidth = undefined; + currentColumnWidth = undefined; + }; + } + + Packlink.GridResizerService = new GridResizerService(); +})(window, document, window.localStorage); diff --git a/src/BusinessLogic/Resources/js/LoginController.js b/src/BusinessLogic/Resources/js/LoginController.js new file mode 100644 index 00000000..f28477ca --- /dev/null +++ b/src/BusinessLogic/Resources/js/LoginController.js @@ -0,0 +1,95 @@ +if (!window.Packlink) { + window.Packlink = {}; +} + +(function () { + /** + * Handles login page logic. + * + * @param {{submit: string, listOfCountriesUrl: string}} configuration + * @constructor + */ + function LoginController(configuration) { + + const templateService = Packlink.templateService, + ajaxService = Packlink.ajaxService, + state = Packlink.state, + templateId = 'pl-login-page', + errorMessage = Packlink.translationService.translate('login.apiKeyIncorrect'); + + let inputElem, loginBtn; + + /** + * Displays page content. + */ + this.display = () => { + templateService.setCurrentTemplate(templateId); + + const loginPage = templateService.getMainPage(); + + loginBtn = templateService.getComponent('pl-login-button'); + inputElem = templateService.getComponent('pl-login-api-key'); + inputElem.addEventListener('input', (event) => { + enableButton(event); + }); + + templateService.getComponent('pl-login-form', loginPage).addEventListener('submit', login); + templateService.getComponent('pl-go-to-register', loginPage).addEventListener('click', goToRegister); + Packlink.utilityService.hideSpinner(); + }; + + /** + * Handles form submit. + * @param event + * @returns {boolean} + */ + const login = (event) => { + event.preventDefault(); + + Packlink.utilityService.showSpinner(); + + ajaxService.post(configuration.submit, {apiKey: event.target['apiKey'].value}, successfulLogin, failedLogin); + + return false; + }; + + /** + * Redirects to register. + * + * @param event + * + * @returns {boolean} + */ + const goToRegister = (event) => { + event.preventDefault(); + + let registerModalController = new Packlink.RegisterModalController( + 'pl-modal-mask', + configuration.listOfCountriesUrl, + ); + registerModalController.display(); + + return false; + }; + + const enableButton = (event) => { + Packlink.validationService.removeError(inputElem); + loginBtn.disabled = event.target.value.length === 0; + }; + + const successfulLogin = (response) => { + if (response.success) { + state.goToState('onboarding-state'); + } else { + failedLogin(); + } + }; + + const failedLogin = () => { + Packlink.validationService.setError(inputElem, errorMessage); + Packlink.utilityService.hideSpinner(); + }; + } + + Packlink.LoginController = LoginController; +})(); diff --git a/src/BusinessLogic/Resources/js/ModalService.js b/src/BusinessLogic/Resources/js/ModalService.js new file mode 100644 index 00000000..e23d03d7 --- /dev/null +++ b/src/BusinessLogic/Resources/js/ModalService.js @@ -0,0 +1,136 @@ +if (!window.Packlink) { + window.Packlink = {}; +} + +(function () { + /** + * @typedef ButtonConfig + * @property {string} title + * @property {boolean} [primary] + * @property {function()} onClick + */ + + /** + * @typedef ModalConfiguration + * @property {string} [title] + * @property {string} [content] The content of the body. + * @property {ButtonConfig[]} [buttons] Footer buttons, if any. If not provided, the footer will not be displayed. + * @property {function(HTMLDivElement)} [onOpen] Will fire after the modal is opened. + * @property {function():boolean} [onClose] Will fire before the modal is closed. + * If the return value is false, the modal will not be closed. + * @property {boolean} [footer=false] Indicates whether to use footer. Defaults to false. + * @property {boolean} [canClose=true] Indicates whether to use an (X) button or click outside the modal + * to close it. Defaults to true. + * @property {boolean} [fullWidthBody=false] Indicates whether to make body full width + */ + + /** + * @param {ModalConfiguration} configuration + * @constructor + */ + function ModalService(configuration) { + const modalId = 'pl-modal', + templateService = Packlink.templateService, + utilityService = Packlink.utilityService, + config = configuration; + /** + * @type {HTMLDivElement} + */ + let modal; + + /** + * Creates a footer button. + * + * @param {ButtonConfig} button + * + * @return {HTMLButtonElement} + */ + const createButton = (button) => { + const buttonElem = document.createElement('button'); + const cssClasses = ['pl-button', button.primary ? 'pl-button-primary' : 'pl-button-secondary']; + + buttonElem.className = cssClasses.join(' '); + buttonElem.addEventListener('click', button.onClick); + buttonElem.innerHTML = button.title; + + return buttonElem; + }; + + /** + * + * @param {KeyboardEvent} event + */ + const closeOnEsc = (event) => { + if (event.key === 'Escape') { + this.close(); + } + }; + + /** + * Closes the modal. + */ + this.close = () => { + if (!config.onClose || config.onClose()) { + window.removeEventListener('keyup', closeOnEsc); + modal.remove(); + } + }; + + /** + * Opens the modal. + */ + this.open = () => { + const div = document.createElement('div'); + div.innerHTML = templateService.getComponent(modalId).innerHTML; + // noinspection JSValidateTypes + modal = div.firstElementChild; + const closeBtn = modal.querySelector('.pl-modal-close-button'), + title = modal.querySelector('.pl-modal-title'), + body = modal.querySelector('.pl-modal-body'), + footer = modal.querySelector('.pl-modal-footer'); + + utilityService.showElement(modal); + if (config.canClose === false) { + utilityService.hideElement(closeBtn); + } else { + window.addEventListener('keyup', closeOnEsc); + closeBtn.addEventListener('click', this.close); + modal.addEventListener('click', (event) => { + if (event.target.id === 'pl-modal-mask') { + event.preventDefault(); + this.close(); + + return false; + } + }); + } + + if (config.title) { + title.innerHTML = config.title; + } else { + utilityService.hideElement(title); + } + + body.innerHTML = config.content; + if (configuration.fullWidthBody) { + body.classList.add('pl-full-width'); + } + + if (config.footer === false || !config.buttons) { + utilityService.hideElement(footer); + } else { + config.buttons.forEach((button) => { + const buttonElem = createButton(button); + footer.appendChild(buttonElem); + }); + } + + templateService.getMainPage().parentNode.appendChild(modal); + if (config.onOpen) { + config.onOpen(modal); + } + }; + } + + Packlink.modalService = ModalService; +})(); \ No newline at end of file diff --git a/src/BusinessLogic/Resources/js/MyShippingServicesController.js b/src/BusinessLogic/Resources/js/MyShippingServicesController.js new file mode 100644 index 00000000..9d7e99d7 --- /dev/null +++ b/src/BusinessLogic/Resources/js/MyShippingServicesController.js @@ -0,0 +1,117 @@ +if (!window.Packlink) { + window.Packlink = {}; +} + +(function () { + /** + * @param {{getServicesUrl: string, deleteServiceUrl: string}} configuration + * @constructor + */ + function MyShippingServicesController(configuration) { + const templateService = Packlink.templateService, + translator = Packlink.translationService, + ajaxService = Packlink.ajaxService, + utilityService = Packlink.utilityService, + state = Packlink.state; + + /** + * @type {ShippingService[]} + */ + let activeServices = []; + + /** + * Displays page content. + * + */ + this.display = function () { + utilityService.showSpinner(); + templateService.setCurrentTemplate('pl-my-shipping-services-page'); + ajaxService.get(configuration.getServicesUrl, bindServices); + + const header = templateService.getHeader(), + settingsMenu = header.querySelector('.pl-configuration-menu'), + addServiceButtons = document.getElementById('pl-page').querySelectorAll('.pl-add-service-button'); + + addServiceButtons.forEach((button) => { + button.addEventListener('click', addServiceClick); + }); + + settingsMenu.addEventListener('click', () => { + state.goToState('configuration'); + }); + }; + + const addServiceClick = () => { + state.goToState('pick-shipping-service'); + }; + + /** + * Binds services. + * + * @param {ShippingService[]} services + */ + const bindServices = (services) => { + const table = templateService.getComponent('pl-shipping-services-table'), + list = templateService.getComponent('pl-shipping-services-list'), + render = (elem, id, tag) => { + Packlink.ShippingServicesRenderer.render(elem, id, tag, activeServices, true, handleServiceAction); + }; + + activeServices = services; + render(table.querySelector('tbody'), 'pl-shipping-services-row', 'tr'); + render(list.querySelector('.pl-shipping-services-list'), 'pl-shipping-services-list-item', 'div'); + + if (services.length !== 0) { + // noinspection JSCheckFunctionSignatures + Packlink.GridResizerService.init(table); + utilityService.showElement(table); + utilityService.showElement(list); + utilityService.hideElement(templateService.getComponent('pl-no-shipping-services')); + } else { + utilityService.hideElement(table); + utilityService.hideElement(list); + utilityService.showElement(templateService.getComponent('pl-no-shipping-services')); + } + + utilityService.hideSpinner(); + }; + + /** + * Handles a shipping service action button click. + * + * @param {string} serviceId + * @param {'edit'|'delete'} action + */ + const handleServiceAction = (serviceId, action) => { + if (action === 'edit') { + state.goToState('edit-service', {id: serviceId}); + } else { + ajaxService.post(configuration.deleteServiceUrl, {id: serviceId}, () => { + const filteredServices = activeServices.filter((service) => service.id !== serviceId); + bindServices(filteredServices); + }); + + showMessageModal( + translator.translate('shippingServices.deletedSuccessTitle'), + translator.translate('shippingServices.deletedSuccessDescription') + ); + } + }; + + const showMessageModal = (title, message) => { + const modal = new Packlink.modalService({ + content: templateService.replaceResourcesUrl( + '
' + + '' + + '

' + title + '

' + + '

' + message + '

' + + '
' + ) + }); + + modal.open(); + }; + } + + Packlink.MyShippingServicesController = MyShippingServicesController; +})(); diff --git a/src/BusinessLogic/Resources/js/OnboardingOverviewController.js b/src/BusinessLogic/Resources/js/OnboardingOverviewController.js new file mode 100644 index 00000000..c66afbe7 --- /dev/null +++ b/src/BusinessLogic/Resources/js/OnboardingOverviewController.js @@ -0,0 +1,94 @@ +if (!window.Packlink) { + window.Packlink = {}; +} + +(function () { + function OnboardingOverviewController(configuration) { + + const templateService = Packlink.templateService, + ajaxService = Packlink.ajaxService, + translationService = Packlink.translationService, + state = Packlink.state, + templateId = 'pl-onboarding-overview-page'; + + /** + * @var {Parcel} + */ + let defaultParcel; + /** + * @var {Warehouse} + */ + let defaultWarehouse; + + /** + * Displays page content. + */ + this.display = function () { + ajaxService.get(configuration.defaultParcelGet, fetchDefaultWarehouse); + }; + + function fetchDefaultWarehouse(response) { + defaultParcel = response; + ajaxService.get(configuration.defaultWarehouseGet, initializePage); + } + + function initializePage(response) { + defaultWarehouse = response; + templateService.setCurrentTemplate(templateId); + + const segments = templateService.getMainPage().querySelectorAll('.pl-onboarding-overview-list .pl-list-item'); + populateSegment(segments[0], !!defaultParcel.weight, 'default-parcel', () => { + return translationService.translate('onboardingOverview.parcelData', [ + defaultParcel.weight, defaultParcel.height, defaultParcel.width, defaultParcel.length + ]); + }); + populateSegment(segments[1], !!defaultWarehouse.postal_code, 'default-warehouse', () => { + return translationService.translate('onboardingOverview.warehouseData', [ + defaultWarehouse.alias, + defaultWarehouse.name + ' ' + defaultWarehouse.surname, + defaultWarehouse.company || '-' + ]); + }); + + const submitBtn = templateService.getComponent('pl-onboarding-overview-button'); + submitBtn.disabled = !defaultParcel.weight || !defaultWarehouse.postal_code; + submitBtn.addEventListener('click', () => { + state.goToState('my-shipping-services'); + }); + + Packlink.utilityService.hideSpinner(); + } + + const populateSegment = (segment, data, editState, infoProvider) => { + const icon = segment.querySelector('i'); + const button = segment.querySelector('button'); + const details = segment.querySelector('.pl-item-details'); + + button.addEventListener('click', () => { + state.goToState(editState, { + 'code': 'onboarding', + 'prevState': 'onboarding-overview', + 'nextState': 'onboarding-overview', + }); + }); + + if (!data) { + icon.innerText = 'close'; + icon.classList.add('pl-error-text'); + button.classList.add('pl-button-primary'); + button.classList.remove('pl-button-secondary'); + button.innerText = translationService.translate('general.complete'); + details.innerHTML = translationService.translate('onboardingOverview.missingInfo'); + } else { + icon.innerText = 'check'; + icon.classList.add('pl-icon-text'); + button.classList.remove('pl-button-primary'); + button.classList.add('pl-button-secondary'); + button.innerText = translationService.translate('general.edit'); + details.innerHTML = infoProvider(); + } + }; + } + + Packlink.OnboardingOverviewController = OnboardingOverviewController; +})(); diff --git a/src/BusinessLogic/Resources/js/OnboardingStateController.js b/src/BusinessLogic/Resources/js/OnboardingStateController.js new file mode 100644 index 00000000..9e982b34 --- /dev/null +++ b/src/BusinessLogic/Resources/js/OnboardingStateController.js @@ -0,0 +1,28 @@ +var Packlink = window.Packlink || {}; + +(function () { + function OnboardingStateController(configuration) { + + const state = Packlink.state, + ajaxService = Packlink.ajaxService, + welcomeController = 'onboarding-welcome', + overviewController = 'onboarding-overview'; + + /** + * Displays page content. + */ + this.display = function () { + ajaxService.get(configuration.getState, showPageBasedOnState); + }; + + function showPageBasedOnState(response) { + if (response.state === 'welcome') { + state.goToState(welcomeController); + } else { + state.goToState(overviewController); + } + } + } + + Packlink.OnboardingStateController = OnboardingStateController; +})(); diff --git a/src/BusinessLogic/Resources/js/OnboardingWelcomeController.js b/src/BusinessLogic/Resources/js/OnboardingWelcomeController.js new file mode 100644 index 00000000..64e60839 --- /dev/null +++ b/src/BusinessLogic/Resources/js/OnboardingWelcomeController.js @@ -0,0 +1,31 @@ +if (!window.Packlink) { + window.Packlink = {}; +} + +(function () { + function OnboardingWelcomeController() { + + const templateService = Packlink.templateService, + templateId = 'pl-onboarding-welcome-page'; + + /** + * Displays page content. + */ + this.display = () => { + templateService.setCurrentTemplate(templateId); + + const btn = templateService.getComponent('pl-onboarding-welcome-button'); + btn.addEventListener('click', () => { + Packlink.state.goToState('default-parcel', { + 'code': 'onboarding', + 'prevState': 'onboarding-overview', + 'nextState': 'default-warehouse', + }); + }); + + Packlink.utilityService.hideSpinner(); + }; + } + + Packlink.OnboardingWelcomeController = OnboardingWelcomeController; +})(); diff --git a/src/BusinessLogic/Resources/js/OrderStateMappingController.js b/src/BusinessLogic/Resources/js/OrderStateMappingController.js deleted file mode 100644 index 091f257f..00000000 --- a/src/BusinessLogic/Resources/js/OrderStateMappingController.js +++ /dev/null @@ -1,146 +0,0 @@ -var Packlink = window.Packlink || {}; - -(function () { - function OrderStateMappingController(configuration) { - let templateService = Packlink.templateService; - let utilityService = Packlink.utilityService; - let ajaxService = Packlink.ajaxService; - let state = Packlink.state; - - let statuses = []; - - let page; - let mappings = {}; - let binaryGate = false; - - this.display = function () { - utilityService.showSpinner(); - - page = templateService.setTemplate('pl-order-state-mapping-template'); - - let statusElements = templateService.getComponentsByAttribute('data-pl-status', page); - for (let statusElement of statusElements) { - statuses.push(statusElement.getAttribute('data-pl-status')); - } - - attachEventHandlers(); - - ajaxService.get(configuration.getSystemOrderStatusesUrl, getSystemOrderStatusesSuccessHandler); - ajaxService.get(configuration.getUrl, getMappingsSuccessfulHandler); - }; - - /** - * Attaches event handlers to form components. - */ - function attachEventHandlers() { - for (let status of statuses) { - let select = templateService.getComponent('data-pl-status', page, status); - select.addEventListener('change', mappingChangedHandler, true); - } - - let btn = templateService.getComponent('pl-save-mappings-btn', page); - btn.addEventListener('click', saveOrderStatusMappings, true); - } - - /** - * Handles mapping changed event. - * - * @param event - */ - function mappingChangedHandler(event) { - let value = event.target.value; - let status = event.target.getAttribute('data-pl-status'); - - if (value === '') { - delete mappings[status]; - } else { - mappings[status] = value; - } - } - - /** - * Saves order status mappings. - */ - function saveOrderStatusMappings() { - utilityService.showSpinner(); - ajaxService.post( - configuration.saveUrl, - mappings, - function () { - utilityService.hideSpinner(); - - if (configuration.fromStep) { - state.stepFinished(); - } - } - ); - } - - /** - * Handles successful retrieval of order statuses. - * - * @param {object[]} response - */ - function getSystemOrderStatusesSuccessHandler(response) { - for (let status of statuses) { - let select = templateService.getComponent('data-pl-status', page, status); - addSelectOptions(select, response); - } - - if (binaryGate) { - completePageLoad(); - } - - binaryGate = true; - } - - /** - * Adds options to select html element. - * - * @param {Element} select - * @param {object[]} response - */ - function addSelectOptions(select, response) { - for (let option of response) { - let optionField = document.createElement('option'); - optionField.value = option.code; - optionField.innerHTML = option.label; - - select.appendChild(optionField); - } - } - - /** - * Handles successful retrieval of mapped order statuses. - * - * @param response - */ - function getMappingsSuccessfulHandler(response) { - if (response.length !== 0) { - mappings = response; - } - - if (binaryGate) { - completePageLoad(); - } - - binaryGate = true; - } - - /** - * Finalizes page load by applying selected mappings to selection form. - */ - function completePageLoad() { - for (let status in mappings) { - if (mappings.hasOwnProperty(status)) { - let select = templateService.getComponent('data-pl-status', page, status); - select.value = mappings[status]; - } - } - - utilityService.hideSpinner(); - } - } - - Packlink.OrderStateMappingController = OrderStateMappingController; -})(); \ No newline at end of file diff --git a/src/BusinessLogic/Resources/js/OrderStatusMappingController.js b/src/BusinessLogic/Resources/js/OrderStatusMappingController.js new file mode 100644 index 00000000..def57b8b --- /dev/null +++ b/src/BusinessLogic/Resources/js/OrderStatusMappingController.js @@ -0,0 +1,123 @@ +if (!window.Packlink) { + window.Packlink = {}; +} + +(function () { + /** + * @param {{getMappingAndStatusesUrl: string, setUrl: string}} configuration + * + * @constructor + */ + function OrderStatusMappingController(configuration) { + const templateService = Packlink.templateService, + utilityService = Packlink.utilityService, + translator = Packlink.translationService, + ajaxService = Packlink.ajaxService, + state = Packlink.state, + templateId = 'pl-order-status-mapping-page'; + + let mappings = {}; + + /** + * Handles mapping changed event. + * + * @param event + */ + const mappingChangedHandler = event => { + mappings[event.target.name] = event.target.value; + }; + + /** + * Saves order status mappings. + */ + const saveOrderStatusMappings = () => { + utilityService.showSpinner(); + ajaxService.post( + configuration.setUrl, + mappings, + () => { + state.goToState('configuration'); + }, + Packlink.responseService.errorHandler + ); + }; + + /** + * Appends new status. + * + * @param {string} statusCode + * @param {string} statusLabel + * @param {string} mappedValue + * @param {[]} orderStatuses + * @return {Element} + */ + const injectStatus = (statusCode, statusLabel, mappedValue, orderStatuses) => { + const div = document.createElement('div'); + + div.innerHTML = templateService.getComponent('pl-status-mapping-template').innerHTML; + div.querySelector('.pl-packlink-status').innerHTML = statusLabel; + const select = div.querySelector('select'); + select.name = statusCode; + + for (const orderStatus in orderStatuses) { + if (orderStatuses.hasOwnProperty(orderStatus)) { + const option = document.createElement('option'); + option.value = orderStatus; + option.innerHTML = orderStatuses[orderStatus]; + select.appendChild(option); + } + } + + select.value = mappedValue || ''; + select.addEventListener('change', mappingChangedHandler, true); + + return div.firstElementChild; + }; + + /** + * Finalizes page load by applying selected mappings to selection form. + * + * @param {{packlinkStatuses: {}, mappings: {}, orderStatuses: {}, systemName: string}} response + */ + const constructPage = (response) => { + const page = templateService.getMainPage().querySelector('.pl-order-status-mapping-page'), + name = [response.systemName], + mappingsDiv = templateService.getComponent('pl-order-status-mappings'), + btn = templateService.getComponent('pl-page-submit-btn'), + statuses = response.packlinkStatuses; + + mappings = response.mappings; + for (const status in statuses) { + mappingsDiv.appendChild(injectStatus(status, statuses[status], mappings[status], response.orderStatuses)); + } + + btn.addEventListener('click', saveOrderStatusMappings, true); + + page.querySelector('.pl-page-info').innerHTML = translator.translate('orderStatusMapping.description', name); + + page.querySelector('.pl-order-status-mappings-header .pl-system-order-status').innerHTML = translator.translate( + 'orderStatusMapping.systemOrderStatus', + name + ); + + utilityService.hideSpinner(); + }; + + /** + * Displays the page. + */ + this.display = () => { + templateService.setCurrentTemplate(templateId); + const mainPage = templateService.getMainPage(), + backButton = mainPage.querySelector('.pl-sub-header button'); + + backButton.addEventListener('click', () => { + state.goToState('configuration'); + }); + + ajaxService.get(configuration.getMappingAndStatusesUrl, constructPage); + }; + } + + Packlink.OrderStatusMappingController = OrderStatusMappingController; +})(); diff --git a/src/BusinessLogic/Resources/js/PageControllerFactory.js b/src/BusinessLogic/Resources/js/PageControllerFactory.js index 2e6fee4c..253e91da 100644 --- a/src/BusinessLogic/Resources/js/PageControllerFactory.js +++ b/src/BusinessLogic/Resources/js/PageControllerFactory.js @@ -1,4 +1,6 @@ -var Packlink = window.Packlink || {}; +if (!window.Packlink) { + window.Packlink = {}; +} (function () { function PageControllerFactory() { @@ -8,7 +10,7 @@ var Packlink = window.Packlink || {}; * @param {string} controller * @param {object} configuration */ - this.getInstance = function (controller, configuration) { + this.getInstance = (controller, configuration) => { let parts = controller.split('-'); let name = ''; for (let part of parts) { diff --git a/src/BusinessLogic/Resources/js/PickShippingServiceController.js b/src/BusinessLogic/Resources/js/PickShippingServiceController.js new file mode 100644 index 00000000..d1762ddc --- /dev/null +++ b/src/BusinessLogic/Resources/js/PickShippingServiceController.js @@ -0,0 +1,412 @@ +if (!window.Packlink) { + window.Packlink = {}; +} + +(function () { + /** + * @typedef PickShippingServiceControllerConfiguration + * @property {string} getServicesUrl + * @property {string} getActiveServicesUrl + * @property {string} getTaskStatusUrl + * @property {string} startAutoConfigureUrl + * @property {string} disableCarriersUrl + * @property {boolean} newService + */ + + /** + * @param {PickShippingServiceControllerConfiguration} configuration + * @constructor + */ + function PickShippingServiceController(configuration) { + + const templateService = Packlink.templateService, + ajaxService = Packlink.ajaxService, + translator = Packlink.translationService, + utilityService = Packlink.utilityService, + state = Packlink.state, + templateId = 'pl-pick-service-page'; + + let appliedFilter = {}; + /** + * @type ShippingService[] + */ + let shippingServices; + let desktopMode = true; + /** + * @type ModalService + */ + let noServicesModal; + + /** + * Displays page content. + * + * @param {{from: string, newService: boolean}} config + */ + this.display = function (config) { + utilityService.showSpinner(); + templateService.setCurrentTemplate(templateId); + ajaxService.get(configuration.getTaskStatusUrl, (response) => { + checkServicesStatus(response, config); + }); + + const mainPage = templateService.getMainPage(), + backButton = mainPage.querySelector('.pl-sub-header button'); + + backButton.addEventListener('click', () => { + state.goToState('my-shipping-services'); + }); + + mainPage.querySelectorAll('.pl-filter-option').forEach((optionBtn) => { + optionBtn.addEventListener('click', () => { + desktopMode = true; + filterButtonClicked(optionBtn); + }); + }); + + mainPage.querySelector('#pl-open-filter-button').addEventListener('click', showFilterModal); + }; + + /** + * Checks the status of the update shipping services task. + * + * @param {{status: string}} response + * @param {{from: string, newService: boolean}} config + */ + const checkServicesStatus = (response, config) => { + if (response.status === 'completed') { + ajaxService.get(configuration.getServicesUrl, (services) => { + bindServices(services, config); + }); + } else if (response.status === 'failed') { + showNoServicesModal(); + } else { + setTimeout( + function () { + ajaxService.get(configuration.getTaskStatusUrl, (res) => { + checkServicesStatus(res, config); + }); + }, + 1000 + ); + } + }; + + /** + * Shows the modal with the no services message. + */ + const showNoServicesModal = () => { + utilityService.hideSpinner(); + if (!noServicesModal) { + noServicesModal = new Packlink.modalService({ + title: translator.translate('shippingServices.failedGettingServicesTitle'), + content: '

' + translator.translate('shippingServices.failedGettingServicesSubtitle') + '

', + canClose: false, + buttons: [ + { + title: translator.translate('shippingServices.retry'), + primary: true, + onClick: () => { + startAutoConfigure(); + } + }, + { + title: translator.translate('general.cancel'), + onClick: () => { + hideNoServicesModal(); + state.goToState('my-shipping-services'); + } + }, + ] + }); + } + + noServicesModal.open(); + }; + + /** + * Shows the block with the no services message. + */ + const hideNoServicesModal = () => { + noServicesModal.close(); + }; + + /** + * Starts the auto-configure process. + */ + const startAutoConfigure = () => { + hideNoServicesModal(); + utilityService.showSpinner(); + ajaxService.get( + configuration.startAutoConfigureUrl, + (response) => { + if (response.success) { + hideNoServicesModal(); + ajaxService.get(configuration.getTaskStatusUrl, checkServicesStatus); + } else { + showNoServicesModal(); + } + }, + showNoServicesModal + ); + }; + + /** + * Binds services. + * + * @param {ShippingService[]} services + * @param {{from: string, newService: boolean}} config + */ + const bindServices = (services, config) => { + shippingServices = services; + applyFilter(); + + ajaxService.get(configuration.getActiveServicesUrl, (activeServices) => { + if (config && config.from === 'edit') { + if (config.newService === true && activeServices.length === 1 && configuration.disableCarriersUrl) { + displayDisableShopServicesModal(); + } else { + const modal = new Packlink.modalService({ + content: templateService.replaceResourcesUrl( + '
' + + '' + + '

' + + translator.translate('shippingServices.addedSuccessTitle') + + '

' + + '

' + + translator.translate('shippingServices.addedSuccessDescription') + + '

' + + '
' + ) + }); + + modal.open(); + } + } + + utilityService.hideSpinner(); + }); + }; + + /** + * Filters services based on the selected filter. + */ + const applyFilter = () => { + setSelectedFiltersToPage(); + const filteredServices = filterServices(), + table = templateService.getComponent('pl-shipping-services-table'), + list = templateService.getComponent('pl-shipping-services-list').querySelector('.pl-shipping-services-list'), + render = (elem, id, tag) => { + Packlink.ShippingServicesRenderer.render(elem, id, tag, filteredServices, false, handleServiceAction); + }; + + render(table.querySelector('tbody'), 'pl-shipping-services-row', 'tr'); + render(list, 'pl-shipping-services-list-item', 'div'); + // noinspection JSCheckFunctionSignatures + Packlink.GridResizerService.init(table); + }; + + /** + * Handles a shipping service action button click. + * + * @param {string} serviceId + */ + const handleServiceAction = (serviceId) => { + state.goToState('edit-service', {id: serviceId, fromPick: true}); + }; + + /** + * Applies the filter to the list of services. + * + * @return {ShippingService[]} + */ + const filterServices = () => { + return shippingServices.filter((service) => { + for (const filter in appliedFilter) { + if (appliedFilter.hasOwnProperty(filter) && appliedFilter[filter] + && service.hasOwnProperty(filter) && service[filter] !== appliedFilter[filter] + ) { + return false; + } + } + + return true; + }); + }; + + const showFilterModal = () => { + // noinspection JSCheckFunctionSignatures + const modal = new Packlink.modalService({ + title: translator.translate('shippingServices.filterModalTitle'), + content: '
' + templateService.getMainPage().querySelector('.pl-services-filter-wrapper').innerHTML + '
', + buttons: [ + { + title: translator.translate('shippingServices.applyFilters'), + primary: true, + onClick: (event) => { + applyModalFilter(event.target.parentElement.parentElement); + modal.close(); + } + } + ], + onOpen: filterModalOpened + }); + + desktopMode = false; + modal.open(); + }; + + /** + * Handles an event when filter modal is opened. + * + * @param {HTMLElement} modal + */ + const filterModalOpened = (modal) => { + modal.querySelectorAll('.pl-filter-option').forEach((btn) => { + setButtonFromFilter(btn); + btn.addEventListener('click', () => filterButtonClicked(btn)); + }); + + modal.querySelector('.pl-filter-selected').remove(); + }; + + /** + * Sets the filter option from the given button. + * + * @param {HTMLDivElement} btn + */ + const setFilterFromButton = (btn) => { + const parent = btn.parentElement, + otherBtn = parent.querySelector(':not([data-option=' + btn.dataset.option + '])'); + + if (btn.classList.contains('pl-selected')) { + appliedFilter[btn.dataset.filter] = btn.dataset.option; + } else if (otherBtn.classList.contains('pl-selected')) { + appliedFilter[btn.dataset.filter] = otherBtn.dataset.option; + } else { + appliedFilter[btn.dataset.filter] = null; + } + }; + + /** + * Sets the button class from the filter. + * + * @param {HTMLDivElement} btn + */ + const setButtonFromFilter = (btn) => { + if (appliedFilter[btn.dataset.filter] === btn.dataset.option) { + btn.classList.add('pl-selected'); + } else { + btn.classList.remove('pl-selected'); + } + }; + + /** + * When modal is closed, display selected filter options above the services table. + */ + const setSelectedFiltersToPage = () => { + const placeholder = templateService.getMainPage().querySelector('.pl-services-filter-wrapper .pl-filter-selected'), + elem = placeholder.querySelector('.pl-filter-options'); + + elem.innerHTML = ''; + placeholder.classList.add('pl-hidden'); + + Object.keys(appliedFilter).forEach((filterType) => { + if (appliedFilter[filterType]) { + const newDiv = document.createElement('div'); + newDiv.classList.add('pl-filter-option'); + newDiv.classList.add('pl-selected'); + newDiv.dataset.filter = filterType; + newDiv.dataset.option = appliedFilter[filterType]; + newDiv.innerHTML = translator.translate('shippingServices.' + appliedFilter[filterType]); + newDiv.addEventListener('click', () => { + appliedFilter[filterType] = null; + newDiv.remove(); + setPageFilters(); + applyFilter(); + }); + + elem.appendChild(newDiv); + placeholder.classList.remove('pl-hidden'); + } + }); + }; + + /** + * Applies the filter from the modal. + * + * @param {HTMLElement} modal + */ + const applyModalFilter = (modal) => { + modal.querySelectorAll('.pl-filter-option').forEach(setFilterFromButton); + setPageFilters(); + + applyFilter(); + }; + + /** + * Sets the state of page filters from the applied filters. + */ + const setPageFilters = () => { + const pageFilters = templateService.getMainPage().querySelectorAll('.pl-services-filter-wrapper .pl-filter-option'); + pageFilters.forEach((btn) => { + btn.classList.add('pl-selected'); + setButtonFromFilter(btn); + }); + }; + + /** + * Handles click on a filter button. + * + * @param {HTMLDivElement} btn + */ + const filterButtonClicked = (btn) => { + const filter = btn.dataset.option; + + btn.parentElement.querySelector(':not([data-option=' + filter + '])').classList.remove('pl-selected'); + btn.classList.toggle('pl-selected'); + + if (desktopMode) { + setFilterFromButton(btn); + applyFilter(); + } + }; + + /** + * Displays the modal for disabling shop shipping services. + */ + const displayDisableShopServicesModal = () => { + const modal = new Packlink.modalService({ + canClose: false, + content: templateService.getTemplate('pl-disable-carriers-modal'), + buttons: [ + { + title: translator.translate('general.accept'), + primary: true, + onClick: () => { + utilityService.showSpinner(); + ajaxService.post( + configuration.disableCarriersUrl, + {}, + () => { + utilityService.hideSpinner(); + modal.close(); + }, + Packlink.responseService.errorHandler + ); + } + }, + { + title: translator.translate('general.cancel'), + onClick: () => { + modal.close(); + } + } + ] + }); + + modal.open(); + }; + } + + Packlink.PickShippingServiceController = PickShippingServiceController; +})(); diff --git a/src/BusinessLogic/Resources/js/PricePolicyController.js b/src/BusinessLogic/Resources/js/PricePolicyController.js new file mode 100644 index 00000000..d6aed4ff --- /dev/null +++ b/src/BusinessLogic/Resources/js/PricePolicyController.js @@ -0,0 +1,313 @@ +if (!window.Packlink) { + window.Packlink = {}; +} + +(function () { + /** + * @constructor + */ + function PricePolicyController() { + const templateService = Packlink.templateService, + utilityService = Packlink.utilityService, + translator = Packlink.translationService, + validationService = Packlink.validationService; + + /** + * @type ShippingService + */ + let serviceModel = {}; + + const pricingPolicyModelFields = [ + 'range_type', + 'from_weight', + 'to_weight', + 'from_price', + 'to_price', + 'pricing_policy', + 'increase', + 'change_percent', + 'fixed_price', + ]; + + const rangeTypes = { + 'price': '0', + 'weight': '1', + 'weightAndPrice': '2' + }; + + const pricingPolicies = { + 'packlink': '0', + 'percent': '1', + 'fixed': '2' + }; + + /** + * Displays page content. + * + * @param {{service: ShippingService, policyIndex: number|null, onSave: function(ShippingService)}} config + */ + this.display = function (config) { + serviceModel = config.service; + + const policyIndex = config.policyIndex, + currentPolicy = policyIndex !== null ? serviceModel.pricingPolicies[policyIndex] : null; + + // noinspection JSCheckFunctionSignatures + const modal = new Packlink.modalService({ + content: templateService.getTemplate('pl-pricing-policy-modal'), + canClose: false, + buttons: [ + { + title: translator.translate('general.save'), + primary: true, + onClick: () => { + savePricingPolicy(currentPolicy, policyIndex, modal, config.onSave); + } + }, + { + title: translator.translate('general.cancel'), + onClick: () => { + modal.close(); + } + } + ], + onOpen: () => { + setPricingPolicyInitialState(currentPolicy, policyIndex); + } + }); + + modal.open(); + }; + + /** + * Saves pricing policy + * @param {ShippingPricingPolicy | null} currentPolicy + * @param {number} policyIndex + * @param {ModalService} modal + * @param {function(ShippingService)} onSave + */ + const savePricingPolicy = (currentPolicy, policyIndex, modal, onSave) => { + const form = templateService.getComponent('pl-pricing-policy-form'); + if (!validatePricingPolicyForm(form)) { + return; + } + + let pricingPolicy = {}; + pricingPolicyModelFields.forEach(function (field) { + pricingPolicy[field] = form[field].value !== '' ? form[field].value : null; + }); + + pricingPolicy.increase = form['increase'].checked; + removeUnneededFieldsFromModel(pricingPolicy); + + if (currentPolicy === null) { + serviceModel.pricingPolicies.push(pricingPolicy); + } else { + serviceModel.pricingPolicies[policyIndex] = pricingPolicy; + } + + onSave(serviceModel); + modal.close(); + }; + + /** + * Removes not needed fields from model. + * + * @param {ShippingPricingPolicy} pricingPolicy + */ + const removeUnneededFieldsFromModel = (pricingPolicy) => { + if (pricingPolicy.range_type.toString() === rangeTypes.price) { + pricingPolicy.from_weight = null; + pricingPolicy.to_weight = null; + } else if (pricingPolicy.range_type.toString() === rangeTypes.weight) { + pricingPolicy.from_price = null; + pricingPolicy.to_price = null; + } + + if (pricingPolicy.pricing_policy.toString() !== pricingPolicies.percent) { + pricingPolicy.change_percent = null; + + } else if (pricingPolicy.pricing_policy.toString() !== pricingPolicies.fixed) { + pricingPolicy.fixed_price = null; + } + }; + + /** + * Sets pricing policy form initial state. + * + * @param {ShippingPricingPolicy | null} pricingPolicy + * @param {number} index + */ + const setPricingPolicyInitialState = (pricingPolicy, index) => { + let priceRangeSelect = templateService.getComponent('pl-range-type-select'); + let pricingPolicySelect = templateService.getComponent('pl-pricing-policy-select'); + let pricingPolicyForm = templateService.getComponent('pl-pricing-policy-form'); + let currentIndex = serviceModel.pricingPolicies.length + 1; + + if (pricingPolicy !== null) { + currentIndex = index + 1; + pricingPolicyForm['range_type'].value = pricingPolicy.range_type; + pricingPolicyForm['from_weight'].value = pricingPolicy.from_weight; + pricingPolicyForm['to_weight'].value = pricingPolicy.to_weight; + pricingPolicyForm['from_price'].value = pricingPolicy.from_price; + pricingPolicyForm['to_price'].value = pricingPolicy.to_price; + pricingPolicyForm['pricing_policy'].value = pricingPolicy.pricing_policy; + pricingPolicyForm['increase'].checked = pricingPolicy.increase; + pricingPolicyForm['change_percent'].value = pricingPolicy.change_percent; + pricingPolicyForm['fixed_price'].value = pricingPolicy.fixed_price; + } + + templateService.getComponent('pl-pricing-policy-title').innerHTML = + translator.translate('shippingServices.singlePricePolicy', [currentIndex]); + + setPriceRangeSection(); + setPricingPolicySection(); + setIncreaseToggle(); + priceRangeSelect.addEventListener('change', setPriceRangeSection); + pricingPolicySelect.addEventListener('change', setPricingPolicySection); + templateService.getComponent('pl-price-percentage-increase').addEventListener('click', (event) => { + handleIncreaseToggleChange(event, true); + }); + templateService.getComponent('pl-price-percentage-decrease').addEventListener('click', (event) => { + handleIncreaseToggleChange(event, false); + }); + + validationService.setFormValidation(pricingPolicyForm, pricingPolicyModelFields); + }; + + /** + * Sets price range section. + */ + const setPriceRangeSection = () => { + let priceRangeSelect = templateService.getComponent('pl-range-type-select'); + let fromToPriceWrapper = templateService.getComponent('pl-from-to-price-wrapper'); + let fromToWeightWrapper = templateService.getComponent('pl-from-to-weight-wrapper'); + + utilityService.showElement(fromToPriceWrapper); + utilityService.showElement(fromToWeightWrapper); + + if (priceRangeSelect.value === rangeTypes.price) { + utilityService.hideElement(fromToWeightWrapper); + } else if (priceRangeSelect.value === rangeTypes.weight) { + utilityService.hideElement(fromToPriceWrapper); + } + }; + + /** + * Sets pricing policy section. + */ + const setPricingPolicySection = () => { + let pricingPolicySelect = templateService.getComponent('pl-pricing-policy-select'); + let pricePercentageWrapper = templateService.getComponent('pl-price-percentage-wrapper'); + let fixedPriceWrapper = templateService.getComponent('pl-price-fixed-wrapper'); + + utilityService.hideElement(fixedPriceWrapper); + utilityService.hideElement(pricePercentageWrapper); + + if (pricingPolicySelect.value === pricingPolicies.percent) { + utilityService.showElement(pricePercentageWrapper); + } else if (pricingPolicySelect.value === pricingPolicies.fixed) { + utilityService.showElement(fixedPriceWrapper); + } + }; + + /** + * Sets increase toggle based on selected option. + */ + const setIncreaseToggle = () => { + const increaseButton = templateService.getComponent('pl-price-percentage-increase'); + const decreaseButton = templateService.getComponent('pl-price-percentage-decrease'); + const increaseElement = templateService.getComponent('pl-increase'); + increaseButton.classList.remove('pl-button-primary', 'pl-button-secondary'); + decreaseButton.classList.remove('pl-button-primary', 'pl-button-secondary'); + + if (increaseElement.checked) { + increaseButton.classList.add('pl-button-primary'); + decreaseButton.classList.add('pl-button-secondary'); + } else { + increaseButton.classList.add('pl-button-secondary'); + decreaseButton.classList.add('pl-button-primary'); + } + }; + + /** + * Toggle changed event handler. + * + * @param {Event} event + * @param {boolean} isChecked + * @returns {boolean} + */ + const handleIncreaseToggleChange = (event, isChecked) => { + templateService.getComponent('pl-increase').checked = isChecked; + setIncreaseToggle(); + event.preventDefault(); + return false; + }; + + /** + * Validates pricing policy form. + * + * @param {HTMLElement} pricingPolicy + * + * @returns {boolean} + */ + const validatePricingPolicyForm = (pricingPolicy) => { + const rangeType = pricingPolicy['range_type'].value, + currentPricingPolicy = pricingPolicy['pricing_policy'].value; + + if (!validateRange(rangeType, pricingPolicy['from_price'], pricingPolicy['to_price'], rangeTypes.price) || + !validateRange(rangeType, pricingPolicy['from_weight'], pricingPolicy['to_weight'], rangeTypes.weight) + ) { + return false; + } + + if (currentPricingPolicy === pricingPolicies.percent) { + if (!validationService.validateRequiredField(pricingPolicy['change_percent']) || + !validationService.validateNumber(pricingPolicy['change_percent'])) { + return false; + } + + if (!pricingPolicy['increase'].checked && pricingPolicy['change_percent'].value > 99) { + validationService.setError(pricingPolicy['change_percent'], translator.translate('validation.invalidMaxValue', [99])) + return false; + } + } + + return !(currentPricingPolicy === pricingPolicies.fixed + && (!validationService.validateRequiredField(pricingPolicy['fixed_price']) + || !validationService.validateNumber(pricingPolicy['fixed_price']))); + }; + + /** + * Validates the given range. + * + * @param {string} currentRangeType + * @param {HTMLInputElement} fromRange + * @param {HTMLInputElement} toRange + * @param {string} rangeTypeCondition + * @returns {boolean} + */ + const validateRange = (currentRangeType, fromRange, toRange, rangeTypeCondition) => { + if (currentRangeType === rangeTypeCondition || currentRangeType === rangeTypes.weightAndPrice) { + if (!validationService.validateRequiredField(fromRange) || + !validationService.validateNumber(fromRange) || + !validationService.validateNumber(toRange) + ) { + return false; + } + + if (parseFloat(fromRange.value) >= parseFloat(toRange.value)) { + validationService.setError( + toRange, + translator.translate('shippingServices.invalidRange') + ); + return false; + } + } + + return true; + }; + } + + Packlink.PricePolicyController = PricePolicyController; +})(); diff --git a/src/BusinessLogic/Resources/js/RegisterController.js b/src/BusinessLogic/Resources/js/RegisterController.js new file mode 100644 index 00000000..7ae525f5 --- /dev/null +++ b/src/BusinessLogic/Resources/js/RegisterController.js @@ -0,0 +1,193 @@ +if (!window.Packlink) { + window.Packlink = {}; +} + +(function () { + /** + * Handles register page logic. + * + * @param {{getRegistrationData: string, submit: string}} configuration + * @constructor + */ + function RegisterController(configuration) { + + const templateService = Packlink.templateService, + ajaxService = Packlink.ajaxService, + state = Packlink.state, + utilityService = Packlink.utilityService, + translationService = Packlink.translationService, + validationService = Packlink.validationService, + responseService = Packlink.responseService, + templateId = 'pl-register-page'; + + let form, + country; + + /** + * The main entry point for controller. + */ + this.display = (additionalConfig) => { + utilityService.showSpinner(); + templateService.setCurrentTemplate(templateId); + country = additionalConfig.hasOwnProperty('country') ? additionalConfig.country : 'ES'; + + let getDataUrl = new URL(configuration.getRegistrationData); + getDataUrl.searchParams.append('country', country); + ajaxService.get(getDataUrl.toString(), populateInitialValues); + + const registerPage = templateService.getMainPage(); + + form = templateService.getComponent('pl-register-form', registerPage); + form.addEventListener('submit', register); + + templateService.getComponent('pl-go-to-login', registerPage).addEventListener('click', goToLogin); + + templateService.getComponent('pl-register-platform-country', registerPage).value = + additionalConfig.hasOwnProperty('platform_country') ? additionalConfig.platform_country : 'ES'; + + initInputField('pl-register-email'); + initInputField('pl-register-password'); + initInputField('pl-register-phone'); + initInputField('pl-register-shipment-volume'); + initInputField('pl-register-terms-and-conditions'); + }; + + /** + * Populates initial values from the backend. + * + * @param {{ + * email: string, + * phone: string, + * source: string, + * termsAndConditionsUrl: string, + * privacyPolicyUrl: string + * }} response + */ + const populateInitialValues = (response) => { + const emailInput = templateService.getComponent('pl-register-email'), + phoneInput = templateService.getComponent('pl-register-phone'), + sourceInput = templateService.getComponent('pl-register-source'); + + emailInput.value = response.email; + phoneInput.value = response.phone; + sourceInput.value = response.source; + + let termsAndConditionsLabel = templateService.getComponent('pl-register-terms-and-conditions-label'), + termsTranslation = translationService.translate( + 'register.termsAndConditions', + [response.termsAndConditionsUrl, response.privacyPolicyUrl] + ); + + termsAndConditionsLabel.querySelector('label').innerHTML += termsTranslation; + utilityService.hideSpinner(); + }; + + /** + * Initializes the input field. Attaches proper event listeners. + * + * @param {string} componentSelector + */ + const initInputField = (componentSelector) => { + let input = templateService.getComponent(componentSelector); + + input.addEventListener('blur', () => { + validationService.validateInputField(input); + enableSubmit(); + }, true); + + input.addEventListener('input', () => { + validationService.removeError(input); + }); + + input.addEventListener('change', () => { + enableSubmit(); + if (componentSelector === 'pl-register-terms-and-conditions' && !input.checked) { + templateService.getComponent('pl-register-button').disabled = true; + } + }); + }; + + /** + * Redirects to login. + * + * @param {Event} event + * + * @returns {boolean} + */ + const goToLogin = (event) => { + event.preventDefault(); + + Packlink.state.goToState('login'); + + return false; + }; + + /** + * Enables or disables the submit button. + */ + const enableSubmit = () => { + let inputs = form.querySelectorAll('input,select'), + registerButton = templateService.getComponent('pl-register-button'); + + registerButton.disabled = false; + inputs.forEach((input) => { + if (input.hasAttribute('data-pl-contains-errors')) { + registerButton.disabled = true; + } + }); + }; + + const validateForm = () => { + validationService.validateForm(form); + + enableSubmit(); + }; + + /** + * Handles form submit. + * + * @param {Event} event + * @returns {boolean} + */ + const register = (event) => { + event.preventDefault(); + validateForm(); + + if (form.querySelectorAll('[data-pl-contains-errors]').length === 0) { + utilityService.showSpinner(); + ajaxService.post( + configuration.submit, + { + 'email': event.target['email'].value, + 'password': event.target['password'].value, + 'estimated_delivery_volume': event.target['estimated_delivery_volume'].value, + 'phone': event.target['phone'].value, + 'platform_country': event.target['platform_country'].value, + 'source': event.target['source'].value, + 'terms_and_conditions': !!event.target['terms_and_conditions'].checked, + 'marketing_emails': !!event.target['marketing_emails'].checked, + }, + successfulRegister, + responseService.errorHandler + ); + } + + return false; + }; + + /** + * Handles a successful registration request. + * + * @param {{success: boolean, message: string}} response + */ + const successfulRegister = (response) => { + if (response.success) { + state.goToState('onboarding-state'); + } else { + responseService.errorHandler(response); + } + }; + } + + Packlink.RegisterController = RegisterController; +})(); diff --git a/src/BusinessLogic/Resources/js/RegisterModalController.js b/src/BusinessLogic/Resources/js/RegisterModalController.js new file mode 100644 index 00000000..6fec3cfe --- /dev/null +++ b/src/BusinessLogic/Resources/js/RegisterModalController.js @@ -0,0 +1,111 @@ +if (!window.Packlink) { + window.Packlink = {}; +} + +(function () { + + /** + * Country object received from the back end. + * + * @typedef {{ + * name: string, + * code: string, + * postal_code: string, + * registration_link: string, + * platform_country: string + * }} Country + */ + + /** + * Controller that displays shipping country selector modal. + * + * @constructor + * @param {string} modalTemplateId + * @param {string} listOfCountriesUrl + */ + function RegisterModalController(modalTemplateId, listOfCountriesUrl) { + // noinspection JSCheckFunctionSignatures + const ajaxService = Packlink.ajaxService, + translator = Packlink.translationService, + templateService = Packlink.templateService, + utilityService = Packlink.utilityService, + modal = new Packlink.modalService({ + title: translator.translate('register.chooseYourCountry'), + content: Packlink.templateService.getTemplate('pl-register-modal'), + onOpen: (modal) => { + ajaxService.get(listOfCountriesUrl, (response) => { + populateCountryList(modal, response); + }); + } + }); + + /** + * Displays countries in the modal body. + * + * @param {HTMLElement} modal + * @param {Country[]} response + */ + const populateCountryList = (modal, response) => { + let countryList = modal.querySelector('.pl-register-country-list-wrapper'), + template = countryList.querySelector('#country-template'), + countryFilter = modal.querySelector('#pl-country-filter'); + + countryFilter.addEventListener('input', filterCountries); + + response.forEach((country) => { + let countryElement = document.createElement('div'); + + countryElement.innerHTML = template.innerHTML.replace('$code', country.code) + .replace('logo_url', templateService.replaceResourcesUrl('{$BASE_URL$}/images/flags/' + country.code + '.svg')) + .replace(/country_name/g, country.name); + + countryElement.firstElementChild.addEventListener('click', () => handleCountrySelected(country)); + countryList.appendChild(countryElement.firstElementChild); + }); + + utilityService.hideSpinner(); + }; + + /** + * Handles click on a country. + * + * @param {Country} country + */ + const handleCountrySelected = (country) => { + modal.close(); + Packlink.state.goToState('register', {country: country.code, platform_country: country.platform_country}); + }; + + /** + * Filters country list on user input. + * + * @param event + */ + const filterCountries = (event) => { + let filter = event.target.value.toLowerCase(); + + let countries = document.querySelectorAll('.pl-register-country-list-wrapper .pl-country'); + + countries.forEach((country) => { + if (filter === '' + || country.dataset.code.toLowerCase().startsWith(filter) + || country.querySelector('.pl-country-name').innerText.toLowerCase().search(filter) !== -1 + ) { + country.classList.remove('pl-hidden'); + } else { + country.classList.add('pl-hidden'); + } + }); + }; + + /** + * The main entry point for controller. + */ + this.display = () => { + utilityService.showSpinner(); + modal.open(); + }; + } + + Packlink.RegisterModalController = RegisterModalController; +})(); diff --git a/src/BusinessLogic/Resources/js/ResponseService.js b/src/BusinessLogic/Resources/js/ResponseService.js new file mode 100644 index 00000000..0defc812 --- /dev/null +++ b/src/BusinessLogic/Resources/js/ResponseService.js @@ -0,0 +1,33 @@ +if (!window.Packlink) { + window.Packlink = {}; +} + +(function () { + /** + * The ResponseService constructor. + * + * @constructor + */ + function ResponseService() { + const utilityService = Packlink.utilityService, + validationService = Packlink.validationService; + + /** + * Handles an error response from the submit action. + * + * @param {{success: boolean, error?: string, messages?: ValidationMessage[]}} response + */ + this.errorHandler = (response) => { + utilityService.hideSpinner(); + if (response.error) { + utilityService.showFlashMessage(response.error, 'danger', 7000); + } else if (response.messages) { + validationService.handleValidationErrors(response.messages); + } else { + utilityService.showFlashMessage('Unknown error occurred.', 'danger', 7000); + } + }; + } + + Packlink.responseService = new ResponseService(); +})(); diff --git a/src/BusinessLogic/Resources/js/ServiceCountriesModalController.js b/src/BusinessLogic/Resources/js/ServiceCountriesModalController.js new file mode 100644 index 00000000..12ca89bc --- /dev/null +++ b/src/BusinessLogic/Resources/js/ServiceCountriesModalController.js @@ -0,0 +1,220 @@ +if (!window.Packlink) { + window.Packlink = {}; +} + +(function () { + /** + * @typedef ServiceCountriesModalControllerConfiguration + * @property {string} getCountriesListUrl + */ + + /** + * @param {ServiceCountriesModalControllerConfiguration} configuration + * @constructor + */ + function ServiceCountriesModalController(configuration) { + const templateService = Packlink.templateService, + ajaxService = Packlink.ajaxService, + utilityService = Packlink.utilityService, + translator = Packlink.translationService; + + /** + * @type ShippingService + */ + let serviceModel = {}; + + /** + * Displays page content. + * + * @param {{service: ShippingService, onSave: function(ShippingService)}} config + */ + this.display = (config) => { + utilityService.showSpinner(); + serviceModel = config.service; + const modal = new Packlink.modalService({ + content: templateService.getTemplate('pl-countries-selection-modal'), + canClose: false, + fullWidthBody: true, + title: translator.translate('shippingServices.selectCountriesHeader'), + buttons: [ + { + title: translator.translate('general.accept'), + primary: true, + onClick: () => { + saveCountriesSelection(modal, config.onSave); + } + }, + { + title: translator.translate('general.cancel'), + onClick: () => { + modal.close(); + } + } + ], + onOpen: () => { + ajaxService.get(configuration.getCountriesListUrl, setCountriesSelectionInitialState); + } + }); + + modal.open(); + }; + + /** + * Saves countries selection. + * + * @param {ModalService} modal + * @param {function(ShippingService)} onSave + */ + const saveCountriesSelection = (modal, onSave) => { + const countriesSelectionForm = templateService.getComponent('pl-countries-selection-form'), + allCountries = countriesSelectionForm.querySelectorAll('.pl-shipping-country-selection-wrapper input'), + selectedCountries = countriesSelectionForm.querySelectorAll('.pl-shipping-country-selection-wrapper input:checked'), + isShipToAllCountries = allCountries.length === selectedCountries.length; + + if (!isShipToAllCountries) { + if (selectedCountries.length === 0) { + showValidationMessage(); + return; + } + + serviceModel.shippingCountries = []; + selectedCountries.forEach( + (input) => { + serviceModel.shippingCountries.push(input.name); + } + ); + } + + serviceModel.isShipToAllCountries = isShipToAllCountries; + + onSave(serviceModel); + modal.close(); + }; + + /** + * Sets countries selection initial state. + * + * @param {[{value: string, label: string}]} listOfCountries + */ + const setCountriesSelectionInitialState = (listOfCountries) => { + const shippingCountryWrapper = templateService.getComponent('pl-shipping-country-selection-wrapper'); + shippingCountryWrapper.innerHTML = ''; + listOfCountries.forEach((country) => { + shippingCountryWrapper.innerHTML += '
' + + '' + + '' + + '
'; + }); + + const countriesSelectionForm = templateService.getComponent('pl-countries-selection-form'), + countryInputs = countriesSelectionForm.querySelectorAll('.pl-shipping-country-selection-wrapper input'); + + countriesSelectionForm['isShipToAllCountries'].checked = serviceModel.isShipToAllCountries; + + if (serviceModel.isShipToAllCountries) { + countryInputs.forEach((input) => { + input.checked = true; + }); + } else { + serviceModel.shippingCountries.forEach((country) => { + const cBox = countriesSelectionForm.querySelector('#pl-' + country); + if (cBox) { + cBox.checked = true; + } + }); + + handleCountrySelectionChanged(); + } + + setCountryChangeEvents(countryInputs); + setShipToAllCountriesChangeEvent(countriesSelectionForm); + utilityService.hideSpinner(); + }; + + /** + * Handles country selection changed. + */ + const handleCountrySelectionChanged = () => { + const countriesSelectionForm = templateService.getComponent('pl-countries-selection-form'); + const selectedCountries = countriesSelectionForm.querySelectorAll('.pl-shipping-country-selection-wrapper input:checked'); + const label = templateService.getComponent('pl-check-all-countries'); + const countryInputs = countriesSelectionForm.querySelectorAll('.pl-shipping-country-selection-wrapper input'); + countriesSelectionForm['isShipToAllCountries'].checked = selectedCountries.length > 0; + + if (selectedCountries.length === countryInputs.length || selectedCountries.length === 0) { + label.innerHTML = translator.translate('shippingServices.selectAllCountries'); + } else if (selectedCountries.length === 1) { + label.innerHTML = translator.translate('shippingServices.oneCountrySelected'); + } else { + label.innerHTML = translator.translate('shippingServices.selectedCountries', [selectedCountries.length]); + } + }; + + /** + * Sets event listeners for country change. + * + * @param {[HTMLInputElement]}countryInputs + */ + const setCountryChangeEvents = (countryInputs) => { + countryInputs.forEach((input) => { + markSelectedCountry(input); + + input.addEventListener('change', () => { + markSelectedCountry(input); + handleCountrySelectionChanged(); + }); + }); + }; + + /** + * Sets event listener for shipToAllCountries checkbox. + * + * @param {HTMLElement} countriesSelectionForm + */ + const setShipToAllCountriesChangeEvent = (countriesSelectionForm) => { + countriesSelectionForm['isShipToAllCountries'].addEventListener('change', (event) => { + const label = templateService.getComponent('pl-check-all-countries'); + countriesSelectionForm.querySelectorAll('.pl-shipping-country-selection-wrapper input').forEach((input) => { + input.checked = event.target.checked; + if (!input.checked) { + label.innerHTML = translator.translate('shippingServices.selectAllCountries'); + } + markSelectedCountry(input); + }); + }); + }; + + /** + * Marks selected country. + * + * @param {HTMLInputElement} input + */ + const markSelectedCountry = (input) => { + let inputWrapper = input.parentElement; + + inputWrapper.classList.remove('pl-shipping-country-selected'); + if (input.checked) { + inputWrapper.classList.add('pl-shipping-country-selected'); + } + }; + + const showValidationMessage = () => { + const errorMsg = templateService.getComponent('pl-countries-alert-wrapper'); + errorMsg.querySelector('.material-icons').addEventListener('click', hideValidationMessage); + hideValidationMessage(); + errorMsg.classList.add('visible'); + setTimeout(hideValidationMessage, 7000); + }; + + const hideValidationMessage = () => { + const errorMsg = templateService.getComponent('pl-countries-alert-wrapper'); + if (errorMsg) { + errorMsg.classList.remove('visible'); + } + } + } + + Packlink.ServiceCountriesModalController = ServiceCountriesModalController; +})(); diff --git a/src/BusinessLogic/Resources/js/ShippingMethodsController.js b/src/BusinessLogic/Resources/js/ShippingMethodsController.js deleted file mode 100644 index 9bf7edcb..00000000 --- a/src/BusinessLogic/Resources/js/ShippingMethodsController.js +++ /dev/null @@ -1,1829 +0,0 @@ -var Packlink = window.Packlink || {}; - -(function () { - function ShippingMethodsController(configuration) { - const PRICING_POLICY_PACKLINK = 1; - const PRICING_POLICY_PERCENT = 2; - const PRICING_POLICY_FIXED_BY_WEIGHT = 3; - const PRICING_POLICY_FIXED_BY_VALUE = 4; - - const STATUSES = [ - 'disabled', - 'in-progress', - 'completed' - ]; - - let templateService = Packlink.templateService; - let utilityService = Packlink.utilityService; - let ajaxService = Packlink.ajaxService; - let state = Packlink.state; - - let isDashboardShown = false; - - let selectedId = null; - - let currentNavTab = 'all'; - - let spinnerBarrierCount = 0; - let spinnerBarrier = getSpinnerBarrier(); - - /** @var {{isParcelSet, isWarehouseSet, isShippingMethodSet}} dashboardData */ - let dashboardData = {}; - - /** - * @var {{id, name, title, logoUrl, taxClass, deliveryDescription, showLogo, deliveryType, - * parcelDestination, parcelOrigin, selected, - * pricePolicy, fixedPriceByWeightPolicy, fixedPriceByValuePolicy, percentPricePolicy, - * isShipToAllCountries, shippingCountries}} methodModel - */ - let methodModel = {}; - let taxClasses = []; - - let filters = { - title: [], - deliveryType: [], - parcelOrigin: [], - parcelDestination: [] - }; - - // Element in DOM where shipping methods page content is inserted. - let extensionPoint; - - let filtersExtensionPoint; - let navExtensionPoint; - let resultExtensionPoint; - let tableExtensionPoint; - let tableRowExtensionPoint; - let taxSelector = null; - - let shippingMethods = {}; - - let shippingMethodTemplates = {}; - - let renderedShippingMethods = []; - - let autoConfigureInitialized = false; - - let countrySelector = {}; - let availableCountries = []; - - /** - * Displays page content. - */ - this.display = function () { - utilityService.showSpinner(); - - extensionPoint = templateService.setTemplate('pl-shipping-methods-page-template'); - - filtersExtensionPoint = addStaticComponent( - 'pl-shipping-methods-filters-template', - 'pl-shipping-methods-filters-extension-point', - 'data-pl-shipping-methods-filter', - shippingMethodFilterClickHandler - ); - - navExtensionPoint = addStaticComponent( - 'pl-shipping-methods-nav-template', - 'pl-shipping-methods-nav-extension-point', - 'data-pl-shipping-methods-nav-button', - shippingMethodNavigationButtonClickHandler - ); - - resultExtensionPoint = addStaticComponent( - 'pl-shipping-methods-result-template', - 'pl-shipping-methods-result-extension-point' - ); - - tableExtensionPoint = addStaticComponent( - 'pl-shipping-methods-table-template', - 'pl-shipping-methods-table-extension-point' - ); - - tableRowExtensionPoint = templateService.getComponent( - 'pl-shipping-method-table-row-extension-point', - tableRowExtensionPoint - ); - - templateService.getComponent('pl-disable-methods-modal-cancel').addEventListener( - 'click', - hideDisableShopShippingMethodsModal - ); - - templateService.getComponent('pl-disable-methods-modal-accept').addEventListener( - 'click', - function () { - utilityService.showSpinner(); - ajaxService.get( - configuration.disableShopShippingMethodsUrl, - methodsDisabledSuccessCallback, - methodsDisabledFailedCallback - ); - } - ); - - if (configuration.hasTaxConfiguration) { - ajaxService.get(configuration.getTaxClassesUrl, getTaxClassesSuccessHandler); - } - - if (configuration.hasCountryConfiguration) { - ajaxService.get(configuration.getShippingCountries, getShippingCountriesHandler); - } - - ajaxService.get(configuration.getDashboardStatusUrl, function (response) { - getStatusHandler(response); - ajaxService.get(configuration.getMethodsStatusUrl, getShippingMethodsStatusHandler); - }); - }; - - /** - * Get status of getting shipping methods task. - * - * @param {object} response - */ - function getShippingMethodsStatusHandler(response) { - if (configuration.context !== state.getContext()) { - return; - } - - if (response.status === 'completed') { - ajaxService.get(configuration.getAllMethodsUrl, getShippingMethodsHandler); - - return; - } - - if (response.status === 'failed') { - hideDashboardModal(true); - showNoShippingMethodsMessage(); - - return; - } - - setTimeout( - function () { - ajaxService.get(configuration.getMethodsStatusUrl, getShippingMethodsStatusHandler); - }, - 1000 - ); - } - - /** - * Get all shipping methods callback. - * - * @param {array} response - */ - function getShippingMethodsHandler(response) { - if (configuration.context !== state.getContext()) { - return; - } - - for (let method of response) { - shippingMethods[method['id']] = method; - } - - if (response.length === 0) { - hideDashboardModal(true); - showNoShippingMethodsMessage(); - - return; - } - - hideGettingShippingMethodsMessage(); - renderShippingMethods(); - } - - /** - * Shows message when getting shipping services. - */ - function showGettingShippingMethodsMessage() { - utilityService.enableInputMask(); - templateService.getComponent('pl-getting-shipping-services', extensionPoint).classList.remove('hidden'); - } - - /** - * Hides message when shipping services are available. - */ - function hideGettingShippingMethodsMessage() { - utilityService.disableInputMask(); - templateService.getComponent('pl-getting-shipping-services', extensionPoint).classList.add('hidden'); - } - - /** - * Shows message when shipping services cannot be fetched. - */ - function showNoShippingMethodsMessage() { - let container = templateService.getComponent('pl-no-shipping-services', extensionPoint); - utilityService.hideSpinner(); - hideGettingShippingMethodsMessage(); - if (container) { - container.classList.remove('hidden'); - if (!autoConfigureInitialized) { - initAutoConfigure(container); - } - } - } - - /** - * Hides message when shipping services cannot be fetched. - */ - function hideNoShippingMethodsMessage() { - templateService.getComponent('pl-no-shipping-services', extensionPoint).classList.add('hidden'); - showGettingShippingMethodsMessage(); - } - - /** - * Initializes the auto-configure process. - * - * @param {Element} [container] - */ - function initAutoConfigure(container) { - let configureButton = templateService.getComponent('pl-shipping-services-retry-btn', container); - if (configureButton && configuration.autoConfigureStartUrl) { - autoConfigureInitialized = true; - configureButton.addEventListener('click', startAutoConfigure); - } - } - - /** - * Starts the auto-configure process. - */ - function startAutoConfigure() { - hideNoShippingMethodsMessage(); - ajaxService.post( - configuration.autoConfigureStartUrl, - [], - function success(response) { - if (response.success === true) { - ajaxService.get(configuration.getMethodsStatusUrl, getShippingMethodsStatusHandler); - } else { - showNoShippingMethodsMessage(); - } - }, - function error() { - showNoShippingMethodsMessage(); - } - ); - } - - /** - * Get setup status callback. - * - * @param {object} response - */ - function getStatusHandler(response) { - if (configuration.context !== state.getContext()) { - return; - } - - dashboardData = response; - - initSteps(); - - if (!dashboardData.isParcelSet || !dashboardData.isWarehouseSet || !dashboardData.isShippingMethodSet) { - showDashboardModal(); - } else { - hideDashboardModal(); - } - - templateService.getComponent('pl-dashboard-modal-wrapper', extensionPoint).addEventListener( - 'click', - hideDashboardModal - ); - - templateService.getComponent('pl-dashboard-modal', extensionPoint).addEventListener( - 'click', - function (event) { - event.stopPropagation(); - } - ); - - if (spinnerBarrier === ++spinnerBarrierCount) { - utilityService.hideSpinner(); - } - } - - /** - * Adds static component to shipping methods page. - * - * @param {string} template - * @param {string} point - * @param {string} [actionElementIdentifier] - * @param {function} [actionCallback] - * - * @return {Element} - */ - function addStaticComponent(template, point, actionElementIdentifier, actionCallback) { - let staticComponentExtensionPoint = templateService.setTemplate( - template, - point - ); - - if (typeof actionElementIdentifier !== 'undefined') { - let actionElements = templateService.getComponentsByAttribute( - actionElementIdentifier, - staticComponentExtensionPoint - ); - - for (let actionElement of actionElements) { - actionElement.addEventListener('click', actionCallback, true); - } - } - - return staticComponentExtensionPoint; - } - - /** - * Handles click event on shipping method filter. - * - * @param event - */ - function shippingMethodFilterClickHandler(event) { - let filter = event.target.getAttribute('data-pl-shipping-methods-filter'); - let filterParts = filter.split('-'); - let filterType = filterParts[0]; - let filterValue = filterParts[1]; - let filterIndex = filters[filterType].indexOf(filterValue); - - if (filterIndex === -1) { - filters[filterType].push(filterValue); - } else { - filters[filterType].splice(filterIndex, 1); - } - - renderShippingMethods(); - } - - /** - * Performs filtering of shipping methods. - */ - function filterMethods() { - renderedShippingMethods = []; - let filterTypes = Object.keys(filters); - let result = {}; - - for (let t of filterTypes) { - result[t] = []; - if (filters[t].length === 0) { - result[t] = Object.keys(shippingMethods); - continue; - } - - for (let f of filters[t]) { - result[t].push(...applyFilter(t, f)); - } - } - - renderedShippingMethods = result[filterTypes[0]]; - // Finds intersection of results; - for (let i = 1; i < filterTypes.length; i++) { - renderedShippingMethods = renderedShippingMethods.filter( - function (item) { - return result[filterTypes[i]].indexOf(item) !== -1; - } - ); - } - - // Take only unique values. Rendered shipping methods variable has to behave like a set. - // Also take current selection tab into consideration. - renderedShippingMethods = renderedShippingMethods.filter( - function (r, index, set) { - let shippingMethod = shippingMethods[r]; - let selectRequired = currentNavTab === 'selected'; - - return (set.indexOf(r) === index) && (selectRequired && shippingMethod.selected || !selectRequired); - } - ); - } - - /** - * Returns ids of shipping methods that fulfill specific filter requirement. - * - * @param {string} type - * @param {string} filter - * - * @return {array} - */ - function applyFilter(type, filter) { - let methods = Object.keys(shippingMethods); - - return methods.filter(function (method) { - let shippingMethod = shippingMethods[method]; - - return (shippingMethod[type] === filter); - }); - } - - /** - * Handles click event on shipping method navigation button. - * - * @param event - */ - function shippingMethodNavigationButtonClickHandler(event) { - let navButton = event.target; - let tab = navButton.getAttribute('data-pl-shipping-methods-nav-button'); - - if (tab === currentNavTab) { - return; - } - - navButton.classList.add('selected'); - let oldTab = templateService.getComponent( - 'data-pl-shipping-methods-nav-button', - navExtensionPoint, - currentNavTab - ); - - oldTab.classList.remove('selected'); - - currentNavTab = tab; - renderShippingMethods(); - } - - /** - * Renders shipping methods. - */ - function renderShippingMethods() { - filterMethods(); - - templateService.clearComponent(tableRowExtensionPoint); - let numberOfSelectedMethods = 0; - - for (let shippingMethod of renderedShippingMethods) { - let row = templateService.getTemplate('pl-shipping-methods-row-template')[0]; - row.setAttribute('id', 'pl-shipping-method-row-' + shippingMethod); - constructRow(shippingMethod, row); - tableRowExtensionPoint.appendChild(row); - shippingMethodTemplates[shippingMethod] = row; - if (shippingMethods[shippingMethod].selected) { - numberOfSelectedMethods++; - } - } - - let renderedIndicator = templateService.getComponent('pl-number-showed-methods', resultExtensionPoint); - renderedIndicator.innerHTML = renderedShippingMethods.length; - } - - /** - * Fills row template with concrete information such as title etc. - * Also, attaches proper event handlers to actionable elements of row template. - * - * @param {int} id - * @param {Element} template - */ - function constructRow(id, template) { - let shippingMethod = shippingMethods[id]; - - let name = templateService.getComponent('pl-shipping-method-name', template); - name.innerHTML = shippingMethod.name; - - let selectButton = templateService.getComponent('pl-shipping-method-select-btn', template); - selectButton.setAttribute('data-pl-shipping-method-id', id.toString()); - selectButton.addEventListener('click', handleShippingMethodSelectClicked, true); - - templateService.getComponent('pl-logo', template).setAttribute('src', shippingMethod.logoUrl); - - if (shippingMethod.selected) { - selectButton.classList.add('selected'); - } - - let configButton = templateService.getComponent('pl-shipping-method-config-btn', template); - configButton.setAttribute('data-pl-shipping-method-id', id.toString()); - configButton.addEventListener('click', handleShippingMethodConfigClicked, true); - - if (shippingMethod.parcelOrigin === 'pickup') { - templateService.getComponent('pl-pudo-icon-origin', template).classList.add('pl-pickup'); - } - - if (shippingMethod.parcelDestination === 'home') { - templateService.getComponent('pl-pudo-icon-dest', template).classList.add('pl-pickup'); - } - - if (shippingMethod.title === 'national') { - templateService.getComponent('pl-method-title', template).classList.add('pl-national'); - } - - templateService.getComponent('pl-delivery-type', template).innerHTML = shippingMethod.deliveryDescription; - - initPriceIndicators(shippingMethod, template); - } - - /** - * Displays proper pricing indicator. - * - * @param shippingMethod - * @param template - */ - function initPriceIndicators(shippingMethod, template) { - let indicator = 'packlink'; - - if (shippingMethod.pricePolicy === PRICING_POLICY_PERCENT) { - indicator = 'percent'; - } else if (shippingMethod.pricePolicy === PRICING_POLICY_FIXED_BY_WEIGHT) { - indicator = 'fixed-weight'; - } else if (shippingMethod.pricePolicy === PRICING_POLICY_FIXED_BY_VALUE) { - indicator = 'fixed-value'; - } - - templateService.getComponent('data-pl-price-indicator', template, indicator).classList.add('selected'); - } - - /** - * Handles shipping method select switch click event. - * - * @param event - */ - function handleShippingMethodSelectClicked(event) { - utilityService.showSpinner(); - - selectedId = event.target.getAttribute('data-pl-shipping-method-id'); - - if (!shippingMethods[selectedId].selected) { - ajaxService.post(configuration.activateUrl, {id: selectedId}, handleActivateSuccess, handleActivateError); - } else { - ajaxService.post(configuration.deactivateUrl, {id: selectedId}, handleActivateSuccess, handleActivateError); - } - - renderShippingMethods(); - } - - /** - * Shipping method activate/deactivate success callback. - * - * @param response - */ - function handleActivateSuccess(response) { - shippingMethods[selectedId].selected = !shippingMethods[selectedId].selected; - - if (response.message) { - utilityService.showFlashMessage(response.message, 'success'); - } - - if (shippingMethods[selectedId].selected && shouldGetShopShippingMethodCount()) { - getShopShippingMethodCount(); - } else { - utilityService.hideSpinner(); - } - - selectedId = null; - renderShippingMethods(); - } - - /** - * Retrieves number of active shipping methods. - * - * @return {number} - */ - function getNumberOfActiveServices() { - let result = 0; - let serviceIds = Object.keys(shippingMethods); - - for (let id of serviceIds) { - if (shippingMethods[id].selected) { - result++; - } - } - - return result; - } - - /** - * Shipping method activate/deactivate success callback. - * - * @param response - */ - function handleActivateError(response) { - selectedId = null; - - if (response.message) { - utilityService.showFlashMessage(response.message, 'danger'); - } - - utilityService.hideSpinner(); - } - - /** - * Handles successful retrieval of shop shipping method count. - * - * @param response - */ - function getShopShippingMethodsCountCallback(response) { - if (typeof response.count === 'number' && response.count !== 0) { - showDisableShopShippingMethodsModal(); - } - - utilityService.hideSpinner(); - } - - /** - * Shows disable shipping methods modal. - */ - function showDisableShopShippingMethodsModal() { - templateService.getComponent('pl-disable-methods-modal-wrapper', extensionPoint).classList.remove('hidden'); - } - - /** - * Hides disable shipping methods modal. - */ - function hideDisableShopShippingMethodsModal() { - templateService.getComponent('pl-disable-methods-modal-wrapper', extensionPoint).classList.add('hidden'); - } - - /** - * Handles successfully disabling shop shipping methods. - * - * @param response - */ - function methodsDisabledSuccessCallback(response) { - hideDisableShopShippingMethodsModal(); - - if (response && response.message) { - utilityService.showFlashMessage(response.message, 'success'); - } - - utilityService.hideSpinner(); - } - - /** - * Handles error during disabling of shop shipping methods. - * - * @param response - */ - function methodsDisabledFailedCallback(response) { - hideDisableShopShippingMethodsModal(); - - if (response && response.message) { - utilityService.showFlashMessage(response.message, 'danger'); - } - - utilityService.hideSpinner(); - } - - /** - * Handles event when shipping method configuration is clicked. - * - * @param event - */ - function handleShippingMethodConfigClicked(event) { - let configTemplate = templateService.getTemplate('pl-shipping-method-configuration-template')[0]; - let methodId = parseInt(event.target.getAttribute('data-pl-shipping-method-id')); - shippingMethodTemplates[methodId].after(configTemplate); - configTemplate.setAttribute('id', 'pl-shipping-method-config-form'); - - constructShippingMethodConfigForm(methodId, configTemplate); - scrollConfigForm(methodId); - - utilityService.enableInputMask(); - utilityService.configureInputElements(); - } - - /** - * Fills config template with concrete information such as title etc. - * Also, attaches proper event handlers to actionable elements of a config form. - * - * @param {int} id - * @param {Element} template - */ - function constructShippingMethodConfigForm(id, template) { - methodModel = utilityService.cloneObject(shippingMethods[id]); - templateService.getComponent('pl-method-title-input', template).value = methodModel.name; - - if (configuration.hasTaxConfiguration - && methodModel.taxClass !== null - && classExists(methodModel.taxClass) - ) { - templateService.getComponent('pl-tax-selector', template).value = methodModel.taxClass; - } - - if (methodModel.pricePolicy === PRICING_POLICY_FIXED_BY_WEIGHT) { - displayFixedPricesSubForm(false, false, true, true, true); - } else if (methodModel.pricePolicy === PRICING_POLICY_FIXED_BY_VALUE) { - displayFixedPricesSubForm(false, false, true, true, false); - } else if (methodModel.pricePolicy === PRICING_POLICY_PERCENT) { - displayPercentForm(true); - } - - templateService.getComponent('pl-shipping-method-config-cancel-btn', template).addEventListener( - 'click', - handleShippingMethodCancelClicked, - true - ); - - templateService.getComponent('pl-shipping-method-config-save-btn', template).addEventListener( - 'click', - handleShippingMethodSaveClicked, - true - ); - - templateService.getComponent('pl-method-title-input', template).addEventListener( - 'blur', - handleMethodNameChanged, - true - ); - - if ( - typeof configuration.canDisplayCarrierLogos === 'undefined' - || configuration.canDisplayCarrierLogos === true - ) { - let showLogoCheckbox = templateService.getComponent('pl-show-logo', template); - showLogoCheckbox.addEventListener('click', handleShowLogoChanged, true); - showLogoCheckbox.checked = methodModel.showLogo; - } - - let pricingPolicySelector = templateService.getComponent('pl-pricing-policy-selector', template); - pricingPolicySelector.addEventListener('change', handleShippingMethodPricingPolicyChanged, true); - pricingPolicySelector.value = methodModel.pricePolicy; - - if (configuration.hasCountryConfiguration) { - initializeCountryCountrySelector(methodModel, template); - } - } - - /** - * Initializes shipping country selector - * - * @param {object} method - * @param {Element} template - */ - function initializeCountryCountrySelector(method, template) { - let checkbox = templateService.getComponent('pl-country-selector-checkbox', template); - let countryListBtn = templateService.getComponent('pl-country-list-btn', template); - let countrySelectorCtrl = new Packlink.CountrySelectorController(); - - checkbox.addEventListener('change', onChangeCountrySelectorCheckbox); - countryListBtn.addEventListener('click', onClickCountryList); - - countrySelector.isShipToAllCountries = method.isShipToAllCountries; - countrySelector.shippingCountries = method.shippingCountries; - - checkbox.checked = countrySelector.isShipToAllCountries; - if (countrySelector.isShipToAllCountries) { - countryListBtn.classList.add('hidden'); - } - - function onChangeCountrySelectorCheckbox(event) { - countrySelector.isShipToAllCountries = event.target.checked; - - if (countrySelector.isShipToAllCountries) { - countryListBtn.classList.add('hidden'); - countrySelectorCtrl.destroy(); - } else { - countryListBtn.classList.remove('hidden'); - countrySelectorCtrl.display(availableCountries, countrySelector.shippingCountries, save, cancel); - } - } - - function onClickCountryList() { - countrySelectorCtrl.display(availableCountries, countrySelector.shippingCountries, save, cancel); - } - - function save(selectedCountries) { - countrySelector.shippingCountries = selectedCountries; - countrySelectorCtrl.destroy(); - } - - function cancel() { - countrySelectorCtrl.destroy(); - } - } - - /** - * Scrolls config from. - * - * @param {number} methodId - */ - function scrollConfigForm(methodId) { - let scroller = templateService.getComponent('pl-table-scroll', extensionPoint); - - if (scroller) { - let rowIndex = renderedShippingMethods.indexOf(methodId + ''); - scroller.scrollTo({ - smooth: true, - left: 0, - top: (configuration.rowHeight * rowIndex + configuration.scrollOffset) - }); - } else { - shippingMethodTemplates[methodId].scrollIntoView(); - } - } - - /** - * Handles event when show logo checkbox is checked. - * - * @param event - */ - function handleShowLogoChanged(event) { - methodModel.showLogo = event.target.checked; - } - - /** - * Handles event when shipping method name is changed. - * - * @param event - */ - function handleMethodNameChanged(event) { - let value = event.target.value; - if (value === '') { - templateService.setError(event.target, Packlink.errorMsgs.required); - } else if (configuration.maxTitleLength && value.length > configuration.maxTitleLength) { - templateService.setError(event.target, Packlink.errorMsgs.titleLength); - } else { - templateService.removeError(event.target); - } - - methodModel.name = value; - } - - /** - * Handles shipping method configuration cancel button clicked event. - * - * @param event - */ - function handleShippingMethodCancelClicked(event) { - closeConfigForm(); - } - - /** - * Closes shipping method config form. - */ - function closeConfigForm() { - let configTemplate = templateService.getComponent( - 'pl-shipping-method-config-form', - tableRowExtensionPoint - ); - - configTemplate.remove(); - - if (methodModel.id) { - scrollConfigForm(methodModel.id + ''); - } - - methodModel = {}; - utilityService.disableInputMask(); - } - - /** - * Handles shipping method save clicked. - * - * @param event - */ - function handleShippingMethodSaveClicked(event) { - // Delete unnecessary fields in model. - delete methodModel.deliveryType; - delete methodModel.parcelDestination; - delete methodModel.parcelOrigin; - delete methodModel.selected; - delete methodModel.logoUrl; - delete methodModel.title; - - if (isShippingMethodValid()) { - utilityService.showSpinner(); - - if (methodModel.pricePolicy === PRICING_POLICY_PACKLINK) { - delete methodModel.fixedPriceByWeightPolicy; - delete methodModel.fixedPriceByValuePolicy; - delete methodModel.percentPricePolicy; - } else if (methodModel.pricePolicy === PRICING_POLICY_PERCENT) { - delete methodModel.fixedPriceByWeightPolicy; - delete methodModel.fixedPriceByValuePolicy; - } else if (methodModel.pricePolicy === PRICING_POLICY_FIXED_BY_VALUE) { - delete methodModel.percentPricePolicy; - delete methodModel.fixedPriceByWeightPolicy; - } else { - delete methodModel.percentPricePolicy; - delete methodModel.fixedPriceByValuePolicy; - } - - if (configuration.hasTaxConfiguration) { - methodModel.taxClass = templateService.getComponent('pl-tax-selector', extensionPoint).value; - } - - if (configuration.hasCountryConfiguration) { - methodModel.isShipToAllCountries = countrySelector.isShipToAllCountries; - - if (!methodModel.isShipToAllCountries) { - methodModel.shippingCountries = countrySelector.shippingCountries; - } else { - methodModel.shippingCountries = []; - } - - countrySelector = {}; - } - - ajaxService.post( - configuration.saveUrl, - methodModel, - function (response) { - shippingMethods[response.id] = response; - utilityService.showFlashMessage(Packlink.successMsgs.shippingMethodSaved, 'success'); - closeConfigForm(); - renderShippingMethods(); - if (shouldGetShopShippingMethodCount()) { - getShopShippingMethodCount(); - } else { - utilityService.hideSpinner(); - } - }, - function (response) { - if (response.message) { - utilityService.showFlashMessage(response.message, 'danger'); - } - - utilityService.hideSpinner(); - } - ); - } - } - - /** - * Returns whether a call for getting number of shop shipping methods should be made. - * - * @return {boolean} - */ - function shouldGetShopShippingMethodCount() { - return getNumberOfActiveServices() === 1 - && typeof (configuration.getShopShippingMethodCountUrl) !== 'undefined'; - } - - /** - * Performs an AJAX call for getting number of shop shipping methods, if proper conditions are met. - */ - function getShopShippingMethodCount() { - ajaxService.get( - configuration.getShopShippingMethodCountUrl, - getShopShippingMethodsCountCallback - ); - } - - /** - * Validates shipping method when save button in config form is clicked. - * - * @return {boolean} - */ - function isShippingMethodValid() { - let isValid = true; - - if (methodModel.name === '') { - let nameInput = templateService.getComponent('pl-method-title-input', tableExtensionPoint); - templateService.setError(nameInput, Packlink.errorMsgs.required); - isValid = false; - } - - if (configuration.maxTitleLength && methodModel.name.length > configuration.maxTitleLength) { - let nameInput = templateService.getComponent('pl-method-title-input', tableExtensionPoint); - templateService.setError(nameInput, Packlink.errorMsgs.titleLength); - isValid = false; - } - - if (methodModel.pricePolicy === PRICING_POLICY_PERCENT) { - if (!methodModel.percentPricePolicy - || !methodModel.percentPricePolicy.amount - || typeof methodModel.percentPricePolicy.amount !== 'number' - || methodModel.percentPricePolicy.amount <= 0 - || !methodModel.percentPricePolicy.increase && (methodModel.percentPricePolicy.amount >= 100) - ) { - isValid = false; - let amountInput = templateService.getComponent('pl-perecent-amount', tableExtensionPoint); - templateService.setError(amountInput, Packlink.errorMsgs.invalid); - } - } - - if ((methodModel.pricePolicy === PRICING_POLICY_FIXED_BY_WEIGHT && !isFixedPriceValid(true, true, true)) - || (methodModel.pricePolicy === PRICING_POLICY_FIXED_BY_VALUE && !isFixedPriceValid(true, true, false)) - ) { - isValid = false; - } - - if (configuration.hasCountryConfiguration - && !countrySelector.isShipToAllCountries - && countrySelector.shippingCountries.length === 0) { - utilityService.showFlashMessage(Packlink.errorMsgs.invalidCountryList, 'danger'); - - isValid = false; - } - - return isValid; - } - - /** - * Handles shipping method pricing policy changed event. - * - * @param event - */ - function handleShippingMethodPricingPolicyChanged(event) { - let pricingPolicy = parseInt(event.target.value); - - if (pricingPolicy === PRICING_POLICY_PACKLINK) { - methodModel.pricePolicy = PRICING_POLICY_PACKLINK; - templateService.setTemplate('', 'pl-pricing-extension-point'); - } else if (pricingPolicy === PRICING_POLICY_FIXED_BY_WEIGHT) { - displayFixedPricesSubForm(false, false, true, true, true); - } else if (pricingPolicy === PRICING_POLICY_FIXED_BY_VALUE) { - displayFixedPricesSubForm(false, false, true, true, false); - } else if (pricingPolicy === PRICING_POLICY_PERCENT) { - displayPercentForm(true); - } - } - - /** - * Displays fixed prices sub-form. - * - * @param {boolean} validateLastAmount - * @param {boolean} validateLastTo - * @param {boolean} rerenderForm - * @param {boolean} shouldFocus - * @param {boolean} byWeight - */ - function displayFixedPricesSubForm(validateLastAmount, validateLastTo, rerenderForm, shouldFocus, byWeight) { - let policy = getFixedPricePolicy(byWeight); - if (rerenderForm) { - let point = templateService.setTemplate( - byWeight ? 'pl-fixed-prices-by-weight-template' : 'pl-fixed-prices-by-value-template', - 'pl-pricing-extension-point' - ); - - let addButton = templateService.getComponent('pl-fixed-price-add', point); - addButton.addEventListener( - 'click', - byWeight ? addFixedPriceByWeightCriteria : addFixedPriceByValueCriteria, - true - ); - - methodModel.pricePolicy = byWeight ? PRICING_POLICY_FIXED_BY_WEIGHT : PRICING_POLICY_FIXED_BY_VALUE; - if (!methodModel[policy] || methodModel[policy].length === 0) { - methodModel[policy] = []; - methodModel[policy].push({from: 0, to: '', amount: ''}); - } - - let addedPricePoint = templateService.getComponent('pl-fixed-price-criteria-extension-point', point); - for (let i = 0; i < methodModel[policy].length; i++) { - constructFixedPrice(methodModel[policy], i, addedPricePoint, byWeight); - - if (shouldFocus && (i === methodModel[policy].length - 1)) { - templateService.getComponent('data-pl-to-id', extensionPoint, i).focus(); - } - } - } - - isFixedPriceValid(validateLastAmount, validateLastTo, byWeight); - - utilityService.configureInputElements(); - } - - /** - * Handles fixed price criteria added event. - * - * @param event - */ - function addFixedPriceByWeightCriteria(event) { - addFixedPriceCriteria(event, true); - } - - /** - * Handles fixed price criteria added event. - * - * @param event - */ - function addFixedPriceByValueCriteria(event) { - addFixedPriceCriteria(event, false); - } - - /** - * Handles fixed price criteria added event. - * - * @param event - * @param {boolean} byWeight - */ - function addFixedPriceCriteria(event, byWeight) { - let policy = getFixedPricePolicy(byWeight); - let index = methodModel[policy].length - 1; - let currentCriteria = methodModel[policy][index]; - - if (currentCriteria.to - && typeof currentCriteria.to === 'number' - && currentCriteria.to > currentCriteria.from - && currentCriteria.amount - && typeof currentCriteria.amount === 'number' - && currentCriteria.amount > 0 - ) { - methodModel[policy].push({from: currentCriteria.to, to: '', amount: ''}); - displayFixedPricesSubForm(false, false, true, true, byWeight); - - return; - } - - displayFixedPricesSubForm(true, true, false, false, byWeight); - } - - /** - * Fills already added fixed price policy. - * Attaches event handler to remove button. - * - * @param {object} policies - * @param {int} id - * @param {Element} point - * @param {boolean} byWeight - */ - function constructFixedPrice(policies, id, point, byWeight) { - let template = templateService.getTemplate( - byWeight ? 'pl-fixed-price-by-weight-criteria-template' : 'pl-fixed-price-by-value-criteria-template' - )[0]; - - template.setAttribute('data-pl-row', id.toString()); - - if (policies.length === 1) { - template.classList.add('first'); - } else { - template.classList.remove('first'); - } - - initializeCriteriaFields(policies[id], id, template, byWeight); - - let removeBtn = templateService.getComponent('data-pl-remove', template, 'criteria'); - - removeBtn.addEventListener( - 'click', - byWeight ? handleFixedPriceByWeightCriteriaRemoved : handleFixedPriceByValueCriteriaRemoved, - true - ); - removeBtn.setAttribute('data-pl-criteria-id', id.toString()); - - point.appendChild(template); - } - - /** - * Fills criteria fields. - * - * @param {object} policy - * @param {int} id - * @param {Element} template - * @param {boolean} byWeight - */ - function initializeCriteriaFields(policy, id, template, byWeight) { - let fields = [ - 'from', - 'to', - 'amount' - ]; - - for (let field of fields) { - let input = templateService.getComponent('data-pl-fixed-price', template, field); - input.value = policy[field]; - input.setAttribute('data-pl-' + field + '-id', id.toString()); - - if (field === 'from') { - if (id === 0) { - input.addEventListener('blur', function (event) { - onFixedPriceFromBlur(event, byWeight); - }, true); - } else { - input.disabled = true; - } - } - - if (field === 'to') { - input.addEventListener( - 'blur', - byWeight ? onFixedPriceByWeightToBlur : onFixedPriceByValueToBlur, true - ); - input.setAttribute('tabindex', id * 2 + 1); - } - - if (field === 'amount') { - input.addEventListener( - 'blur', - byWeight ? onFixedPriceByWeightAmountBlur : onFixedPriceByValueAmountBlur, - true - ); - input.setAttribute('tabindex', id * 2 + 2); - } - } - } - - /** - * Handles on Fixed Price From input field blur event. - * - * @param event - * @param {boolean} byWeight - */ - function onFixedPriceFromBlur(event, byWeight) { - let policy = getFixedPricePolicy(byWeight); - let index = parseInt(event.target.getAttribute('data-pl-from-id')); - - if (index !== 0) { - return; - } - - let value = event.target.value; - let numericValue = parseFloat(value); - // noinspection EqualityComparisonWithCoercionJS - methodModel[policy][index].from = event.target.value == numericValue ? numericValue : value; - - templateService.removeError(event.target); - - displayFixedPricesSubForm(index === methodModel[policy].length - 1, false, false, false, byWeight) - } - - /** - * Handles blur event on to input field. - * - * @param event - */ - function onFixedPriceByWeightToBlur(event) { - onFixedPriceToBlur(event, true); - } - - /** - * Handles blur event on to input field. - * - * @param event - */ - function onFixedPriceByValueToBlur(event) { - onFixedPriceToBlur(event, false); - } - - /** - * Handles blur event on to input field. - * - * @param event - * @param {boolean} byWeight - */ - function onFixedPriceToBlur(event, byWeight) { - let policy = getFixedPricePolicy(byWeight); - let index = parseInt(event.target.getAttribute('data-pl-to-id')); - let value = event.target.value; - let numericValue = parseFloat(value); - // noinspection EqualityComparisonWithCoercionJS - methodModel[policy][index].to = event.target.value == numericValue ? numericValue : value; - - if (value !== '' && !isNaN(value)) { - if (index < methodModel[policy].length - 1) { - let successor = methodModel[policy][index + 1]; - let isSuccessorLast = index + 1 === methodModel[policy].length - 1; - - if (typeof successor.to === 'number' - && (successor.to > numericValue || isSuccessorLast) - || typeof successor.to !== 'number' - ) { - successor.from = methodModel[policy][index].to; - let fromInput = templateService.getComponent('data-pl-from-id', extensionPoint, index + 1); - fromInput.value = successor.from; - if ((isSuccessorLast) && typeof successor.to === 'number' && successor.to <= successor.from) { - successor.to = ''; - } - } - } - } - - templateService.removeError(event.target); - displayFixedPricesSubForm(false, index === methodModel[policy].length - 1, false, false, byWeight); - } - - /** - * Handles fixed price criteria amount blur event. - * - * @param event - */ - function onFixedPriceByWeightAmountBlur(event) { - onFixedPriceAmountBlur(event, true); - } - - /** - * Handles fixed price criteria amount blur event. - * - * @param event - */ - function onFixedPriceByValueAmountBlur(event) { - onFixedPriceAmountBlur(event, false); - } - - /** - * Handles fixed price criteria amount blur event. - * - * @param event - * @param {boolean} byWeight - */ - function onFixedPriceAmountBlur(event, byWeight) { - let policy = getFixedPricePolicy(byWeight); - let index = parseInt(event.target.getAttribute('data-pl-amount-id')); - let numeric = parseFloat(event.target.value); - - // noinspection EqualityComparisonWithCoercionJS - methodModel[policy][index].amount = event.target.value == numeric ? numeric : event.target.value; - templateService.removeError(event.target); - displayFixedPricesSubForm(index === methodModel[policy].length - 1, false, false, false, byWeight); - } - - /** - * Validates fixed price criteria. - * - * @param validateLastAmount - * @param validateLastTo - * @param {boolean} byWeight - * - * @return {boolean} - */ - function isFixedPriceValid(validateLastAmount, validateLastTo, byWeight) { - if (!isFixedPriceRangeValid(byWeight) - || !isFixedPriceInputTypeValid(byWeight) - || !isFixedPriceAmountValid(byWeight) - || !isFixedPriceNumberOfDecimalPlacesValid(byWeight) - ) { - return false; - } - - if (validateLastAmount || validateLastTo) { - let result = true; - let policies = methodModel[getFixedPricePolicy(byWeight)]; - let index = policies.length - 1; - let last = policies[index]; - - if (validateLastTo) { - result = validateFixedPriceField('to', last, index, last.from); - } - - if (validateLastAmount) { - result = validateFixedPriceField('amount', last, index, byWeight ? 0.001 : 0); - } - - return result; - } - - return true; - } - - function validateFixedPriceField(fieldName, last, index, lowerBound) { - let input = templateService.getComponent('data-pl-' + fieldName + '-id', tableExtensionPoint, index); - let result = true; - // noinspection EqualityComparisonWithCoercionJS - if (last[fieldName] === '' - || isNaN(last[fieldName]) - || typeof last[fieldName] !== 'number' - || last[fieldName] < lowerBound - || last[fieldName] != parseFloat(last[fieldName].toFixed(2)) - ) { - result = false; - templateService.setError(input, Packlink.errorMsgs.invalid); - } else { - templateService.removeError(input); - } - - return result; - } - - /** - * Validates fixed price input type. - */ - function isFixedPriceInputTypeValid(byWeight) { - let policies = methodModel[getFixedPricePolicy(byWeight)]; - let fields = ['from', 'amount', 'to']; - let result = true; - - for (let i = 0; i < policies.length; i++) { - for (let field of fields) { - let value = policies[i][field]; - if (value === '' || isNaN(value) || typeof value !== 'number') { - let input = templateService.getComponent('data-pl-' + field + '-id', tableExtensionPoint, i); - templateService.setError(input, Packlink.errorMsgs.numeric); - result = false; - } - } - } - - return result; - } - - /** - * Validates fixed price amount. - * - * @return {boolean} - */ - function isFixedPriceAmountValid(byWeight) { - let result = true; - let boundary = byWeight ? 0.0001 : 0; - let policies = methodModel[getFixedPricePolicy(byWeight)]; - - for (let i = 0; i < policies.length; i++) { - if (policies[i]['amount'] < boundary) { - let input = templateService.getComponent('data-pl-amount-id', tableExtensionPoint, i); - templateService.setError(input, Packlink.errorMsgs.invalid); - result = false; - } - } - - return result; - } - - /** - * Validates fixed price range. - * - * @return {boolean} - */ - function isFixedPriceRangeValid(byWeight) { - let policies = methodModel[getFixedPricePolicy(byWeight)]; - let result = true; - - for (let i = 0; i < policies.length; i++) { - let current = policies[i]; - let successor = policies.length < i + 1 ? policies[i + 1] : null; - - if (current.from < 0) { - let input = templateService.getComponent('data-pl-from-id', tableExtensionPoint, i); - templateService.setError(input, Packlink.errorMsgs.invalid); - result = false; - } - - if (current.from >= current.to || (successor && successor.from && current.to > successor.from)) { - let input = templateService.getComponent('data-pl-to-id', tableExtensionPoint, i); - templateService.setError(input, Packlink.errorMsgs.invalid); - result = false; - } - } - - return result; - } - - /** - * Validates fixed price number of decimal places. - */ - function isFixedPriceNumberOfDecimalPlacesValid(byWeight) { - let policies = methodModel[getFixedPricePolicy(byWeight)]; - let result = true; - - for (let i = 0; i < policies.length; i++) { - let current = policies[i]; - // noinspection EqualityComparisonWithCoercionJS - if (current.to && current.to != current.to.toFixed(2)) { - let input = templateService.getComponent('data-pl-to-id', tableExtensionPoint, i); - templateService.setError(input, Packlink.errorMsgs.numberOfDecimalPlaces); - result = false; - } - - // noinspection EqualityComparisonWithCoercionJS - if (current.amount && current.amount != current.amount.toFixed(2)) { - let input = templateService.getComponent('data-pl-amount-id', tableExtensionPoint, i); - templateService.setError(input, Packlink.errorMsgs.numberOfDecimalPlaces); - result = false; - } - } - - return result; - } - - /** - * Handles removal of fixed price criteria. - * - * @param event - */ - function handleFixedPriceByWeightCriteriaRemoved(event) { - handleFixedPriceCriteriaRemoved(event, true); - } - - /** - * Handles removal of fixed price criteria. - * - * @param event - */ - function handleFixedPriceByValueCriteriaRemoved(event) { - handleFixedPriceCriteriaRemoved(event, false); - } - - /** - * Handles removal of fixed price criteria. - * - * @param event - * @param {boolean} byWeight - */ - function handleFixedPriceCriteriaRemoved(event, byWeight) { - let policies = methodModel[getFixedPricePolicy(byWeight)]; - let index = parseInt(event.target.getAttribute('data-pl-criteria-id')); - if (index !== policies.length - 1) { - policies[index + 1].from = policies[index].from; - } - - policies.splice(index, 1); - displayFixedPricesSubForm(false, false, true, true, byWeight); - } - - function getFixedPricePolicy(byWeight) { - return byWeight ? 'fixedPriceByWeightPolicy' : 'fixedPriceByValuePolicy'; - } - - /** - * Displays packlink percent sub-form. - * - * @param {boolean} [shouldFocusInput] - */ - function displayPercentForm(shouldFocusInput) { - templateService.setTemplate('pl-packlink-percent-template', 'pl-pricing-extension-point'); - - let buttons = templateService.getComponentsByAttribute( - 'data-pl-packlink-percent-btn', - tableExtensionPoint - ); - - for (let button of buttons) { - button.addEventListener('click', handlePacklinkPercentButtonClicked, true); - } - - let input = templateService.getComponent( - 'pl-perecent-amount', - tableExtensionPoint - ); - input.addEventListener('blur', handlePercentInputBlurEvent, true); - - if (shouldFocusInput) { - input.focus(); - } - - methodModel.pricePolicy = PRICING_POLICY_PERCENT; - - if (!methodModel.percentPricePolicy) { - methodModel.percentPricePolicy = { - increase: true - }; - } else { - if (methodModel.percentPricePolicy.amount) { - input.value = methodModel.percentPricePolicy.amount; - - if (!methodModel.percentPricePolicy.increase) { - if (methodModel.percentPricePolicy.amount >= 100) { - templateService.setError(input, Packlink.errorMsgs.invalid); - } - } - } - } - - setPercentButtonsClass(); - utilityService.configureInputElements(); - } - - /** - * Handles blur event on percent input field; - * - * @param event - */ - function handlePercentInputBlurEvent(event) { - let value = event.target.value; - let numeric = parseFloat(value); - // noinspection EqualityComparisonWithCoercionJS - methodModel.percentPricePolicy.amount = value == numeric ? numeric : value; - - if (value === '' - || isNaN(value) - || value <= 0 - || !methodModel.percentPricePolicy.increase && (value >= 100) - ) { - templateService.setError(event.target, Packlink.errorMsgs.invalid); - } else { - displayPercentForm(); - } - } - - /** - * Sets correct class to switch buttons. - */ - function setPercentButtonsClass() { - let increaseButton = templateService.getComponent( - 'data-pl-packlink-percent-btn', - tableExtensionPoint, - 'increase' - ); - - let decreaseButton = templateService.getComponent( - 'data-pl-packlink-percent-btn', - tableExtensionPoint, - 'decrease' - ); - - if (methodModel.percentPricePolicy.increase) { - increaseButton.classList.add('selected'); - decreaseButton.classList.remove('selected'); - } else { - increaseButton.classList.remove('selected'); - decreaseButton.classList.add('selected'); - } - } - - /** - * Handles event when increase/decrease price by percent button is clicked. - * - * @param event - */ - function handlePacklinkPercentButtonClicked(event) { - let value = event.target.getAttribute('data-pl-packlink-percent-btn'); - methodModel.percentPricePolicy.increase = value === 'increase'; - displayPercentForm(true); - } - - /** - * Fills tax selector. - * - * @param response - */ - function getTaxClassesSuccessHandler(response) { - taxSelector = templateService.getComponent('pl-tax-selector', document.body); - - while (taxSelector.firstChild) { - taxSelector.firstChild.remove(); - } - - for (let taxClass of response) { - let option = document.createElement('option'); - option.value = taxClass['value']; - option.innerHTML = taxClass['label']; - taxSelector.appendChild(option); - taxClasses.push(taxClass['value']); - } - - taxSelector.value = response[0]['value']; - - if (spinnerBarrier === ++spinnerBarrierCount) { - utilityService.hideSpinner(); - } - } - - function getShippingCountriesHandler(response) { - availableCountries = response; - - if (spinnerBarrier === ++spinnerBarrierCount) { - utilityService.hideSpinner(); - } - } - - /** - * Checks whether tax class exists in system. - * - * @param {string} taxClass - * - * @return {boolean} - */ - function classExists(taxClass) { - for (let taxClassValue of taxClasses) { - if (taxClassValue === taxClass) { - return true; - } - } - - return false; - } - - /** - * DASHBOARD MODAL SECTION - */ - - /** - * Initializes dashboard steps. - */ - function initSteps() { - initParcelStep(); - initWarehouseStep(); - initMethodsStep(); - initStepSubtitle(); - } - - /** - * Initializes default parcel step. - */ - function initParcelStep() { - let status = dashboardData.isParcelSet ? 'completed' : 'in-progress'; - let step = templateService.getComponent('pl-parcel-step'); - if (status !== 'disabled') { - step.addEventListener('click', handleParcelStepClicked, true); - } else { - step.removeEventListener('click', handleParcelStepClicked); - } - - step.classList.remove(...STATUSES); - step.classList.add(status); - } - - /** - * Initializes step subtitle. - */ - function initStepSubtitle() { - if (!dashboardData.isParcelSet || !dashboardData.isWarehouseSet) { - templateService.getComponent('pl-step-subtitle').remove(); - } - } - - /** - * Initializes warehouse step. - */ - function initWarehouseStep() { - let status = 'completed'; - - if (!dashboardData.isWarehouseSet) { - if (dashboardData.isParcelSet) { - status = 'in-progress'; - } else { - status = 'disabled'; - } - } - - let step = templateService.getComponent('pl-warehouse-step'); - step.classList.remove(...STATUSES); - step.classList.add(status); - - if (status !== 'disabled') { - step.addEventListener('click', handleWarehouseStepClicked, true); - } else { - step.removeEventListener('click', handleWarehouseStepClicked); - } - } - - /** - * Initializes shipping methods step. - */ - function initMethodsStep() { - let status = 'completed'; - - if (!dashboardData.isShippingMethodSet) { - if (dashboardData.isParcelSet && dashboardData.isWarehouseSet) { - status = 'in-progress'; - } else { - status = 'disabled'; - } - } - - let step = templateService.getComponent('pl-shipping-methods-step'); - step.classList.remove(...STATUSES); - step.classList.add(status); - - if (status !== 'disabled') { - step.addEventListener('click', handleMethodsStepClicked, true); - } else { - step.removeEventListener('click', handleMethodsStepClicked); - } - } - - /** - * Handles clicked event on parcel step. - * - * @param event - */ - function handleParcelStepClicked(event) { - state.startStep('default-parcel'); - } - - /** - * Handles clicked event on warehouse step. - * - * @param event - */ - function handleWarehouseStepClicked(event) { - state.startStep('default-warehouse'); - } - - /** - * Handles clicked event on shipping methods step. - * - * @param event - */ - function handleMethodsStepClicked(event) { - hideDashboardModal(); - } - - /** - * Shows dashboard modal. - */ - function showDashboardModal() { - templateService.getComponent('pl-dashboard-modal-wrapper', extensionPoint).classList.remove('hidden'); - utilityService.hideSpinner(); - hideGettingShippingMethodsMessage(); - isDashboardShown = true; - } - - /** - * Hides dashboard modal. - * - * @param {boolean} [isAutoconfigure] - */ - function hideDashboardModal(isAutoconfigure) { - if (typeof isAutoconfigure !== 'boolean') { - isAutoconfigure = false; - } - - if (!isAutoconfigure && (!dashboardData.isParcelSet || !dashboardData.isWarehouseSet)) { - return; - } - - templateService.getComponent('pl-dashboard-modal-wrapper', extensionPoint).classList.add('hidden'); - if (Object.keys(shippingMethods).length === 0) { - showGettingShippingMethodsMessage(); - } - - isDashboardShown = false; - } - - /** - * Retrieves spinner barrier value. Spinner barrier is used to denote number of required ajax requests that - * have to be completed before initial loading spinner is hidden. - * - * @return {number} - */ - function getSpinnerBarrier() { - return 1 + !!(configuration.hasTaxConfiguration) + !!(configuration.hasCountryConfiguration); - } - } - - Packlink.ShippingMethodsController = ShippingMethodsController; -})(); diff --git a/src/BusinessLogic/Resources/js/ShippingServicesRenderer.js b/src/BusinessLogic/Resources/js/ShippingServicesRenderer.js new file mode 100644 index 00000000..144bc4c0 --- /dev/null +++ b/src/BusinessLogic/Resources/js/ShippingServicesRenderer.js @@ -0,0 +1,117 @@ +if (!window.Packlink) { + window.Packlink = {}; +} + +(function () { + /** + * @typedef ShippingService + * @property {string} id + * @property {boolean} activated + * @property {string} name + * @property {string} logoUrl + * @property {string} type + * @property {string} deliveryType + * @property {string} carrierName + * @property {string} deliveryDescription + * @property {'dropoff'|'collection'} parcelOrigin + * @property {'pickup'|'delivery'} parcelDestination + * @property {boolean} showLogo + * @property {string} taxClass + * @property {[]} pricingPolicies + * @property {[]} shippingCountries + * @property {boolean} isShipToAllCountries + * @property {boolean} usePacklinkPriceIfNotInRange + */ + + /** + * Renders shipping services table. + * + * @constructor + */ + function ShippingServicesRenderer() { + const templateService = Packlink.templateService, + translator = Packlink.translationService; + + /** + * Renders the services table. + * + * @param {HTMLElement} parent + * @param {string} templateId + * @param {string} elementType + * @param {ShippingService[]} services + * @param {boolean} list + * @param {function(id: string, action: 'add'|'edit'|'delete')} buttonAction + */ + this.render = (parent, templateId, elementType, services, list, buttonAction) => { + parent.innerHTML = ''; + services.forEach((service) => { + const template = templateService.getComponent(templateId), + itemEl = document.createElement(elementType); + + itemEl.innerHTML = template.innerHTML; + constructItem(itemEl, service, list, buttonAction); + + parent.appendChild(itemEl); + }); + }; + + /** + * Fills row template with concrete information such as title etc. + * Also, attaches proper event handlers to actionable elements of row template. + * + * @param {Element} itemEl + * @param {ShippingService} service + * @param {boolean} list + * @param {function(id: string, action: 'add'|'edit'|'delete')} buttonAction + */ + function constructItem(itemEl, service, list, buttonAction) { + const carrierLogo = itemEl.querySelector('#pl-carrier-logo'); + carrierLogo.setAttribute('src', service.logoUrl); + carrierLogo.setAttribute('alt', service.carrierName); + carrierLogo.setAttribute('title', service.carrierName); + + itemEl.querySelector('#pl-service-name').innerHTML = service.name; + itemEl.querySelector('#pl-service-policy').innerHTML = translator.translate('shippingServices.' + (service.pricingPolicies.length ? 'myPrices' : 'packlinkPrices')); + + itemEl.querySelector('#pl-service-delivery-description').innerHTML = service.deliveryDescription; + itemEl.querySelector('#pl-service-type').innerHTML = translator.translate('shippingServices.' + service.type); + + const parcelOrigin = itemEl.querySelector('#pl-service-origin'); + if (service.parcelOrigin === 'collection') { + parcelOrigin.querySelector('#pl-origin-dropoff').classList.add('pl-hidden'); + } else { + parcelOrigin.querySelector('#pl-origin-collection').classList.add('pl-hidden'); + } + + const parcelDestination = itemEl.querySelector('#pl-service-destination'); + if (service.parcelDestination === 'delivery') { + parcelDestination.querySelector('#pl-destination-pickup').classList.add('pl-hidden'); + } else { + parcelDestination.querySelector('#pl-destination-delivery').classList.add('pl-hidden'); + } + + const addButton = itemEl.querySelector('#pl-service-actions #pl-add-service'), + editButton = itemEl.querySelector('#pl-service-actions #pl-edit-service'), + deleteButton = itemEl.querySelector('#pl-service-actions #pl-delete-service'); + + if (list) { + addButton.classList.add('pl-hidden'); + } else { + editButton.classList.add('pl-hidden'); + deleteButton.classList.add('pl-hidden'); + } + + addButton.addEventListener('click', () => { + buttonAction(service.id, 'add'); + }); + editButton.addEventListener('click', () => { + buttonAction(service.id, 'edit'); + }); + deleteButton.addEventListener('click', () => { + buttonAction(service.id, 'delete'); + }); + } + } + + Packlink.ShippingServicesRenderer = new ShippingServicesRenderer(); +})(); diff --git a/src/BusinessLogic/Resources/js/SidebarController.js b/src/BusinessLogic/Resources/js/SidebarController.js deleted file mode 100644 index 35f1727b..00000000 --- a/src/BusinessLogic/Resources/js/SidebarController.js +++ /dev/null @@ -1,119 +0,0 @@ -var Packlink = window.Packlink || {}; - -(function () { - function SidebarController(sidebarNavigationCallback, sidebarButtons, submenuItems) { - let templateService = Packlink.templateService; - let navigationCallback = sidebarNavigationCallback; - let currentState = 'shipping-methods'; - let basicSettingsSubmenuDisplayed = false; - - /** - * Sets currently selected button on dashboard. - * - * @param {string} state - */ - this.setState = function (state) { - if (state === 'basic-settings') { - handleBasicSettingsMenuAction(); - } else { - removeSelectedClass(currentState); - addSelectedClass(state); - - if (basicSettingsSubmenuDisplayed && (submenuItems.indexOf(state) === -1)) { - hideBasicSettingsSubmenu(); - } - - currentState = state; - } - }; - - function handleBasicSettingsMenuAction() { - if (!basicSettingsSubmenuDisplayed) { - displayBasicSettingsSubmenu(); - } else { - if (submenuItems.indexOf(currentState) === -1) { - hideBasicSettingsSubmenu(); - } - } - } - - /** - * Removes "selected" class from target button. - * - * @param {string} targetButton - */ - function removeSelectedClass(targetButton) { - let currentButton = templateService.getComponent(getButtonId(targetButton)); - currentButton.classList.remove('selected'); - } - - /** - * Adds "selected" class from target button. - * - * @param {string} targetButton - */ - function addSelectedClass(targetButton) { - let currentButton = templateService.getComponent(getButtonId(targetButton)); - currentButton.classList.add('selected'); - } - - /** - * Adds click event handlers to initial sidebar buttons. - * Doesn't add event handlers to submenu buttons as they are not - * initially displayed. - * - * @param {function} sidebarNavigationCallback - */ - function addEventHandlersToSidebarButtons(sidebarNavigationCallback) { - for (let buttonId of sidebarButtons) { - let button = templateService.getComponent(getButtonId(buttonId)); - button.addEventListener('click', sidebarNavigationCallback, true); - if (buttonId === 'shipping-methods') { - button.classList.add('selected'); - } - } - } - - /** - * Displays basic settings submenu. - */ - function displayBasicSettingsSubmenu() { - let sidebarButtons = templateService.getTemplate('pl-sidebar-subitem-template'); - let submenuExtensionPoint = templateService.getComponent('pl-sidebar-extension-point'); - while (sidebarButtons.length) { - let button = sidebarButtons[0]; - button.addEventListener('click', navigationCallback, true); - button.setAttribute('id', getButtonId(button.getAttribute('data-pl-sidebar-btn'))); - submenuExtensionPoint.appendChild(button); - } - - addSelectedClass('basic-settings'); - basicSettingsSubmenuDisplayed = true; - } - - /** - * Hides basic settings submenu. - */ - function hideBasicSettingsSubmenu() { - let submenuExtensionPoint = templateService.getComponent('pl-sidebar-extension-point'); - templateService.clearComponent(submenuExtensionPoint); - - removeSelectedClass('basic-settings'); - basicSettingsSubmenuDisplayed = false; - } - - /** - * Formats button id. - * - * @param {string} name - * @return {string} - */ - function getButtonId(name) { - return 'pl-sidebar-' + name + '-btn'; - } - - addEventHandlersToSidebarButtons(navigationCallback); - } - - Packlink.SidebarController = SidebarController; -})(); \ No newline at end of file diff --git a/src/BusinessLogic/Resources/js/StateController.js b/src/BusinessLogic/Resources/js/StateController.js index 641d0bdb..cbf8dd64 100644 --- a/src/BusinessLogic/Resources/js/StateController.js +++ b/src/BusinessLogic/Resources/js/StateController.js @@ -1,197 +1,104 @@ -var Packlink = window.Packlink || {}; +if (!window.Packlink) { + window.Packlink = {}; +} (function () { + /** + * @typedef StateConfiguration + * @property {string} [pagePlaceholder] + * @property {{}} pageConfiguration + * @property {string} stateUrl + * @property {{}} templates + * @property {string} baseResourcesUrl + */ + /** * Main controller of the application. * - * @param {{ - * sidebarButtons: array, - * submenuItems: array, - * pageConfiguration: array, - * scrollConfiguration: {scrollOffset: number, rowHeight: number}, - * hasTaxConfiguration: boolean, - * hasCountryConfiguration: boolean, - * canDisplayCarrierLogos: boolean, - * shippingServiceMaxTitleLength: number, - * autoConfigureStartUrl: string, - * dashboardGetStatusUrl: string, - * defaultParcelGetUrl: string, - * defaultParcelSubmitUrl: string, - * defaultWarehouseGetUrl: string, - * getSupportedCountriesUrl: string, - * defaultWarehouseSubmitUrl: string, - * defaultWarehouseSearchPostalCodesUrl: string, - * debugGetStatusUrl: string, - * debugSetStatusUrl: string, - * shippingMethodsGetAllUrl: string, - * shippingMethodsGetStatusUrl: string, - * shippingMethodsGetTaxClassesUrl: string, - * shippingMethodsSaveUrl: string, - * shippingMethodsActivateUrl: string, - * shippingMethodsDeactivateUrl: string, - * shopShippingMethodCountGetUrl: string, - * shopShippingMethodsDisableUrl: string, - * getSystemOrderStatusesUrl: string, - * orderStatusMappingsSaveUrl: string, - * orderStatusMappingsGetUrl: string, - * getShippingCountriesUrl: string, - * initialPage: string - * }} configuration + * @param {StateConfiguration} configuration * * @constructor */ function StateController(configuration) { - let pageControllerFactory = Packlink.pageControllerFactory; - let initialPage = 'shipping-methods'; - - let sidebarButtons = [ - 'shipping-methods', - 'basic-settings' - ]; - - if (typeof configuration.sidebarButtons !== 'undefined') { - sidebarButtons = sidebarButtons.concat(configuration.sidebarButtons); - } - - let submenuItems = [ - 'order-state-mapping', - 'default-parcel', - 'default-warehouse' - ]; - - if (typeof configuration.submenuItems !== 'undefined') { - submenuItems = submenuItems.concat(configuration.submenuItems); - } - - let utilityService = Packlink.utilityService; - let context = ''; - - let pageConfiguration = { - 'default-parcel': { - getUrl: configuration.defaultParcelGetUrl, - submitUrl: configuration.defaultParcelSubmitUrl - }, - 'default-warehouse': { - getUrl: configuration.defaultWarehouseGetUrl, - getSupportedCountriesUrl: configuration.getSupportedCountriesUrl, - submitUrl: configuration.defaultWarehouseSubmitUrl, - searchPostalCodesUrl: configuration.defaultWarehouseSearchPostalCodesUrl - }, - 'shipping-methods': { - getDashboardStatusUrl: configuration.dashboardGetStatusUrl, - getAllMethodsUrl: configuration.shippingMethodsGetAllUrl, - getMethodsStatusUrl: configuration.shippingMethodsGetStatusUrl, - activateUrl: configuration.shippingMethodsActivateUrl, - deactivateUrl: configuration.shippingMethodsDeactivateUrl, - saveUrl: configuration.shippingMethodsSaveUrl, - rowHeight: configuration.scrollConfiguration.rowHeight, - scrollOffset: configuration.scrollConfiguration.scrollOffset, - maxTitleLength: configuration.shippingServiceMaxTitleLength, - getShopShippingMethodCountUrl: configuration.shopShippingMethodCountGetUrl, - disableShopShippingMethodsUrl: configuration.shopShippingMethodsDisableUrl, - autoConfigureStartUrl: configuration.autoConfigureStartUrl, - hasTaxConfiguration: configuration.hasTaxConfiguration, - getTaxClassesUrl: configuration.shippingMethodsGetTaxClassesUrl, - canDisplayCarrierLogos: configuration.canDisplayCarrierLogos, - getShippingCountries: configuration.getShippingCountriesUrl, - hasCountryConfiguration: configuration.hasCountryConfiguration - }, - 'order-state-mapping': { - getSystemOrderStatusesUrl: configuration.getSystemOrderStatusesUrl, - getUrl: configuration.orderStatusMappingsGetUrl, - saveUrl: configuration.orderStatusMappingsSaveUrl - }, - 'footer': { - getDebugStatusUrl: configuration.debugGetStatusUrl, - setDebugStatusUrl: configuration.debugSetStatusUrl - }, - 'initialPage': configuration.initialPage - }; - - if (typeof configuration.pageConfiguration !== 'undefined') { - pageConfiguration = {...pageConfiguration, ...configuration.pageConfiguration}; - } - - if (typeof pageConfiguration.initialPage !== 'undefined') { - initialPage = pageConfiguration.initialPage; - } - - let sidebarController = new Packlink.SidebarController(navigate, sidebarButtons, submenuItems); - sidebarController.setState(initialPage); + const pageControllerFactory = Packlink.pageControllerFactory, + ajaxService = Packlink.ajaxService, + utilityService = Packlink.utilityService, + templateService = Packlink.templateService; + + let currentState = ''; + let previousState = ''; + + this.display = () => { + templateService.setBaseResourceUrl(configuration.baseResourcesUrl); + if (configuration.pagePlaceholder) { + templateService.setMainPlaceholder(configuration.pagePlaceholder); + } - this.display = function () { - pageControllerFactory.getInstance('footer', getControllerConfiguration('footer')).display(); + templateService.setTemplates(configuration.templates); - let dp = pageControllerFactory.getInstance( - initialPage, - getControllerConfiguration(initialPage) - ); - dp.display(); + ajaxService.get(configuration.stateUrl, displayPageBasedOnState); }; /** - * Opens configuration page that corresponds to particular step. + * Navigates to a state. * - * @param {string} step + * @param {string} controller + * @param {object|null} additionalConfig */ - this.startStep = function (step) { - utilityService.disableInputMask(); - let controller = pageControllerFactory.getInstance(step, getControllerConfiguration(step, true)); - controller.display(); - }; + this.goToState = (controller, additionalConfig = null) => { + Packlink.StateUUIDService.setStateUUID(Math.random().toString(36)); - /** - * Called when configuration step is finished. - */ - this.stepFinished = function () { - pageControllerFactory.getInstance( - 'shipping-methods', - getControllerConfiguration('shipping-methods')).display(); + let dp = pageControllerFactory.getInstance( + controller, + getControllerConfiguration(controller) + ); + + if (dp) { + utilityService.showSpinner(); + dp.display(additionalConfig); + } + + previousState = currentState; + currentState = controller; }; + this.getPreviousState = () => previousState; + /** - * Returns context. + * Opens a specific page based on the current state. + * + * @param {{state: string}} response */ - this.getContext = function () { - return context; + const displayPageBasedOnState = (response) => { + switch (response.state) { + case 'login': + this.goToState('login'); + break; + case 'onBoarding': + this.goToState('onboarding-state'); + break; + default: + this.goToState('my-shipping-services'); + break; + } }; /** - * Navigation callback. - * Handles navigation menu button clicked event. + * Gets controller configuration. * - * @param event + * @param {string} controller + * @param {boolean} [fromStep] + * @return {{}} */ - function navigate(event) { - let state = event.target.getAttribute('data-pl-sidebar-btn'); - sidebarController.setState(state); - if (state !== 'basic-settings') { - utilityService.disableInputMask(); - - let controller = pageControllerFactory.getInstance(state, getControllerConfiguration(state)); - controller.display(); - } - } - - function getControllerConfiguration(controller, fromStep) { - let config = utilityService.cloneObject(pageConfiguration[controller]); - - setContext(); - config.context = context; + const getControllerConfiguration = (controller, fromStep = false) => { + let config = utilityService.cloneObject(configuration.pageConfiguration[controller]); if (fromStep) { config.fromStep = true; } return config; - } - - /** - * Sets context. - */ - function setContext() { - context = Math.random().toString(36); - } + }; } Packlink.StateController = StateController; diff --git a/src/BusinessLogic/Resources/js/StateUUIDService.js b/src/BusinessLogic/Resources/js/StateUUIDService.js new file mode 100644 index 00000000..681c36bd --- /dev/null +++ b/src/BusinessLogic/Resources/js/StateUUIDService.js @@ -0,0 +1,19 @@ +if (!window.Packlink) { + window.Packlink = {}; +} + +(() => { + function StateUUIDService() { + let currentState = ''; + + this.setStateUUID = (state) => { + currentState = state; + }; + + this.getStateUUID = () => { + return currentState; + }; + } + + Packlink.StateUUIDService = new StateUUIDService(); +})(); \ No newline at end of file diff --git a/src/BusinessLogic/Resources/js/SystemInfoController.js b/src/BusinessLogic/Resources/js/SystemInfoController.js new file mode 100644 index 00000000..22a417b9 --- /dev/null +++ b/src/BusinessLogic/Resources/js/SystemInfoController.js @@ -0,0 +1,54 @@ +if (!window.Packlink) { + window.Packlink = {}; +} + +(function () { + /** + * Handles System info dialog. + * + * @constructor + * + * @param {{getStatusUrl: string, setStatusUrl: string}} config + */ + function SystemInfoController(config) { + // noinspection JSCheckFunctionSignatures + const ajaxService = Packlink.ajaxService, + utilityService = Packlink.utilityService, + modal = new Packlink.modalService({ + title: Packlink.translationService.translate('systemInfo.title'), + content: Packlink.templateService.getTemplate('pl-system-info-modal'), + onOpen: (modal) => { + ajaxService.get(config.getStatusUrl, (response) => { + getStatus(modal, response); + }); + } + }); + + /** + * @param {HTMLDivElement} modal + * @param {{status: boolean, downloadUrl: string}} response + */ + const getStatus = (modal, response) => { + const checkbox = modal.querySelector('#pl-debug-status'), + buttonLink = modal.querySelector('a'); + + checkbox.checked = response.status; + checkbox.addEventListener('click', () => { + ajaxService.post(config.setStatusUrl, {'status': checkbox.checked}); + }); + + buttonLink.href = response.downloadUrl; + + utilityService.hideSpinner(); + }; + + /** + * Displays page content. + */ + this.display = () => { + modal.open(); + }; + } + + Packlink.SystemInfoController = SystemInfoController; +})(); diff --git a/src/BusinessLogic/Resources/js/TemplateService.js b/src/BusinessLogic/Resources/js/TemplateService.js index 12c9bc56..5125b92b 100644 --- a/src/BusinessLogic/Resources/js/TemplateService.js +++ b/src/BusinessLogic/Resources/js/TemplateService.js @@ -1,40 +1,63 @@ -var Packlink = window.Packlink || {}; +if (!window.Packlink) { + window.Packlink = {}; +} (function () { function TemplateService() { /** - * Retrieves template children by template id. + * The configuration object for all templates. + */ + let templates = {}; + let mainPlaceholder = '#pl-main-page-holder'; + this.baseResourceUrl = ''; + + /** + * Sets the base resource URL. * - * @param {string} template - * @return {HTMLCollection | null} + * @param {string} url */ - this.getTemplate = function (template) { - let temp = document.getElementById(template); + this.setBaseResourceUrl = (url) => { + this.baseResourceUrl = url; + }; - if (!temp) { - return null; - } + /** + * Sets the main page placeholder. If not set, defaults to the one set in this service. + * + * @param {string} placeholder + */ + this.setMainPlaceholder = (placeholder) => { + mainPlaceholder = placeholder; + }; - let clone = temp.cloneNode(true); + /** + * Gets the main page DOM element. + * + * @returns {Element} + */ + this.getMainPage = () => document.querySelector(mainPlaceholder); - return clone.children; - }; + /** + * Gets the header of the page. + * + * @return {HTMLElement} + */ + this.getHeader = () => document.getElementById('pl-main-header'); /** - * Retrieves component by it's id or attribute. + * Retrieves component by its id or attribute. * * @param {string} component * @param {Element} [element] * @param {string|int} [attribute] * - * @return {Element} + * @return {HTMLElement} */ - this.getComponent = function (component, element, attribute) { - if (typeof element === 'undefined') { + this.getComponent = (component, element, attribute) => { + if (!element) { return document.getElementById(component); } - if (typeof attribute === 'undefined') { + if (!attribute) { return element.querySelector('#' + component); } @@ -42,94 +65,57 @@ var Packlink = window.Packlink || {}; }; /** - * Retrieves all nodes with specified attribute. + * Sets the content templates. * - * @param {string} attribute - * @param {Element} [element] - * - * @return {NodeListOf} + * @param {{}} configuration */ - this.getComponentsByAttribute = function (attribute, element) { - let selector = '[' + attribute + ']'; - - if (typeof element === 'undefined') { - return document.querySelectorAll(selector); - } - - return element.querySelectorAll(selector); + this.setTemplates = (configuration) => { + templates = configuration; }; /** - * Changes currently active page. + * Gets the template with translated text. * - * @param {string} template - * @param {string} [extensionPointIdentifier] - * @param {boolean} [clearExtensionPoint=true] + * @param {string} templateId * - * @return {Element} + * @return {string} HTML as string. */ - this.setTemplate = function (template, extensionPointIdentifier, clearExtensionPoint) { - if (typeof extensionPointIdentifier === 'undefined') { - extensionPointIdentifier = 'pl-content-extension-point'; - } - - if (typeof clearExtensionPoint === 'undefined') { - clearExtensionPoint = true; - } - - let extensionPoint = this.getComponent(extensionPointIdentifier); - - if (clearExtensionPoint) { - this.clearComponent(extensionPoint); - } - - let templateElements = this.getTemplate(template); - while (templateElements && templateElements.length) { - extensionPoint.appendChild(templateElements[0]); - } - - return extensionPoint; - }; + this.getTemplate = (templateId) => this.replaceResourcesUrl( + Packlink.translationService.translateHtml(templates[templateId]) + ); /** - * Removes component's children. + * Sets current template in the page. * - * @param {Element} component + * @param {string} templateId */ - this.clearComponent = function (component) { - while (component.firstChild) { - component.removeChild(component.firstChild); + this.setCurrentTemplate = (templateId) => { + for (let [extensionPointId, html] of Object.entries(templates[templateId])) { + const component = this.getComponent(extensionPointId); + + if (component) { + component.innerHTML = this.replaceResourcesUrl(html ? Packlink.translationService.translateHtml(html) : ''); + } } }; /** - * Sets error for input. + * Replaces all resources URL placeholders with the correct URL. * - * @param {Element} input - * @param {string} message + * @param {string} html + * @return {string} */ - this.setError = function (input, message) { - this.removeError(input); - - let errorTemplate = this.getTemplate('pl-error-template')[0]; - let msgField = this.getComponent('pl-error-text', errorTemplate); - msgField.innerHTML = message; - input.after(errorTemplate); - input.classList.add('pl-error'); - }; + this.replaceResourcesUrl = (html) => html.replace(/{\$BASE_URL\$}/g, this.baseResourceUrl); /** - * Removes error from input element. + * Removes component's children. * - * @param {Element} input + * @param {Element} component */ - this.removeError = function (input) { - let firstSibling = input.nextSibling; - if (firstSibling && firstSibling.getAttribute && firstSibling.getAttribute('data-pl-element') === 'error') { - firstSibling.remove(); + this.clearComponent = (component) => { + while (component.firstChild) { + component.removeChild(component.firstChild); } - - input.classList.remove('pl-error'); }; } diff --git a/src/BusinessLogic/Resources/js/TranslationMessages.js b/src/BusinessLogic/Resources/js/TranslationMessages.js deleted file mode 100644 index de02ef05..00000000 --- a/src/BusinessLogic/Resources/js/TranslationMessages.js +++ /dev/null @@ -1,17 +0,0 @@ -var Packlink = window.Packlink || {}; - -Packlink.errorMsgs = { - required: 'This field is required.', - numeric: 'Value must be valid number.', - invalid: 'This field is not valid.', - phone: 'This field must be valid phone number.', - titleLength: 'Title can have at most 64 characters.', - greaterThanZero: 'Value must be greater than 0.', - numberOfDecimalPlaces: 'Field must have 2 decimal places.', - integer: 'Field must be an integer.', - invalidCountryList: 'You must select destination countries.' -}; - -Packlink.successMsgs = { - shippingMethodSaved: 'Shipping service successfully saved.' -}; diff --git a/src/BusinessLogic/Resources/js/TranslationService.js b/src/BusinessLogic/Resources/js/TranslationService.js new file mode 100644 index 00000000..186f0213 --- /dev/null +++ b/src/BusinessLogic/Resources/js/TranslationService.js @@ -0,0 +1,110 @@ +if (!window.Packlink) { + window.Packlink = {}; +} + +(function () { + /** + * A Translation service. This class turns an input key and params to the translated text. + * The translations are used from the global Packlink.translations object. It expects two keys in this object: + * 'current' and 'default', where 'current' holds the translations for the current language, + * and 'default' holds the translations in the default language. The 'default' will be used as a fallback if + * the 'current' object does not have the given entry. Both properties should be objects with the "section - key" + * format. For example: + * current: { + * login: { + * title: 'The title', + * subtitle: 'This is the subtitle of the %s app.' + * }, + * secondPage: { + * title: 'The second page title', + * description: 'Use this page to set the second thing.' + * } + * } + * + * With this in mind, the translation keys are in format "section.key", for example "login.title". + * + * @constructor + */ + function TranslationService() { + /** + * Gets the translation from the dictionary if exists. + * + * @param {'default' | 'current'} type + * @param {string} group + * @param {string} key + * @returns {null|string} + */ + const getTranslation = (type, group, key) => { + if (Packlink.translations[type][group] && Packlink.translations[type][group][key]) { + return Packlink.translations[type][group][key]; + } + + return null; + }; + + /** + * Replaces the parameters in the given text, if any. + * + * @param {string} text + * @param {[]} params + * @return {string} + */ + const replaceParams = (text, params) => { + if (!params) { + return text; + } + + let i = 0; + return text.replace(/%s/g, function () { + const param = params[i] !== undefined ? params[i] : '%s'; + i++; + + return param; + }); + }; + + /** + * Returns a translated string based on the input key and given parameters. If the string to translate + * has parameters, the placeholder is "%s". For example: Input key %s is not valid. This method will + * replace parameters in the order given in the params array, if any. + * + * @param {string} key The translation key in format "group.key". + * @param {[]} [params] An array of parameters to be replaced in the output string. + * + * @return {string} + */ + this.translate = (key, params) => { + const keys = key.split('.'); + + const result = getTranslation('current', keys[0], keys[1]) || getTranslation('default', keys[0], keys[1]); + if (result) { + return replaceParams(result, params); + } + + return key; + }; + + /** + * Replaces the translations in the given HTML code. + * + * @param {string} html + * @return {string} The updated HTML. + */ + this.translateHtml = (html) => { + // Replace the placeholders for translations. They are in the format {$key|param1|param2}. + let format = /{\$[.\-_A-Za-z|]+}/g; + const me = this; + + return html.replace(format, (key) => { + // remove the placeholder characters to get "key|param1|param2" + key = key.substr(2, key.length - 3); + // split parameters + let params = key.split('|'); + + return me.translate(params[0], params.slice(1)) || key; + }); + } + } + + Packlink.translationService = new TranslationService(); +})(); diff --git a/src/BusinessLogic/Resources/js/UtilityService.js b/src/BusinessLogic/Resources/js/UtilityService.js index d32f8b01..0213951b 100644 --- a/src/BusinessLogic/Resources/js/UtilityService.js +++ b/src/BusinessLogic/Resources/js/UtilityService.js @@ -1,51 +1,39 @@ -var Packlink = window.Packlink || {}; +if (!window.Packlink) { + window.Packlink = {}; +} (function () { function UtilityService() { /** - * Adds proper event listeners to input fields in order to allow input filed label translation. - */ - this.configureInputElements = function () { - let inputContainers = document.getElementsByClassName('pl-text-input'); - - for (let container of inputContainers) { - let input = container.getElementsByTagName('input')[0]; - if (typeof input !== 'undefined') { - textInputTransformLabel(input); - input.addEventListener('focus', textInputFocusHandler, true); - input.addEventListener('focusout', textInputFocusHandler, true); - } - } - }; - - /** - * Enables input mask. This mask disables input fields, buttons, checkboxes etc. - * Mask has z-index of 100, therefore an element that has to be excluded from input mask - * has to have z-index greater than 100; + * Shows the HTML node. + * + * @param {HTMLElement} element */ - this.enableInputMask = function () { - document.getElementById('pl-input-mask').classList.add('enabled'); + this.showElement = (element) => { + element.classList.remove('pl-hidden'); }; /** - * Disables input mask. + * Hides the HTML node. + * + * @param {HTMLElement} element */ - this.disableInputMask = function () { - document.getElementById('pl-input-mask').classList.remove('enabled'); + this.hideElement = (element) => { + element.classList.add('pl-hidden'); }; /** * Enables loading spinner. */ - this.showSpinner = function () { - document.getElementById('pl-spinner').classList.add('enabled'); + this.showSpinner = () => { + this.showElement(document.getElementById('pl-spinner')); }; /** * Hides loading spinner. */ - this.hideSpinner = function () { - document.getElementById('pl-spinner').classList.remove('enabled'); + this.hideSpinner = () => { + this.hideElement(document.getElementById('pl-spinner')); }; /** @@ -55,29 +43,31 @@ var Packlink = window.Packlink || {}; * * @param {string} message * @param {'danger' | 'warning' | 'success'} status + * @param {number} [clearAfter] Time in ms to remove alert message. */ - this.showFlashMessage = function (message, status) { - let statuses = [ - 'warning', - 'danger', - 'success' - ]; - - let messageNode = document.getElementById('pl-flash-message'); - messageNode.classList.remove(...statuses); - messageNode.classList.add(status); - - let textNode = document.getElementById('pl-flash-message-text'); + this.showFlashMessage = (message, status, clearAfter) => { + let messageNode = document.createElement('div'); + messageNode.innerHTML = Packlink.templateService.getComponent('pl-alert').innerHTML; + messageNode = messageNode.firstElementChild; + + const alertBox = messageNode.querySelector('.pl-alert'); + + alertBox.classList.add('pl-alert-' + status); + + let textNode = messageNode.querySelector('.pl-alert-text'); textNode.innerHTML = message; - let hideHandler = function () { - messageNode.style.display = 'none'; + const hideHandler = () => { + messageNode.remove(); }; - let closeButton = document.getElementById('pl-flash-message-close-btn'); - closeButton.addEventListener('click', hideHandler, true); + if (clearAfter) { + setTimeout(hideHandler, clearAfter); + } + + messageNode.addEventListener('click', hideHandler, true); - messageNode.style.display = 'flex'; + Packlink.templateService.getMainPage().appendChild(messageNode); }; /** @@ -89,25 +79,24 @@ var Packlink = window.Packlink || {}; * @param {object} obj * @return {object} */ - this.cloneObject = function (obj) { - return JSON.parse(JSON.stringify(obj)); - }; + this.cloneObject = (obj) => JSON.parse(JSON.stringify(obj)); /** * Debounces function. * * @param {number} delay - * @param {function} target - * @return {Function} + * @param {function(...)} target + * @param {...} target function args. + * @return {function(...)} */ - this.debounce = function (delay, target) { + this.debounce = (delay, target) => { let timerId; - return function (...args) { + return (...args) => { if (timerId) { clearTimeout(timerId); } - timerId = setTimeout(function () { + timerId = setTimeout(() => { target(...args); timerId = null; }, delay); @@ -117,17 +106,17 @@ var Packlink = window.Packlink || {}; /** * Adds given character as a prefix to the given input so that result always has the same string length. * If the input is "56", length is 4 and character is "0", resulting string will be "0056". - * If the input is "P", length is 2 and character is "-" resulting string will be "-4". + * If the input is "P", length is 2 and character is "-" resulting string will be "-P". * If the input is "40", length is 2, resulting string will be "40". * If the input is "TEXT", length is 2, resulting string will be "TEXT". * - * @param input A string or number to pad. + * @param {number|string} input A string or number to pad. * @param {number} length Total length of the final string. * @param {string} character The character to pad to the beginning. Defaults to "0". * * @returns {string} Padded string. */ - this.pad = function (input, length, character) { + this.pad = (input, length, character) => { let prefix = ''; for (let i = 0; i < length; i++) { prefix += character ? character : '0'; @@ -136,25 +125,26 @@ var Packlink = window.Packlink || {}; return (prefix + input).slice(length * -1); }; - /** PRIVATE METHODS **/ /** - * @param {Event} event + * Converts a collection to array. + * + * @param collection + * @return {[]} */ - function textInputFocusHandler(event) { - textInputTransformLabel(event.target); - } + this.toArray = (collection) => { + if (Array.prototype.from) { + return Array.from(collection); + } - /** - * @param input - */ - function textInputTransformLabel(input) { - let isSelected = document.activeElement === input || input.value, - spans = input.parentNode.getElementsByTagName('span'); + const result = [], + length = collection.length; - if (spans && spans.length > 0) { - spans[0].className = 'pl-text-input-label' + (isSelected ? ' selected' : ''); + for (let i = 0; i < length; i++) { + result.push(collection[i]); } - } + + return result; + }; } Packlink.utilityService = new UtilityService(); diff --git a/src/BusinessLogic/Resources/js/ValidationService.js b/src/BusinessLogic/Resources/js/ValidationService.js new file mode 100644 index 00000000..9c2cbd2a --- /dev/null +++ b/src/BusinessLogic/Resources/js/ValidationService.js @@ -0,0 +1,312 @@ +if (!window.Packlink) { + window.Packlink = {}; +} + +(function () { + /** + * @typedef ValidationMessage + * @property {string} code The message code. + * @property {string} field The field name that the error is related to. + * @property {string} message The error message. + */ + + const inputType = { + number: 'number', + email: 'email', + phone: 'phone', + password: 'password', + text: 'text' + }; + + const validationRule = { + numeric: 'numeric', + integer: 'integer', + greaterThanZero: 'greaterThanZero', + nonNegative: 'nonNegative' + }; + + /** + * The ValidationService constructor. + * + * @constructor + */ + function ValidationService() { + const translationService = Packlink.translationService, + templateService = Packlink.templateService, + utilityService = Packlink.utilityService; + + /** + * Sets form validation. + * + * @param {HTMLElement} form + * @param {string[]} fields + */ + this.setFormValidation = (form, fields) => { + for (const field of fields) { + let input = form[field]; + input.addEventListener('blur', (event) => { + // noinspection JSCheckFunctionSignatures + this.validateInputField(event.target); + }, true); + input.addEventListener('input', (event) => { + // noinspection JSCheckFunctionSignatures + this.removeError(event.target); + }, true); + } + }; + + /** + * Validates form. Validates all input and select elements by using data attributes as rules. + * + * @param {Element} form + * @param {string[]} excludedElementNames + * @return {boolean} + */ + this.validateForm = (form, excludedElementNames = []) => { + const inputElements = utilityService.toArray(form.getElementsByTagName('input')), + selects = utilityService.toArray(form.getElementsByTagName('select')), + inputs = inputElements.concat(selects), + length = inputs.length; + + let result = true; + + for (let i = 0; i < length; i++) { + if (excludedElementNames.indexOf(inputs[i].name) >= 0) { + continue; + } + result &= this.validateInputField(inputs[i]); + } + + return result; + }; + + /** + * Validates a single input element based on the element type and validation rules. + * Adds a validation error if needed. + * + * @param {Element|HTMLInputElement} input + * @return {boolean} + */ + this.validateInputField = (input) => { + this.removeError(input); + + const data = input.dataset; + + if (data.required !== undefined && !this.validateRequiredField(input)) { + return false; + } + + switch (data.type) { + case inputType.number: + return this.validateNumber(input); + case inputType.email: + return this.validateEmail(input); + case inputType.phone: + return this.validatePhone(input); + case inputType.password: + return this.validatePasswordLength(input); + case inputType.text: + return this.validateMaxLength(input); + } + + return true; + }; + + /** + * Validates if the input has a value. If the value is not set, adds the error mark on field. + * + * @param {HTMLInputElement|HTMLSelectElement} input + * @return {boolean} + */ + this.validateRequiredField = (input) => validateField( + input, + input.value === '' || (input.type === 'checkbox' && !input.checked), + 'validation.requiredField' + ); + + /** + * Validates a numeric input. + * + * @param {HTMLInputElement} input + * @return {boolean} Indication of the validity. + */ + this.validateNumber = (input) => { + const ruleset = input.dataset.validationRule ? input.dataset.validationRule.split(',') : [], + length = ruleset.length; + + if (!validateField(input, isNaN(input.value), 'validation.' + validationRule.numeric)) { + return false; + } + + const value = +input.value; + for (let i = 0; i < length; i++) { + const rule = ruleset[i]; + let condition = false; + switch (rule) { + case validationRule.integer: + condition = Number.isInteger(value); + break; + case validationRule.greaterThanZero: + condition = value > 0; + break; + case validationRule.nonNegative: + condition = value >= 0; + break; + default: + continue; + } + + if (!validateField(input, !condition, 'validation.' + rule)) { + // break on first rule + return false; + } + } + + return true; + }; + + /** + * Validates if the input is a valid email. If not, adds the error mark on field. + * + * @param {HTMLInputElement} input + * @return {boolean} + */ + this.validateEmail = (input) => { + let regex = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; + + return validateField( + input, + !regex.test(String(input.value).toLowerCase()), + 'validation.invalidEmail' + ); + }; + + /** + * Validates if the input is a valid phone number. If not, adds the error mark on field. + * + * @param {HTMLInputElement} input + * @return {boolean} + */ + this.validatePhone = (input) => { + let regex = /^( |\+|\/|\.\|-|\(|\)|\d)+$/m; + + return validateField( + input, + !regex.test(String(input.value).toLowerCase()), + 'validation.invalidPhone' + ); + }; + + /** + * Validates if the input field has enough characters. If not, adds the error mark on field. + * + * @param {HTMLInputElement} input + * @return {boolean} + */ + this.validatePasswordLength = (input) => validateField( + input, + input.value.length < input.dataset.minLength, + 'validation.shortPassword', + [input.dataset.minLength] + ); + + /** + * Validates if the input field is longer than a specified number of characters. + * If so, adds the error mark on field. + * + * @param {HTMLInputElement} input + * @return {boolean} + */ + this.validateMaxLength = (input) => validateField( + input, + input.dataset.maxLength && input.value.length > input.dataset.maxLength, + 'validation.maxLength', + [input.dataset.maxLength] + ); + + /** + * Handles validation errors. These errors come from the back end. + * @param {ValidationMessage[]} errors + */ + this.handleValidationErrors = (errors) => { + for (const error of errors) { + this.markFieldGroupInvalid('[name=' + error.field + ']', error.message); + } + }; + + /** + * Marks a field as invalid. + * + * @param {string} fieldSelector The field selector. + * @param {string} message The message to display. + * @param {Element} [parent] A parent element. + */ + this.markFieldGroupInvalid = (fieldSelector, message, parent) => { + if (!parent) { + parent = templateService.getMainPage(); + } + + const inputEl = parent.querySelector(fieldSelector); + if (!inputEl) { + utilityService.showFlashMessage(message + '. Field: ' + fieldSelector, 'danger', 7000); + } else { + this.setError(inputEl, message); + + } + }; + + /** + * Sets error for an input. + * + * @param {Element} input + * @param {string} message + */ + this.setError = (input, message) => { + this.removeError(input); + + let errorTemplate = document.createElement('div'); + input.setAttribute('data-pl-contains-errors', '1'); + errorTemplate.innerHTML = templateService.getComponent('pl-error-template').innerHTML; + errorTemplate.firstElementChild.innerHTML = message; + input.after(errorTemplate.firstElementChild); + input.parentElement.classList.add('pl-error'); + }; + + /** + * Removes error from input form group element. + * + * @param {Element} input + */ + this.removeError = (input) => { + let errorElement = input.parentNode.querySelector('.pl-error-message'); + if (errorElement) { + input.parentNode.removeChild(errorElement); + } + + input.removeAttribute('data-pl-contains-errors'); + input.parentElement.classList.remove('pl-error'); + }; + + /** + * Validates the condition against the input field and marks field invalid if the error condition is met. + * + * @param {Element} element + * @param {boolean} errorCondition Error condition. + * @param {string} errorCode + * @param {*[]} [errorParams] + * + * @return {boolean} + */ + const validateField = (element, errorCondition, errorCode, errorParams) => { + if (errorCondition) { + this.setError(element, translationService.translate(errorCode, errorParams)); + + return false; + } + + return true; + }; + } + + Packlink.validationService = new ValidationService(); +})(); \ No newline at end of file diff --git a/src/BusinessLogic/Resources/lang/de.json b/src/BusinessLogic/Resources/lang/de.json new file mode 100644 index 00000000..c63762cb --- /dev/null +++ b/src/BusinessLogic/Resources/lang/de.json @@ -0,0 +1,355 @@ +{ + "general": { + "packlinkPro": "Packlink PRO Shipping", + "saveUp": "Sparen Sie bis zu 70% bei Ihren Versandkosten. Keine festen Gebühren, kein Mindestvolumen beim Versand. Verwalten Sie alle Ihre Sendungen auf einer einzigen Plattform.", + "packlinkShipping": "Packlink Shipping S.L.", + "noContract": "Ohne Vertragsbindung – Sofortiger Zugriff auf über 300 Versanddienste in einer einzigen Plattform. Verbinden Sie Ihren Onlineshop und steuern Sie alle Sendungen über Packlink PRO.", + "developedAndManaged": "Entwickelt und verwaltet von Packlink", + "generalConditions": "Allgemeine Bedingungen", + "basicSettings": "Einstellungen", + "contactUs": "Kontaktieren Sie uns", + "systemInfo": "Systeminformationen", + "debugMode": "Debug mode", + "help": "Hilfe", + "downloadSystemInfoFile": "Systeminfodatei herunterladen", + "curlIsNotInstalled": "cURL ist in Ihrer PHP-Installation nicht installiert oder aktiviert. Dies ist jedoch erforderlich, damit die Hintergrundaufgabe funktioniert. Bitte installieren Sie es und aktualisieren Sie diese Seite.", + "loading": "Lädt...", + "save": "Speichern", + "saveChanges": "Speichern", + "accept": "Akzeptieren", + "cancel": "Abbrechen", + "continue": "Weiter", + "complete": "Vollständig", + "add": "Hinzufügen", + "edit": "Bearbeiten", + "delete": "Löschen", + "discard": "Verwerfen" + }, + "validation": { + "requiredField": "Dies ist ein Pflichtfeld.", + "invalidField": "This field is invalid.", + "invalidEmail": "Das Feld muss eine gültige E-Mail-Adresse sein.", + "shortPassword": "Das Passwort muss mindestens %s Zeichen haben.", + "invalidPhone": "Bitte geben Sie eine gültige Telefonnummer ein.", + "maxLength": "Dieses Feld darf maximal %s Zeichen enthalten.", + "numeric": "Bitte geben Sie einen numerischen Wert ein.", + "greaterThanZero": "Der Wert muss größer als 0 sein.", + "nonNegative": "Das Feld darf keine negativen Werte enthalten.", + "numberOfDecimalPlaces": "Das Feld muss 2 Dezimalstellen haben.", + "integer": "Das Feld muss eine ganze Zahl sein.", + "invalidCountryList": "Bitte wählen Sie die Zielländer aus.", + "invalidLanguage": "Dieses Feld enthält keine gültige Sprache.", + "invalidPlatformCountry": "Dieses Feld enthält kein gültiges Land.", + "invalidUrl": "Dieses Feld muss eine gültige URL enthalten.", + "invalidFieldValue": "Dieses Feld muss auf „%s“ gesetzt werden.", + "invalidMaxValue": "Der maximale Wert beträgt %s." + }, + "login": { + "apiKey": "API-Schlüssel", + "apiKeyIncorrect": "Falscher API-Schlüssel", + "dontHaveAccount": "Sie haben noch keinen Account?", + "welcome": "Willkommen zu Packlink PRO", + "connectYourService": "Verbinden Sie Ihren Dienst, indem Sie Ihren API-Schlüssel verwenden. Sie finden ihn unter
„Packlink Pro API-Schlüssel“ bei den „Einstellungen“.", + "validateApiKey": "API-Schlüssel bestätigen", + "chooseYourCountry": "Wählen Sie Ihr Land aus, um mit der Registrierung beginnen zu können", + "registerMe": "Registrieren" + }, + "countries": { + "ES": "Spanien", + "DE": "Deutschland", + "FR": "Frankreich", + "IT": "Italien", + "AT": "Österreich", + "NL": "Niederlande", + "BE": "Belgien", + "PT": "Portugal", + "TR": "Türkei", + "IE": "Irland", + "GB": "Vereinigtes Königreich", + "HU": "Ungarn", + "PL": "Polen", + "CH": "Schweiz", + "LU": "Luxemburg", + "AR": "Argentinien", + "US": "Vereinigte Staaten", + "BO": "Bolivien", + "MX": "Mexiko", + "CL": "Chile", + "CZ": "Tschechien", + "SE": "Schweden" + }, + "register": { + "logIn": "Einloggen", + "email": "E-Mail", + "password": "Passwort", + "haveAccount": "Haben Sie bereits einen Account?", + "startEnjoying": "Genießen Sie ab jetzt Packlink PRO!", + "registerAccount": "Jetzt registrieren", + "getToKnowYou": "Wir möchten Sie besser kennenlernen, um uns an Sie anzupassen und Ihnen ein individuelles Angebot zukommen zu lassen", + "monthlyShipmentVolume": "Wie hoch ist Ihr monatliches Sendungsaufkommen?", + "phone": "Telefonnummer", + "termsAndConditions": "
Ich akzeptiere die Nutzungsbedingungen und die Datenschutzerklärung von Packlink
", + "marketingEmails": "Ich ermächtige Packlink Shipping S.L., mir kommerzielle Mitteilungen per E-Mail zuzusenden", + "submit": "Registrieren", + "chooseYourCountry": "Wählen Sie Ihr Land aus", + "searchCountry": "Land suchen", + "invalidDeliveryVolume": "Das Feld enthält keinen gültigen Lieferumfang." + }, + "onboardingWelcome": { + "header": "Richten Sie nun die grundlegenden Informationen ein, damit Sie Versendungen vornehmen können", + "steps": "In nur zwei Schritten können wir Ihnen den Spediteur anbieten, der all Ihre Anforderungen erfüllt", + "stepOne": "Paketangaben einfügen", + "stepTwo": "Absenderadresse eingeben", + "startSetUp": "Einrichten beginnen" + }, + "onboardingOverview": { + "header": "Fast geschafft! Bitte überprüfen Sie, ob die eingegebenen Informationen richtig sind oder vervollständigen Sie die Angaben, um fortzufahren.", + "parcelDetails": "Paketangaben", + "senderAddress": "Absenderadresse", + "missingInfo": "Fehlende Informationen", + "parcelData": "Gewicht %s kg | Höhe %s cm | Breite %s cm | Länge %s cm", + "warehouseData": "%s | %s | %s" + }, + "defaultParcel": { + "title-onboarding": "1. Paketangaben einfügen", + "description-onboarding": "Wir werden diese Standarddaten für Artikel verwenden, die keine festgelegten Abmessungen und kein festgelegtes Gewicht haben. Sie können sie jederzeit über die Registerkarte Einstellungen bearbeiten.", + "title-config": "Standardpaket", + "description-config": "Wir werden diese Daten für Artikel verwenden, die keine festgelegten Abmessungen und kein festgelegtes Gewicht haben.", + "weight": "Gewicht", + "height": "Höhe", + "width": "Breite", + "length": "Länge" + }, + "defaultWarehouse": { + "title-onboarding": "2. Absenderadresse eingeben", + "description-onboarding": "Wir werden diese Adresse verwenden, um einen Standardabsender für alle Sendungen zu erstellen. Sie können die Adresse jederzeit über die Registerkarte Einstellungen bearbeiten.", + "title-config": "Standard-Absenderadresse", + "description-config": "Wir werden diese Adresse verwenden, um einen Standardabsender für alle Sendungen zu erstellen. Sie können die Adresse jederzeit bearbeiten.", + "alias": "Absendername", + "alias-placeholder": "Hauptlager", + "name": "Name der Kontaktperson", + "name-placeholder": "Name", + "surname": "Nachname der Kontaktperson", + "surname-placeholder": "Nachname", + "company": "Unternehmen", + "company-placeholder": "Unternehmen", + "country": "Land", + "postal_code": "Stadt oder Postleitzahl", + "postal_code-placeholder": "-", + "address": "Straße und Hausnummer", + "address-placeholder": "Anschrift", + "phone": "Telefonnummer", + "phone-placeholder": "123 456 7777", + "email": "E-Mail-Adresse", + "email-placeholder": "ihreemail@example.com" + }, + "configuration": { + "menu": "Einstellungen", + "title": "Einstellungen", + "orderStatus": "Bestellstatus", + "warehouse": "Standard-Absenderadresse", + "parcel": "Standardpaket", + "help": "Hilfe", + "contactUs": "Kontaktieren Sie uns:", + "systemInfo": "Systeminformationen" + }, + "systemInfo": { + "title": "Systeminfodatei
und debug mode", + "debugMode": "Debug mode", + "download": "Systeminfodatei herunterladen" + }, + "orderStatusMapping": { + "title": "Bestellstatus", + "description": "Mit Packlink können Sie Ihren %s Bestellstatus mit Versandinformationen aktualisieren.
Sie können die Informationen jederzeit bearbeiten.", + "packlinkProShipmentStatus": "VERSANDSTATUS von Packlink PRO", + "systemOrderStatus": "BESTELLSTATUS von %s", + "none": "Leer", + "pending": "Ausstehend", + "processing": "In Bearbeitung", + "readyForShipping": "Versandfertig", + "inTransit": "Unterwegs", + "delivered": "Zugestellt", + "cancelled": "Storniert" + }, + "shippingServices": { + "myServices": "Mein Versandservice", + "noServicesTitle": "Sie brauchen nur Ihre Transportdienstleistung hinzuzufügen", + "noServicesDescription": "Sie werden Ihrem Kunden zum Zeitpunkt der Kaufabwicklung angezeigt, damit er auswählen kann, welcher Spediteur sein Paket ausliefert. Richten Sie es jetzt ein, damit Sie sich um nichts anderes kümmern müssen. Sie können es jederzeit ändern, um die Versanddienste zu erneuern oder zu aktualisieren.", + "addService": "Dienstleistung hinzufügen", + "addNewService": "Neue Dienstleistung hinzufügen", + "addedSuccessTitle": "Versanddienst erfolgreich hinzugefügt", + "addedSuccessDescription": "Sie können die Dienstleistung unter „Meine Dienstleistungen“ bearbeiten.", + "deletedSuccessTitle": "Versanddienst erfolgreich gelöscht", + "deletedSuccessDescription": "Über die Schaltfläche „Neue Dienstleistung hinzufügen“ können Sie neue Dienstleistungen hinzufügen.", + "disableCarriersTitle": "Sie haben Ihre erste Transportdienstleistung erstellt. Möchten Sie die vorherigen Spediteure deaktivieren?", + "disableCarriersDescription": "Um Ihnen einen besseren Service anbieten zu können, sollten Sie die vorher genutzten Spediteure deaktivieren.", + "successfullyDisabledShippingMethods": "Versanddienste erfolgreich deaktiviert.", + "failedToDisableShippingMethods": "Versanddienste konnten nicht deaktiviert werden.", + "pickShippingService": "Lieferdienst hinzufügen", + "failedGettingServicesTitle": "Beim Abrufen der Versanddienste ist ein Problem aufgetreten", + "failedGettingServicesSubtitle": "Möchten Sie es erneut versuchen?", + "retry": "Erneut versuchen", + "filterModalTitle": "Filter", + "applyFilters": "Anwenden", + "type": "Typ", + "national": "National", + "international": "International", + "deliveryType": "Versandart", + "economic": "Standard", + "express": "Express", + "parcelOrigin": "Absendeort des Pakets", + "collection": "Abholung an Adresse", + "dropoff": "Abgabe im Paketshop", + "parcelDestination": "Zielort des Pakets", + "pickup": "Abholung im Paketshop", + "delivery": "Zustellung an Adresse", + "carrierLogo": "Logo des Versandunternehmens", + "carrier": "Versandpartner", + "serviceTitle": "Name des Dienstes", + "serviceTitleDescription": "Personalisieren Sie Ihren Versand, indem Sie ihn bearbeiten. Ihre Kunden werden es sehen können.", + "transitTime": "Versandlaufzeit", + "origin": "Absender", + "destination": "Empfänger", + "myPrices": "Meine Preise", + "packlinkPrices": "Packlink-Preise", + "configureService": "Dienst einrichten", + "showCarrierLogo": "Meinen Kunden das Logo anzeigen", + "pricePolicy": "Preispolitik", + "pricePolicyDescription": "Standardmäßig werden die Packlink Basispreise eingestellt sein. Sie können diese jedoch weiter unten genauer konfigurieren.", + "configurePricePolicy": "Meine Versandkosten einrichten", + "taxClassTitle": "Wählen Sie Ihre gespeicherte Steuerkategorie", + "tax": "Steuer", + "serviceCountriesTitle": "Verfügbarkeit nach Zielland", + "serviceCountriesDescription": "Wählen Sie die Verfügbarkeit der Länder, die von Ihrem Versanddienst unterstützt werden.", + "openCountries": "Länder anzeigen", + "allCountriesSelected": "Es wurden alle Länder ausgewählt", + "oneCountrySelected": "Es wurde ein Land ausgewählt", + "selectedCountries": "Es wurden %s Länder ausgewählt", + "firstPricePolicyDescription": "Perfekt, nur noch wenige Schritte:

1. Wählen Sie aus, ob Sie diese nach Gewicht und / oder Kaufpreis einstellen möchten.

2. Wählen Sie eine Preispolitik und richten Sie diese ein.", + "addFirstPolicy": "Preisregel hinzufügen", + "addAnotherPolicy": "Weitere Regel hinzufügen", + "from": "Von", + "to": "Bis", + "price": "Preis", + "rangeTypeExplanation": "1. Wählen Sie aus, ob Sie diese nach Gewicht und / oder Kaufpreis einstellen möchten.", + "rangeType": "Typenreihe", + "priceRange": "Preisklasse", + "priceRangeWithData": "Preisklasse: von %s € bis %s €", + "weightRange": "Gewichtsklasse", + "weightRangeWithData": "Gewichtsklasse: von %s kg bis %s kg", + "weightAndPriceRange": "Gewichts- und Preisklasse", + "weightAndPriceRangeWithData": "Gewichts- und Preisklasse: von %s kg bis %s kg und von %s € bis %s €", + "singlePricePolicy": "Preispolitik %s", + "pricePolicyExplanation": "2. Wählen Sie eine Preispolitik und richten Sie diese ein.", + "packlinkPrice": "Packlink-Preise", + "percentagePacklinkPrices": "% der Packlink-Preise", + "percentagePacklinkPricesWithData": "% der Packlink-Preise: %s von %s%", + "fixedPrices": "Festpreis", + "fixedPricesWithData": "Festpreis: %s €", + "increaseExplanation": "Erhöhen: fügen Sie % zum Preis hinzu. Dies wird dann vom Kunden bezahlt.", + "reduceExplanation": "Reduzieren: ziehen Sie % vom Preis ab. Dies werden Sie dann selbst zahlen.", + "select": "Auswählen", + "invalidRange": "Unzulässiger Bereich", + "increase": "Erhöhen", + "reduce": "Reduzieren", + "selectAllCountries": "Alle ausgewählten Länder", + "selectCountriesHeader": "Länder, die von Ihrem
Versanddienst unterstützt werden", + "selectCountriesSubheader": "Wählen Sie die Verfügbarkeit von mindestens einem Land aus und fügen Sie
beliebig viele hinzu", + "usePacklinkRange": "Für alle anderen Bereiche gelten die Packlink-Preise", + "discardChangesQuestion": "Es gibt nicht gespeicherte Änderungen.
Sind Sie sicher, dass Sie zurückgehen und sie verwerfen möchten?", + "atLeastOneCountry": "" + }, + "orderListAndDetails": { + "packlinkOrderDraft": "Packlink Auftragsentwurf", + "printed": "Ausgedruckt", + "ready": "Bereit", + "printLabel": "Versandetikett drucken", + "shipmentLabels": "Versandetiketten", + "printShipmentLabels": "Packlink PRO Versandetiketten drucken", + "shipmentDetails": "Versanddetails", + "disablePopUpBlocker": "Bitte deaktivieren Sie den Popup-Blocker auf dieser Seite, um alle Versandetiketten auf einmal zu öffnen.", + "date": "Datum", + "number": "Nummer", + "status": "Status", + "print": "Drucken", + "carrierTrackingNumbers": "Trackingnummer des Versandunternehmens", + "trackIt": "Versand verfolgen", + "packlinkReferenceNumber": "Packlink Referenznummer", + "packlinkShippingPrice": "Packlink Versandpreis", + "viewOnPacklink": "Auf Packlink PRO ansehen", + "createDraft": "Entwurf erstellen", + "draftIsBeingCreated": "Der Entwurf wird gerade in Packlink PRO erstellt", + "createOrderDraft": "Entwurf der Bestellung in Packlink PRO erstellen", + "draftCreateFailed": "Der Versuch der Entwurfserstellung ist fehlgeschlagen. Fehler: %s", + "packlinkShipping": "Packlink Shipping", + "shipmentOrderNotExist": "Die Bestellung mit der Versandreferenznummer %s existiert nicht im Shop", + "orderDetailsNotFound": "Bestelldetails nicht gefunden", + "orderNotExist": "Die Bestellung mit der ID %s existiert nicht im Shop", + "bulkFailCreateFail": "Die Datei der Versandetiketten konnte nicht erstellt werden", + "draftOrderDelayed": "Die Erstellung des Entwurfs verzögert sich", + "completeServicesSetup": "You need to complete the services setup to create order draft.", + "sendWithPacklink": "Send with Packlink" + }, + "checkoutProcess": { + "choseDropOffLocation": "Dieser Versanddienst unterstützt die Zustellung an vordefinierte Abgabestellen. Bitte wählen Sie den für Sie günstigsten Ort aus, indem Sie auf \"Abgabestelle auswählen\" klicken.", + "selectDropOffLocation": "Abgabestelle auswählen", + "changeDropOffLocation": "Abgabestelle ändern", + "packageDeliveredTo": "Das Paket wird geliefert an:", + "dropOffDeliveryAddress": "Zustelladresse", + "changeAddress": "Für Ihre Zustelladresse sind keine Zustellorte verfügbar. Bitte ändern Sie Ihre Adresse.", + "shippingServiceNotFound": "Versanddienst nicht gefunden" + }, + "locationPicker": { + "monday": "Montag", + "tuesday": "Dienstag", + "wednesday": "Mittwoch", + "thursday": "Donnerstag", + "friday": "Freitag", + "saturday": "Samstag", + "sunday": "Sonntag", + "zipCode": "Postleitzahl", + "idCode": "ID Code", + "selectThisLocation": "Diesen Ort auswählen", + "showWorkingHours": "Öffnungszeiten anzeigen", + "hideWorkingHours": "Öffnungszeiten ausblenden", + "showOnMap": "Auf der Karte anzeigen", + "searchBy": "Suche nach Standortname, ID oder Adresse" + }, + "migrationLogMessages": { + "removeControllersHooksFailed": "Alte Controller und Hooks konnten nicht entfernt werden auf Grund von: %s", + "deleteObsoleteFilesFailed": "Alte Dateien konnten nicht gelöscht werden auf Grund von: %s", + "oldAPIKeyDetected.": "Alter API-Schlüssel erkannt.", + "successfullyLoggedIn": "Erfolgreich mit bestehendem API-Schlüssel angemeldet.", + "removingObsoleteFiles.": "Veraltete Dateien werden entfernt.", + "cannotEnqueueUpgradeShopDetailsTask": "UpgradeShopDetailsTask kann aus folgenden Gründen nicht in die Warteschlange gestellt werden: %s", + "deletingOldPluginDta.": "Alte Plugin-Daten werden gelöscht.", + "cannotRetrieveOrderReferences": "Bestellreferenzen können nicht abgerufen werden auf Grund von: %s", + "createOrderReferenceFailed": "Referenz für Auftrag %s konnte nicht erstellt werden.", + "setLabelsFailed": "Versandetiketten für Bestellung mit Referenz %s konnten nicht erstellt werden.", + "setTrackingInfoFailed": "Fehler beim Festlegen der Tracking-Informationen für die Bestellung mit Referenz %s", + "orderNotFound": "Auftrag mit Referenz %s nicht gefunden." + }, + "systemLogMessages": { + "errorCreatingDefaultTask": "Fehler beim Erstellen der Standardkonfiguration des Task-Runner-Status.", + "webhookReceived": "Webhook von Packlink erhalten", + "couldNotDelete": "Element %s mit ID %s konnte nicht gelöscht werden", + "entityCannotBeSaved": "Element %s mit ID %s kann nicht gespeichert werden.", + "entityCannotBeUpdated": "Element %s mit ID %s kann nicht aktualisiert werden.", + "fieldIsNotIndexed": "Feld %s ist nicht indiziert!", + "unknownOrNotIndexed": "Unbekannte oder nicht indizierte OrderBy-Spalte %s", + "dirNotCreated": "Das Verzeichnis \"%s\" wurde nicht erstellt", + "failedCreatingBackup": "Fehler beim Erstellen des Backup-Versanddienstes.", + "backupServiceNotFound": "Backup-Versanddienst konnte nicht gefunden werden." + }, + "autoTest": { + "dbNotAccessible.": "Zugriff auf Datenbank nicht möglich.", + "taskCouldNotBeStarted.": "Die Aufgabe konnte nicht gestartet werden.", + "moduleAutoTest": "Automatischer Test PacklinkPRO-Modul", + "useThisPageTestConfiguration": "Auf dieser Seite können Sie die Systemkonfiguration und die Dienste des PacklinkPRO-Moduls testen.", + "start": "Start", + "downloadTestLog": "Testprotokoll herunterladen", + "openModule": "PacklinkPRO-Modul öffnen", + "autotestPassedSuccessfully": "Der automatische Test wurde erfolgreich abgeschlossen!", + "testNotSuccessful": "Der Test wurde nicht erfolgreich abgeschlossen!" + } +} \ No newline at end of file diff --git a/src/BusinessLogic/Resources/lang/en.json b/src/BusinessLogic/Resources/lang/en.json new file mode 100644 index 00000000..5cc5d3bb --- /dev/null +++ b/src/BusinessLogic/Resources/lang/en.json @@ -0,0 +1,355 @@ +{ + "general": { + "packlinkPro": "Packlink PRO Shipping", + "saveUp": "Save up to 70% on your shipping costs. No fixed fees, no minimum shipping volume required. Manage all your shipments in a single platform.", + "packlinkShipping": "Packlink Shipping S.L.", + "noContract": "No contract needed! More than 300 transport services into a single platform. You can connect all your ecommerce and manage all your shipments in Packlink PRO.", + "developedAndManaged": "Developed and managed by Packlink", + "generalConditions": "General conditions", + "basicSettings": "Basic settings", + "contactUs": "Contact us", + "systemInfo": "System info", + "debugMode": "Debug mode", + "help": "Help", + "downloadSystemInfoFile": "Download system info file", + "curlIsNotInstalled": "cURL is not installed or enabled in your PHP installation. This is required for background task to work. Please install it and then refresh this page.", + "loading": "Loading...", + "save": "Save", + "saveChanges": "Save changes", + "accept": "Accept", + "cancel": "Cancel", + "continue": "Continue", + "complete": "Complete", + "add": "Add", + "edit": "Edit", + "delete": "Delete", + "discard": "Discard" + }, + "validation": { + "requiredField": "This field is required.", + "invalidField": "This field is invalid.", + "invalidEmail": "This field must be valid email.", + "shortPassword": "The password must be at least %s characters long.", + "invalidPhone": "This field must be valid phone number.", + "maxLength": "The field cannot have more than %s characters.", + "numeric": "Value must be valid number.", + "greaterThanZero": "Value must be greater than 0.", + "nonNegative": "Field cannot have a negative value.", + "numberOfDecimalPlaces": "Field must have 2 decimal places.", + "integer": "Field must be an integer.", + "invalidCountryList": "You must select destination countries.", + "invalidLanguage": "Field is not a valid language.", + "invalidPlatformCountry": "Field is not a valid platform country.", + "invalidUrl": "Field must be a valid URL.", + "invalidFieldValue": "Field must be set to \"%s\".", + "invalidMaxValue": "Maximal allowed value is %s." + }, + "login": { + "apiKey": "Api key", + "apiKeyIncorrect": "API key was incorrect.", + "dontHaveAccount": "Don't have an account?", + "welcome": "Welcome to Packlink PRO", + "connectYourService": "Connect your service using your key. You'll find it in the
\"Packlink Pro API Key\" section of the \"Settings\".", + "validateApiKey": "Validate API key", + "chooseYourCountry": "Choose your country to start the registration process", + "registerMe": "Register me" + }, + "countries": { + "ES": "Spain", + "DE": "Germany", + "FR": "France", + "IT": "Italy", + "AT": "Austria", + "NL": "Netherlands", + "BE": "Belgium", + "PT": "Portugal", + "TR": "Turkey", + "IE": "Ireland", + "GB": "Great Britain", + "HU": "Hungary", + "PL": "Poland", + "CH": "Switzerland", + "LU": "Luxembourg", + "AR": "Argentina", + "US": "United States", + "BO": "Bolivia", + "MX": "Mexico", + "CL": "Chile", + "CZ": "Czech Republic", + "SE": "Sweden" + }, + "register": { + "logIn": "Log in", + "email": "Email", + "password": "Password", + "haveAccount": "Already have an account?", + "startEnjoying": "Start enjoying Packlink PRO!", + "registerAccount": "Register your account", + "getToKnowYou": "We would like to get to know you better in order to adapt to you and to send you an individual offer", + "monthlyShipmentVolume": "What is your monthly shipment volume?", + "phone": "Phone number", + "termsAndConditions": "
I accept the Terms of Service and the Privacy Policy of Packlink
", + "marketingEmails": "I authorise Packlink Shipping S.L. to send me commercial communications by e-mail", + "submit": "Register", + "chooseYourCountry": "Choose your country", + "searchCountry": "Search country", + "invalidDeliveryVolume": "Field is not a valid delivery volume." + }, + "onboardingWelcome": { + "header": "Let's set up basic information so that you can make shipments", + "steps": "It's just two steps that we need to go through to offer you the carrier that best suits your needs", + "stepOne": "Set parcel details", + "stepTwo": "Set sender's address", + "startSetUp": "Start set up" + }, + "onboardingOverview": { + "header": "Almost there! Please check that the entered information is correct or complete it in order to continue.", + "parcelDetails": "Parcel details", + "senderAddress": "Sender's Address", + "missingInfo": "Missing information", + "parcelData": "Weight %s kg | Height %s cm | Width %s cm | Length %s cm", + "warehouseData": "%s | %s | %s" + }, + "defaultParcel": { + "title-onboarding": "1. Set parcel details", + "description-onboarding": "We will use this default data for items that do not have defined dimensions and weight. You can edit them whenever you like via the settings tab.", + "title-config": "Default parcel", + "description-config": "We will use this data for items that do not have defined dimensions and weight.", + "weight": "Weight", + "height": "Height", + "width": "Width", + "length": "Length" + }, + "defaultWarehouse": { + "title-onboarding": "2. Set sender’s address", + "description-onboarding": "We will use this address to create a default sender for all shipments. You will be able to edit it whenever you wish via the settings tab.", + "title-config": "Default sender address", + "description-config": "We will use this address to create a default sender for all shipments. You can edit it at any time.", + "alias": "Warehouse name", + "alias-placeholder": "Main warehouse", + "name": "Name of contact person", + "name-placeholder": "Name", + "surname": "Surname of contact person", + "surname-placeholder": "Surname", + "company": "Company name", + "company-placeholder": "Company", + "country": "Country", + "postal_code": "City or postcode", + "postal_code-placeholder": "-", + "address": "Address", + "address-placeholder": "Address", + "phone": "Phone number", + "phone-placeholder": "123 456 7777", + "email": "Email", + "email-placeholder": "youremail@example.com" + }, + "configuration": { + "menu": "Settings", + "title": "Settings", + "orderStatus": "Order status", + "warehouse": "Default sender address", + "parcel": "Default parcel", + "help": "Help", + "contactUs": "Contact us at:", + "systemInfo": "System information" + }, + "systemInfo": { + "title": "System information file
and debug mode", + "debugMode": "Debug mode", + "download": "Download system information" + }, + "orderStatusMapping": { + "title": "Order status", + "description": "With Packlink you can update your %s order status with shipping information.
You can edit it at any time.", + "packlinkProShipmentStatus": "Packlink PRO SHIPMENT STATUS", + "systemOrderStatus": "%s ORDER STATUS", + "none": "None", + "pending": "Pending", + "processing": "Processing", + "readyForShipping": "Ready for shipping", + "inTransit": "In transit", + "delivered": "Delivered", + "cancelled": "Canceled" + }, + "shippingServices": { + "myServices": "My shipping services", + "noServicesTitle": "You just need to add your shipment services", + "noServicesDescription": "They will be shown to your customer at the time of checkout, so that they can choose which carrier will deliver their parcel. Set it up now so that you don't need to worry about anything else. You can change it whenever you wish, to renew or upgrade shipping services.", + "addService": "Add service", + "addNewService": "Add new service", + "addedSuccessTitle": "Shipping service added successfully", + "addedSuccessDescription": "You can edit the service from the \"My services\" section.", + "deletedSuccessTitle": "Shipping service deleted successfully", + "deletedSuccessDescription": "You can add new services from the \"Add new service\" button.", + "disableCarriersTitle": "You have created your first shipment service. Do you want to disable previous carriers?", + "disableCarriersDescription": "To provide you with a better service, it is important to disable the carriers you previously used.", + "successfullyDisabledShippingMethods": "Successfully disabled shipping methods.", + "failedToDisableShippingMethods": "Failed to disable shipping methods.", + "pickShippingService": "Add delivery services", + "failedGettingServicesTitle": "We are having troubles getting shipping services", + "failedGettingServicesSubtitle": "Do you want to retry?", + "retry": "Retry", + "filterModalTitle": "Filters", + "applyFilters": "Apply", + "type": "Type", + "national": "Domestic", + "international": "International", + "deliveryType": "Type of shipment", + "economic": "Budget", + "express": "Express", + "parcelOrigin": "Parcel origin", + "collection": "Collection", + "dropoff": "Drop off", + "parcelDestination": "Parcel destination", + "pickup": "Pick up", + "delivery": "Delivery", + "carrierLogo": "Carrier logo", + "carrier": "Carrier", + "serviceTitle": "Service title", + "serviceTitleDescription": "Customise your shipment by editing it. Your customers will be able to see it.", + "transitTime": "Transit Time", + "origin": "Origin", + "destination": "Destination", + "myPrices": "My prices", + "packlinkPrices": "Packlink prices", + "configureService": "Set up service", + "showCarrierLogo": "Show carrier logo to my customers", + "pricePolicy": "Price policy", + "pricePolicyDescription": "The default will be Packlink's basic prices. But you can configure these in a more precise way below.", + "configurePricePolicy": "Set up my shipment prices", + "taxClassTitle": "Choose your saved tax class", + "tax": "Tax", + "serviceCountriesTitle": "Availability by destination country", + "serviceCountriesDescription": "Select the availability for the countries that are supported for your shipping service.", + "openCountries": "See countries", + "allCountriesSelected": "All countries selected", + "oneCountrySelected": "One country selected", + "selectedCountries": "%s countries selected.", + "firstPricePolicyDescription": "Perfect, just a couple of steps:

1. Choose whether you want to set them by weight and/or purchase price.

2. Choose a price policy and set it up.", + "addFirstPolicy": "Add price rule", + "addAnotherPolicy": "Add another rule", + "from": "From", + "to": "To", + "price": "Price", + "rangeTypeExplanation": "1. Choose whether you want to set them by weight and/or purchase price.", + "rangeType": "Range type", + "priceRange": "Price range", + "priceRangeWithData": "Price range: from %s€ to %s€", + "weightRange": "Weight range", + "weightRangeWithData": "Weight range: from %s kg to %s kg", + "weightAndPriceRange": "Weight and price range", + "weightAndPriceRangeWithData": "Weight and price range: from %s kg to %s kg and from %s€ to %s€", + "singlePricePolicy": "Price policy %s", + "pricePolicyExplanation": "2. Choose a price policy and set it up.", + "packlinkPrice": "Packlink Price", + "percentagePacklinkPrices": "% of Packlink prices", + "percentagePacklinkPricesWithData": "% of Packlink prices: %s by %s%", + "fixedPrices": "Fixed price", + "fixedPricesWithData": "Fixed price: %s€", + "increaseExplanation": "Increase: add a % to the price, this will be paid by the customer.", + "reduceExplanation": "Reduce: deduct a % from the price, you will pay for it yourself.", + "select": "Select", + "invalidRange": "Invalid range", + "increase": "increase", + "reduce": "reduce", + "selectAllCountries": "All selected countries", + "selectCountriesHeader": "Countries supported for your
shipping service", + "selectCountriesSubheader": "Select availability of at least one country and add
as many required", + "usePacklinkRange": "For all other ranges, apply Packlink prices", + "discardChangesQuestion": "There are unsaved changes.
Are you sure you want to go back and discard them?", + "atLeastOneCountry": "At least one country must be selected." + }, + "orderListAndDetails": { + "packlinkOrderDraft": "Packlink order draft", + "printed": "Printed", + "ready": "Ready", + "printLabel": "Print label", + "shipmentLabels": "Shipment Labels", + "printShipmentLabels": "Print Packlink PRO Shipment Labels", + "shipmentDetails": "Shipment details", + "disablePopUpBlocker": "Please disable pop-up blocker on this page in order to bulk open shipment labels", + "date": "Date", + "number": "Number", + "status": "Status", + "print": "Print", + "carrierTrackingNumbers": "Carrier tracking numbers", + "trackIt": "Track it!", + "packlinkReferenceNumber": "Packlink reference number", + "packlinkShippingPrice": "Packlink shipping price", + "viewOnPacklink": "View on Packlink PRO", + "createDraft": "Create Draft", + "draftIsBeingCreated": "Draft is currently being created in Packlink PRO", + "createOrderDraft": "Create order draft in Packlink PRO", + "draftCreateFailed": "Previous attempt to create a draft failed. Error: %s", + "packlinkShipping": "Packlink Shipping", + "shipmentOrderNotExist": "Order with shipment reference %s doesn't exist in the shop", + "orderDetailsNotFound": "Order details not found", + "orderNotExist": "Order with ID %s doesn't exist in the shop", + "bulkFailCreateFail": "Unable to create bulk labels file", + "draftOrderDelayed": "Creation of the draft is delayed", + "completeServicesSetup": "You need to complete the services setup to create order draft.", + "sendWithPacklink": "Send with Packlink" + }, + "checkoutProcess": { + "choseDropOffLocation": "This shipping service supports delivery to pre-defined drop-off locations. Please choose location that suits you the most by clicking on the \"Select drop-off location\" button.", + "selectDropOffLocation": "Select drop-off location", + "changeDropOffLocation": "Change drop-off location", + "packageDeliveredTo": "Package will be delivered to:", + "dropOffDeliveryAddress": "Drop-Off delivery address", + "changeAddress": "There are no delivery locations available for your delivery address. Please change your address.", + "shippingServiceNotFound": "Shipping service not found" + }, + "locationPicker": { + "monday": "Monday", + "tuesday": "Tuesday", + "wednesday": "Wednesday", + "thursday": "Thursday", + "friday": "Friday", + "saturday": "Saturday", + "sunday": "Sunday", + "zipCode": "Zip code", + "idCode": "ID code", + "selectThisLocation": "Select this location", + "showWorkingHours": "Show working hours", + "hideWorkingHours": "Hide working hours", + "showOnMap": "Show on map", + "searchBy": "Search by location name, id or address" + }, + "migrationLogMessages": { + "removeControllersHooksFailed": "Failed to remove old controllers and hooks because: %s", + "deleteObsoleteFilesFailed": "Could not delete obsolete files because: %s", + "oldAPIKeyDetected.": "Old API key detected.", + "successfullyLoggedIn": "Successfully logged in with existing api key.", + "removingObsoleteFiles.": "Removing obsolete files.", + "cannotEnqueueUpgradeShopDetailsTask": "Cannot enqueue UpgradeShopDetailsTask because: %s", + "deletingOldPluginDta.": "Deleting old plugin data.", + "cannotRetrieveOrderReferences": "Cannot retrieve order references because: %s", + "createOrderReferenceFailed": "Failed to create reference for order %s", + "setLabelsFailed": "Failed to set labels for order with reference %s", + "setTrackingInfoFailed": "Failed to set tracking info for order with reference %s", + "orderNotFound": "Order with reference %s not found." + }, + "systemLogMessages": { + "errorCreatingDefaultTask": "Error creating default task runner status configuration.", + "webhookReceived": "Webhook from Packlink received.", + "couldNotDelete": "Could not delete entity %s with ID %s", + "entityCannotBeSaved": "Entity %s with ID %s cannot be saved.", + "entityCannotBeUpdated": "Entity %s with ID %s cannot be updated.", + "fieldIsNotIndexed": "Field %s is not indexed!", + "unknownOrNotIndexed": "Unknown or not indexed OrderBy column %s", + "dirNotCreated": "Directory \"%s\" was not created", + "failedCreatingBackup": "Failed creating backup service.", + "backupServiceNotFound": "Backup service not found." + }, + "autoTest": { + "dbNotAccessible.": "Database not accessible.", + "taskCouldNotBeStarted.": "Task could not be started.", + "moduleAutoTest": "PacklinkPRO module auto-test", + "useThisPageTestConfiguration": "Use this page to test the system configuration and PacklinkPRO module services.", + "start": "Start", + "downloadTestLog": "Download test log", + "openModule": "Open PacklinkPRO module", + "autotestPassedSuccessfully": "Auto-test passed successfully!", + "testNotSuccessful": "The test did not complete successfully." + } +} \ No newline at end of file diff --git a/src/BusinessLogic/Resources/lang/es.json b/src/BusinessLogic/Resources/lang/es.json new file mode 100644 index 00000000..d914a7ba --- /dev/null +++ b/src/BusinessLogic/Resources/lang/es.json @@ -0,0 +1,355 @@ +{ + "general": { + "packlinkPro": "Packlink PRO Shipping", + "saveUp": "Ahorra hasta un 70% en tus gastos de envío. Sin tarifas fijas, sin volumen de envíos mínimo. Gestiona todos tus envíos en una sola plataforma.", + "packlinkShipping": "Packlink Shipping S.L.", + "noContract": "Sin contratos, accede inmediatamente a más de 300 servicios de transporte en una única plataforma. No importa cuantos puntos de venta tengas, todo lo podrás gestionar desde Packlink PRO.", + "developedAndManaged": "Desarrollado y gestionado por Packlink", + "generalConditions": "Condiciones generales", + "basicSettings": "Configuración", + "contactUs": "Contacta con nosotros", + "systemInfo": "Información del sistema", + "debugMode": "Debug mode", + "help": "Ayuda", + "downloadSystemInfoFile": "Descargar el archivo de información del sistema", + "curlIsNotInstalled": "cURL no está instalado ni habilitado en tu instalación de PHP. Es necesario para que funcione la tarea en segundo plano. Por favor, instálalo y luego actualiza esta página.", + "loading": "Cargando...", + "save": "Guardar", + "saveChanges": "Guardar cambios", + "accept": "Aceptar", + "cancel": "Cancelar", + "continue": "Continuar", + "complete": "Completar", + "add": "Añadir", + "edit": "Editar", + "delete": "Borrar", + "discard": "Ignorar" + }, + "validation": { + "requiredField": "Esta campo es obligatorio.", + "invalidField": "This field is invalid.", + "invalidEmail": "El campo debe ser un correo electrónico válido.", + "shortPassword": "La contraseña debe contener %s caracteres como mínimo.", + "invalidPhone": "Este campo deber ser un número de teléfono válido.", + "maxLength": "Este campo no puede tener más de % caracteres.", + "numeric": "Debe ser un valor numérico.", + "greaterThanZero": "El valor debe ser mayor que 0.", + "nonNegative": "Field cannot have a negative value.", + "numberOfDecimalPlaces": "El campo debe tener 2 decimales.", + "integer": "El campo debe ser un número entero.", + "invalidCountryList": "Debes seleccionar los países de destino.", + "invalidLanguage": "Field is not a valid language.", + "invalidPlatformCountry": "Field is not a valid platform country.", + "invalidUrl": "Field must be a valid URL.", + "invalidFieldValue": "Field must be set to \"%s\".", + "invalidMaxValue": "Maximal allowed value is %s." + }, + "login": { + "apiKey": "Api key", + "apiKeyIncorrect": "Clave de API incorrecta", + "dontHaveAccount": "¿No tienes una cuenta?", + "welcome": "Te damos la bienvenida a Packlink PRO", + "connectYourService": "Conecta tu servicio mediante tu clave. La encontrarás en la
sección \"Configuración\" apartado \"Packlink Pro API Key\"", + "validateApiKey": "Validar API key", + "chooseYourCountry": "Elige tu país para comenzar el registro", + "registerMe": "Registrarme" + }, + "countries": { + "ES": "España", + "DE": "Alemania", + "FR": "Francia", + "IT": "Italia", + "AT": "Austria", + "NL": "Países Bajos", + "BE": "Bélgica", + "PT": "Portugal", + "TR": "Turquía", + "IE": "Irlanda", + "GB": "Reino Unido", + "HU": "Hungría", + "PL": "Polonia", + "CH": "Suiza", + "LU": "Luxemburgo", + "AR": "Argentina", + "US": "Estados Unidos", + "BO": "Bolivia", + "MX": "México", + "CL": "Chile", + "CZ": "Republica checa", + "SE": "Suecia" + }, + "register": { + "logIn": "Iniciar sesión", + "email": "Correo electrónico", + "password": "Contraseña", + "haveAccount": "¿Ya tienes una cuenta?", + "startEnjoying": "¡Empieza a disfrutar de Packlink PRO!", + "registerAccount": "Registro", + "getToKnowYou": "Queremos conocerte mejor para adaptarnos a ti y hacerte una propuesta personalizada.", + "monthlyShipmentVolume": "¿Cuántos envíos sueles hacer al mes?", + "phone": "Teléfono", + "termsAndConditions": "
Acepto los Términos y condiciones y la Política de privacidad de Packlink
", + "marketingEmails": "Autorizo a Packlink Shipping S.L. a enviarme información comercial por correo electrónico", + "submit": "Regístrate", + "chooseYourCountry": "Elige tu país", + "searchCountry": "Busca el país", + "invalidDeliveryVolume": "El campo no contiene un volumen de entrega válido." + }, + "onboardingWelcome": { + "header": "Vamos a configurar tu información básica para poder hacer envíos", + "steps": "Son solo dos pasos, los necesitamos para ofrecerte los servicios de transporte que más se adaptan a tus necesidades.", + "stepOne": "Configurar los detalles del paquete", + "stepTwo": "Configurar la dirección del remitente", + "startSetUp": "Comenzar con la configuración" + }, + "onboardingOverview": { + "header": "Ya casi estamos. Por favor, comprueba que la información es correcta o complétala para continuar.", + "parcelDetails": "Detalle del paquete", + "senderAddress": "Dirección del remitente", + "missingInfo": "Faltan datos", + "parcelData": "Peso %s kg | Alto %s cm | Ancho %s cm | Largo %s cm", + "warehouseData": "%s | %s | %s" + }, + "defaultParcel": { + "title-onboarding": "1. Configurar los detalles del paquete", + "description-onboarding": "Utilizaremos estos datos predeterminados para los artículos que no tengan dimensiones y peso definidos. Podrás editarlos cuando quieras desde la pestaña de configuración.", + "title-config": "Paquete predeterminado", + "description-config": "Utilizaremos estos datos para los artículos que no tengan dimensiones y peso definidos.", + "weight": "Peso", + "height": "Alto", + "width": "Ancho", + "length": "Largo" + }, + "defaultWarehouse": { + "title-onboarding": "2. Configurar la dirección del remitente", + "description-onboarding": "Utilizaremos esta dirección para crear un remitente por defecto que valga para todos los envíos. Podrás editarla cuando quieras desde la pestaña de configuración.", + "title-config": "Dirección del remitente por defecto", + "description-config": "Utilizaremos esta dirección para crear un remitente por defecto que valga para todos los envíos.", + "alias": "Nombre del almacén", + "alias-placeholder": "Almacen principal", + "name": "Nombre de la persona de contacto", + "name-placeholder": "Nombe de la persona", + "surname": "Apellido de la persona de contacto", + "surname-placeholder": "Apellido de la persona", + "company": "Nombre de la empresa", + "company-placeholder": "Empresa S.A.", + "country": "País", + "postal_code": "Ciudad o código postal", + "postal_code-placeholder": "-", + "address": "Dirección", + "address-placeholder": "Dirección", + "phone": "Número de teléfono", + "phone-placeholder": "123 456 7777", + "email": "Correo electrónico", + "email-placeholder": "tucorreo@example.com" + }, + "configuration": { + "menu": "Configuración", + "title": "Configuración", + "orderStatus": "Estado de la orden", + "warehouse": "Almacén por defecto", + "parcel": "Paquete predeterminado", + "help": "Ayuda", + "contactUs": "Contáctanos:", + "systemInfo": "Información del sistema" + }, + "systemInfo": { + "title": "System information file
and debug mode", + "debugMode": "Debug mode", + "download": "Download system information" + }, + "orderStatusMapping": { + "title": "Estado de la orden", + "description": "Con Packlink puedes actualizar el estado de tu pedido de %s con la información de envío.
Puedes editarla en cualquier momento.", + "packlinkProShipmentStatus": "ESTADO DEL ENVÍO de Packlink PRO", + "systemOrderStatus": "ESTADO DEL PEDIDO de %s", + "none": "Ninguno", + "pending": "Pendiente", + "processing": "Procesando", + "readyForShipping": "Listo para enviar", + "inTransit": "En tránsito", + "delivered": "Entregado", + "cancelled": "Cancelado" + }, + "shippingServices": { + "myServices": "Mis servicios de envío", + "noServicesTitle": "Solo queda añadir tus servicios de envío", + "noServicesDescription": "Se mostrarán a tu cliente en el momento del checkout, así podrá elegir qué transportista le llevará su paquete. Lo configuras ahora y no tienes que preocuparte por nada más, puedes cambiarlo siempre que quieras para renovar o ampliar servicios de envío.", + "addService": "Añadir servicio", + "addNewService": "Añadir nuevo servicio", + "addedSuccessTitle": "Servicio de envío añadido correctamente", + "addedSuccessDescription": "Podrás editarlo desde la sección \"Mis servicios\".", + "deletedSuccessTitle": "Servicio de envío eliminado correctamente", + "deletedSuccessDescription": "Puedes añadir nuevos servicios con el botón \"Añadir nuevo servicio\".", + "disableCarriersTitle": "Has creado tu primer servicio de envío. ¿Quieres desabilitar los transportistas anteriores?", + "disableCarriersDescription": "Para ofrecerte un mejor servicio, es importante desabilitar los transportistas que tenías previamente.", + "successfullyDisabledShippingMethods": "Servicios de envío correctamente deseleccionados.", + "failedToDisableShippingMethods": "Error al deshabilitar los servicios de envío.", + "pickShippingService": "Añadir servicios de envío", + "failedGettingServicesTitle": "Tenemos dificultades para obtener servicios de envío", + "failedGettingServicesSubtitle": "¿Quieres volverlo a intentar?", + "retry": "Volver a intentar", + "filterModalTitle": "Filtros", + "applyFilters": "Aplicar", + "type": "Tipo", + "national": "Nacional", + "international": "Internacional", + "deliveryType": "Servicio", + "economic": "Económico", + "express": "Express", + "parcelOrigin": "Origen", + "collection": "Recogida", + "dropoff": "Drop off", + "parcelDestination": "Destino", + "pickup": "Pick up", + "delivery": "Entrega", + "carrierLogo": "Logo del transportista", + "carrier": "Transportista", + "serviceTitle": "Nombre del servicio", + "serviceTitleDescription": "Personaliza tu envío editándolo. Será visible para tus clientes.", + "transitTime": "Tiempo de tránsito", + "origin": "Origen", + "destination": "Destino", + "myPrices": "Mis precios", + "packlinkPrices": "Precios de Packlink", + "configureService": "Configura el servicio", + "showCarrierLogo": "Mostrar logo del transportista a mis clientes.", + "pricePolicy": "Política de precios", + "pricePolicyDescription": "Por defecto serán los precios base de Packlink. Pero puedes configurarlos de manera más precisa a continuación.", + "configurePricePolicy": "Configurar mis precios de envío", + "taxClassTitle": "Elige tu tipo de impuesto guardado", + "tax": "Impuestos", + "serviceCountriesTitle": "Disponibilidad por país de destino", + "serviceCountriesDescription": "Selecciona la disponibilidad para los países que hay permitidos para tu servicio de envío.", + "openCountries": "Ver países", + "allCountriesSelected": "Todos los países seleccionados", + "oneCountrySelected": "Un país seleccionado", + "selectedCountries": "%s países seleccionados", + "firstPricePolicyDescription": "Perfecto, son un par de pasos:

1- Elige si quieres fijarlos por el peso y/o el precio de la compra.

2- Elige una política de precios y configúrala.", + "addFirstPolicy": "Añadir regla de precio", + "addAnotherPolicy": "Añadir otro precio", + "from": "Desde", + "to": "Hasta", + "price": "Precio", + "rangeTypeExplanation": "1- Elige si quieres fijarlos por el peso y/o el precio de la compra.", + "rangeType": "Tipo de rango", + "priceRange": "Rango de precio", + "priceRangeWithData": "Rango de precio: desde %s€ hasta %s€", + "weightRange": "Rango de peso", + "weightRangeWithData": "Rango de peso: desde %s kg hasta %s kg", + "weightAndPriceRange": "Rango de peso y precio", + "weightAndPriceRangeWithData": "Rango de peso y precio: desde %s kg hasta %s kg y desde %s€ hasta %s€", + "singlePricePolicy": "Política de precio %s", + "pricePolicyExplanation": "2- Elige una política de precios y configúrala.", + "packlinkPrice": "Precio Packlink", + "percentagePacklinkPrices": "% de los precio de Packlink", + "percentagePacklinkPricesWithData": "% de los precio de Packlink: %s por %s%", + "fixedPrices": "Precio fijo", + "fixedPricesWithData": "Precio fijo: %s€", + "increaseExplanation": "Incrementar: añade un % al precio, lo pagará el cliente.", + "reduceExplanation": "Reducir: descuenta un % al precio, lo pagarás tú.", + "select": "Elige", + "invalidRange": "Rango no válido", + "increase": "incrementar", + "reduce": "reducir", + "selectAllCountries": "Selecionar todos los países", + "selectCountriesHeader": "Países permitidos para tu servicio
de envío", + "selectCountriesSubheader": "Selecciona la disponibilidad de al menos un país y
añade tantos como necesites", + "usePacklinkRange": "Para el resto de rangos, aplicar precios Packlink", + "discardChangesQuestion": "Algunos cambios no se han guardado.
¿Estás seguro de que quieres volver e ignorar los cambios?", + "atLeastOneCountry": "" + }, + "orderListAndDetails": { + "packlinkOrderDraft": "Borrador del pedido de Packlink", + "printed": "Impreso", + "ready": "Listo", + "printLabel": "Imprimir etiquetas", + "shipmentLabels": "Etiquetas de los envíos", + "printShipmentLabels": "Imprimir Packlink PRO etiquetas de los envíos", + "shipmentDetails": "Detalles del envío", + "disablePopUpBlocker": "Deshabilita el bloqueador de elementos emergentes en esta página para abrir de forma masiva las etiquetas de envío", + "date": "Fecha", + "number": "Número", + "status": "Estado", + "print": "Imprimir", + "carrierTrackingNumbers": "Número de tracking del transportista", + "trackIt": "Seguimiento", + "packlinkReferenceNumber": "Número de referencia Packlink", + "packlinkShippingPrice": "Precio de envío de Packlink", + "viewOnPacklink": "Ver en Packlink PRO", + "createDraft": "Crear borrador", + "draftIsBeingCreated": "El borrador se está creando actualmente en Packlink PRO", + "createOrderDraft": "Crear borrador en Packlink PRO", + "draftCreateFailed": "El intento anterior de crear un borrador ha fallado. Error: %s", + "packlinkShipping": "Packlink Shipping", + "shipmentOrderNotExist": "El pedido con referencia de envío %s no existe en la tienda", + "orderDetailsNotFound": "Detalles del pedido no encontrados", + "orderNotExist": "El pedido con ID %s no existe en la tienda", + "bulkFailCreateFail": "No se puede crear el archivo de etiquetas masivas", + "draftOrderDelayed": "La creación del borrador se ha retrasado", + "completeServicesSetup": "You need to complete the services setup to create order draft.", + "sendWithPacklink": "Send with Packlink" + }, + "checkoutProcess": { + "choseDropOffLocation": "Este servicio de envío admite la entrega a ubicaciones de entrega predefinidas. Elige la ubicación que más te convenga haciendo clic en el botón \"Seleccionar ubicación de entrega\"..", + "selectDropOffLocation": "Seleccionar lugar de entrega", + "changeDropOffLocation": "Cambiar el lugar de entrega", + "packageDeliveredTo": "El paquete será entregado a:", + "dropOffDeliveryAddress": "Dirección de entrega", + "changeAddress": "No hay servicio disponible para la dirección de entrega. Por favor cambia tu direccion.", + "shippingServiceNotFound": "Servicio de envío no encontrado" + }, + "locationPicker": { + "monday": "Lunes", + "tuesday": "Martes", + "wednesday": "Miércoles", + "thursday": "Jueves", + "friday": "Viernes", + "saturday": "Sábado", + "sunday": "Domingo", + "zipCode": "Código postal", + "idCode": "Código ID", + "selectThisLocation": "Selecciona esta ubicación", + "showWorkingHours": "Mostrar horas de apertura", + "hideWorkingHours": "Ocultar horario de apertura", + "showOnMap": "Mostrar en el mapa", + "searchBy": "Buscar por nombre de ubicación, ID o dirección" + }, + "migrationLogMessages": { + "removeControllersHooksFailed": "Error al eliminar los controladores y enlaces antiguos porque: %s", + "deleteObsoleteFilesFailed": "No se pudieron eliminar los archivos antiguos porque: %s", + "oldAPIKeyDetected.": "Se ha detectado una clave de API antigua.", + "successfullyLoggedIn": "Has iniciado correctamente la sesión con la clave de API existente.", + "removingObsoleteFiles.": "Eliminando archivo antiguos.", + "cannotEnqueueUpgradeShopDetailsTask": "No se pueden poner en espera los detalles de la actualización de la tienda porque: %s", + "deletingOldPluginDta.": "Eliminando datos antiguos del plugin.", + "cannotRetrieveOrderReferences": "No se pueden recuperar referencias de pedidos porque: %s", + "createOrderReferenceFailed": "Error al crear la referencia para el pedido %s", + "setLabelsFailed": "Error al generar las etiquetas para el pedido con referencia %s", + "setTrackingInfoFailed": "Error al generar la información de seguimiento para el pedido con la referencia %s", + "orderNotFound": "Pedido con referencia %s no encontrado." + }, + "systemLogMessages": { + "errorCreatingDefaultTask": "Error creating default task runner status configuration.", + "webhookReceived": "Webhook de Packlink recibido", + "couldNotDelete": "No se pudo eliminar la entidad %s con ID %s", + "entityCannotBeSaved": "La entidad %s con ID %s no se puede guardar.", + "entityCannotBeUpdated": "La entidad %s con ID %s no se puede actualizar.", + "fieldIsNotIndexed": "¡El campo %s no está indexado!", + "unknownOrNotIndexed": "Desconocido o no indexado OrderBy column %s", + "dirNotCreated": "El directorio \"%s\" no fue creado", + "failedCreatingBackup": "Error al crear el servicio flexible", + "backupServiceNotFound": "Servicio flexible no encontrado" + }, + "autoTest": { + "dbNotAccessible.": "DNo se puede acceder a la base de datos.", + "taskCouldNotBeStarted.": "No se ha podido iniciar la tarea.", + "moduleAutoTest": "Prueba automática del módulo PacklinkPRO", + "useThisPageTestConfiguration": "Utiliza esta página para probar la configuración del sistema y los servicios del módulo PacklinkPRO.", + "start": "Inicio", + "downloadTestLog": "Descargar el registro de pruebas", + "openModule": "Abrir el módulo PacklinkPRO", + "autotestPassedSuccessfully": "Prueba automática superada con éxito.", + "testNotSuccessful": "La prueba no se ha completado correctamente." + } +} \ No newline at end of file diff --git a/src/BusinessLogic/Resources/lang/fr.json b/src/BusinessLogic/Resources/lang/fr.json new file mode 100644 index 00000000..123550f2 --- /dev/null +++ b/src/BusinessLogic/Resources/lang/fr.json @@ -0,0 +1,355 @@ +{ + "general": { + "packlinkPro": "Packlink PRO Shipping", + "saveUp": "Économisez jusqu'à 70% sur vos coûts de livraison. Sans frais fixes, sans volume minimum requis. Gérez tous vos envois depuis une seule plateforme.", + "packlinkShipping": "Packlink Shipping S.L.", + "noContract": "Sans engagement, accédez immédiatement à plus de 300 services de transport sur une seule et unique plateforme. Quelque soit le nombre de points de ventes que vous avez, vous pourrez tout gérer depuis Packlink PRO.", + "developedAndManaged": "Développé et géré par Packlink", + "generalConditions": "Conditions générales", + "basicSettings": "Paramètres", + "contactUs": "Contactez-nous", + "systemInfo": "Informations du système", + "debugMode": "Debug mode", + "help": "Aide", + "downloadSystemInfoFile": "Téléchargez le document d'information système", + "curlIsNotInstalled": "cURL non installé ou non activé dans votre installation PHP. Cette démarche est nécessaire pour que la tâche de fond fonctionne. Veuillez l’installer et rafraîchir cette page.", + "loading": "Chargement en cours...", + "save": "Enregistrer", + "saveChanges": "Enregistrer les modifications", + "accept": "Accepter", + "cancel": "Annuler", + "continue": "Continuer", + "complete": "Compléter", + "add": "Ajouter", + "edit": "Modifier", + "delete": "Supprimer", + "discard": "Ignorer" + }, + "validation": { + "requiredField": "Ce champs est obligatoire.", + "invalidField": "This field is invalid.", + "invalidEmail": "Le champs doit être une adresse email valide", + "shortPassword": "Le mot de passe doit comporter au moins %s lettres.", + "invalidPhone": "Ce champs doit contenir un numéro de téléphone valide.", + "maxLength": "Ce champ ne peut contenir plus de %s caractères.", + "numeric": "Cette valeur doit être numérique.", + "greaterThanZero": "La valeur doit être spérieure à 0.", + "nonNegative": "Le champ ne peut contenir de valeur négative.", + "numberOfDecimalPlaces": "Le champs doit contenir deux décimales.", + "integer": "Le champs doit être un nombre entier.", + "invalidCountryList": "Veuillez sélectionner les pays de destination.", + "invalidLanguage": "Le champ ne contient pas de langue valide.", + "invalidPlatformCountry": "Le champ ne contient pas de pays valide.", + "invalidUrl": "Le champ doit contenir une URL valide.", + "invalidFieldValue": "Le champ doit être défini sur « %s ».", + "invalidMaxValue": "La valeur maximale autorisée est %s." + }, + "login": { + "apiKey": "Clé API", + "apiKeyIncorrect": "Clé API incorrecte", + "dontHaveAccount": "Vous n’avez pas de compte ?", + "welcome": "Bienvenue sur Packlink PRO", + "connectYourService": "Connectez votre service en utilisant votre clé. Vous la trouverez dans la section
« Clé API Packlink Pro » dans « Paramètres ».", + "validateApiKey": "Valider clé API", + "chooseYourCountry": "Sélectionnez votre pays pour commencer votre inscription", + "registerMe": "S’inscrire" + }, + "countries": { + "ES": "Espagne", + "DE": "Allemagne", + "FR": "France", + "IT": "Italie", + "AT": "Autriche", + "NL": "Pays-Bas", + "BE": "Belgique", + "PT": "Portugal", + "TR": "Turquie", + "IE": "Irlande", + "GB": "Royaume-Uni", + "HU": "Hongrie", + "PL": "Pologne", + "CH": "Suisse", + "LU": "Luxembourg", + "AR": "Argentine", + "US": "États Unis", + "BO": "Bolivie", + "MX": "Mexique", + "CL": "Chili", + "CZ": "République Tchèque", + "SE": "Suède" + }, + "register": { + "logIn": "Se connecter", + "email": "Adresse électronique", + "password": "Mot de passe", + "haveAccount": "Vous avez déjà un compte ?", + "startEnjoying": "Commencez à utiliser Packlink PRO !", + "registerAccount": "Enregistrez-vous", + "getToKnowYou": "Nous souhaitons mieux vous connaître afin de nous adapter à vous et de vous faire une offre sur mesure", + "monthlyShipmentVolume": "Combien d’envois faites-vous généralement par mois?", + "phone": "Numéro de téléphone", + "termsAndConditions": "
IJ’accepte les Conditions de service et la Politique de confidentialité de Packlink
", + "marketingEmails": "J’autorise Packlink Shipping S.L. à m’envoyer des communications commerciales par courriel", + "submit": "S'inscrire", + "chooseYourCountry": "Sélectionnez votre pays", + "searchCountry": "Rechercher un pays", + "invalidDeliveryVolume": "Le champ ne contient pas de volume de livraison valide." + }, + "onboardingWelcome": { + "header": "Configurez vos informations de base afin d’effectuer des envois", + "steps": "Il ne vous reste que deux étapes pour savoir quel transporteur répond le mieux à vos besoins", + "stepOne": "Configurez les informations sur le colis", + "stepTwo": "Configurez l’adresse de l’émetteur", + "startSetUp": "Démarrez la configuration" + }, + "onboardingOverview": { + "header": "Vous y êtes presque ! Veuillez vérifier que les informations saisies sont correctes ou complétez-les pour continuer.", + "parcelDetails": "Informations sur le colis", + "senderAddress": "Adresse de l’émetteur", + "missingInfo": "Informations manquantes", + "parcelData": "Poids %s kg | Hauteur %s cm | Largeur %s cm | Longueur %s cm", + "warehouseData": "%s | %s | %s" + }, + "defaultParcel": { + "title-onboarding": "1. Configurez les informations sur le colis", + "description-onboarding": "Nous utiliserons ces données prédéfinies pour les articles dont les dimensions et le poids n’ont pas été définis. Vous pouvez les modifier à tout moment via l’onglet Paramètres.", + "title-config": "Colis prédéfini", + "description-config": "Nous utiliserons ces données pour les articles dont les dimensions et le poids n’ont pas été définis.", + "weight": "Poids", + "height": "Hauteur", + "width": "Largeur", + "length": "Longueur" + }, + "defaultWarehouse": { + "title-onboarding": "2. Configurer l’adresse de l’émetteur", + "description-onboarding": "Nous utiliserons cette adresse pour créer un émetteur prédéfini pour toutes les expéditions. Vous pourrez la modifier à tout moment via l’onglet Paramètres.", + "title-config": "Adresse de l’émetteur prédéfinie", + "description-config": "Nous utiliserons cette adresse pour créer un émetteur prédéfini pour toutes les expéditions. Vous pourrez la modifier à tout moment.", + "alias": "Nom de l’émetteur", + "alias-placeholder": "Magasin principal", + "name": "Prénom de la personne à contacter", + "name-placeholder": "Prénom", + "surname": "Nom de la personne à contacter", + "surname-placeholder": "Nom", + "company": "Nom de l'entreprise", + "company-placeholder": "Société", + "country": "Pays", + "postal_code": "Ville ou code postal", + "postal_code-placeholder": "-", + "address": "Adresse", + "address-placeholder": "Adresse", + "phone": "Numéro de téléphone", + "phone-placeholder": "123 456 7777", + "email": "Email", + "email-placeholder": "votreemail@exemple.com" + }, + "configuration": { + "menu": "Paramètres", + "title": "Paramètres", + "orderStatus": "Statut de la commande", + "warehouse": "Adresse de l’émetteur prédéfinie", + "parcel": "Colis par défaut", + "help": "Aide", + "contactUs": "Contactez-nous:", + "systemInfo": "Informations du système" + }, + "systemInfo": { + "title": "Fichier d’informations du système
et mode débogage", + "debugMode": "Debug mode", + "download": "Téléchargez le document d'information système" + }, + "orderStatusMapping": { + "title": "Statut de la commande", + "description": "Avec Packlink, vous pouvez mettre à jour le statut de votre commande %s avec les informations d’expédition.
Vous pourrez les modifier à tout moment.", + "packlinkProShipmentStatus": "STATUT DE LIVRAISON Packlink PRO", + "systemOrderStatus": "STATUT DE LA COMMANDE %s", + "none": "Aucun", + "pending": "En attente", + "processing": "En cours de traitement", + "readyForShipping": "Prêt pour la livraison", + "inTransit": "En transit", + "delivered": "Délivré", + "cancelled": "Annulé" + }, + "shippingServices": { + "myServices": "Mes services d’expédition", + "noServicesTitle": "Vous devez simplement ajouter vos services d’expédition", + "noServicesDescription": "Votre client pourra les voir au moment du paiement et choisir le transporteur qui livrera son colis. Configurez-les maintenant pour n’avoir à vous soucier de rien d’autre. Vous pourrez à tout moment modifier, mettre à jour ou améliorer vos services d’expédition.", + "addService": "Ajouter un service", + "addNewService": "Ajouter un nouveau service", + "addedSuccessTitle": "Service d’expédition ajouté avec succès", + "addedSuccessDescription": "Vous pouvez modifier le service dans la section « Mes services ».", + "deletedSuccessTitle": "Service d’expédition supprimé avec succès", + "deletedSuccessDescription": "Vous pouvez ajouter de nouveaux services avec le bouton « Ajouter un nouveau service ».", + "disableCarriersTitle": "Vous avez créé votre premier service d’expédition. Souhaitez-vous désactiver les anciens transporteurs ?", + "disableCarriersDescription": "Afin de vous offrir un meilleur service, il est important de désactiver le transporteur que vous utilisiez auparavant.", + "successfullyDisabledShippingMethods": "Service d'envoi désactivé avec succès.", + "failedToDisableShippingMethods": "Échec de la désactivation du service d'envoi.", + "pickShippingService": "Ajouter des services de livraison", + "failedGettingServicesTitle": "Nous rencontrons des problèmes dans l’importation des services de livraison", + "failedGettingServicesSubtitle": "Souhaitez-vous réessayer ?", + "retry": "Réessayer", + "filterModalTitle": "Filtres", + "applyFilters": "Appliquer", + "type": "Type", + "national": "National", + "international": "International", + "deliveryType": "Livraison", + "economic": "Économique", + "express": "Express", + "parcelOrigin": "Origine du colis", + "collection": "Collecte à domicile", + "dropoff": "Dépôt en point relais", + "parcelDestination": "Destination du colis", + "pickup": "Point de retrait", + "delivery": "Livraison à domicile", + "carrierLogo": "Logo du transporteur", + "carrier": "Transporteur", + "serviceTitle": "Titre du service", + "serviceTitleDescription": "Personnalisez votre envoi en le modifiant. Vos clients pourront le voir.", + "transitTime": "Temps de transit", + "origin": "Origine", + "destination": "Destination", + "myPrices": "Mes prix", + "packlinkPrices": "Prix Packlink", + "configureService": "Configurer le service", + "showCarrierLogo": "Montrer le logo du transporteur à mes clients", + "pricePolicy": "Politique tarifaire", + "pricePolicyDescription": "Les tarifs de base de Packlink seront configurés par défaut, mais vous pourrez les configurer plus en détail ci-dessous.", + "configurePricePolicy": "Configurer mes frais d’expédition", + "taxClassTitle": "Sélectionnez votre taux fiscal enregistré", + "tax": "Taxe", + "serviceCountriesTitle": "Disponibilité par pays de destination", + "serviceCountriesDescription": "Sélectionnez la disponibilité des pays pris en charge par votre service d’expédition.", + "openCountries": "Voir pays", + "allCountriesSelected": "Tous les pays sont sélectionnés", + "oneCountrySelected": "Un pays est sélectionné", + "selectedCountries": "%s pays sont sélectionnés", + "firstPricePolicyDescription": "Parfait, il ne vous reste que quelques étapes :

1. Choisissez si vous souhaitez les configurer par poids et/ou par prix d’achat.

2. Choisissez une politique tarifaire et configurez-la.", + "addFirstPolicy": "Ajouter une règle de prix", + "addAnotherPolicy": "Ajouter une autre règle", + "from": "De", + "to": "À", + "price": "Prix", + "rangeTypeExplanation": "1. Choisissez si vous souhaitez les configurer par poids et/ou par prix d’achat.", + "rangeType": "Type de fourchette", + "priceRange": "Fourchette de prix", + "priceRangeWithData": "Fourchette de prix : de %s € à %s €", + "weightRange": "Fourchette de poids", + "weightRangeWithData": "Fourchette de poids : de %s kg à %s kg", + "weightAndPriceRange": "Fourchette de poids et de prix", + "weightAndPriceRangeWithData": "Fourchette de poids et de prix : de %s kg à %s kg et de %s € à %s €", + "singlePricePolicy": "Politique tarifaire %s", + "pricePolicyExplanation": "2. Choisissez une politique tarifaire et configurez-la.", + "packlinkPrice": "Prix Packlink", + "percentagePacklinkPrices": "% des prix Packlink", + "percentagePacklinkPricesWithData": "% des prix Packlink : %s de %s%", + "fixedPrices": "Prix fixe", + "fixedPricesWithData": "Prix fixe : %s €", + "increaseExplanation": "Augmentation : ajoutez % au prix, c’est le client qui payera.", + "reduceExplanation": "Réduction : déduisez % du prix, c’est vous qui payerez.", + "select": "Sélectionner", + "invalidRange": "Fourchette invalide", + "increase": "Augmenter", + "reduce": "Réduire", + "selectAllCountries": "Tous les pays sélectionnés", + "selectCountriesHeader": "Pays pris en charge par votre
service d’expédition", + "selectCountriesSubheader": "Sélectionnez la disponibilité d’au moins un pays et ajoutez-en
autant que nécessaire", + "usePacklinkRange": "Pour toutes les autres fourchettes, appliquez les prix Packlink", + "discardChangesQuestion": "Certaines modifications n’ont pas été enregistrées.
Êtes-vous sûr de vouloir revenir en arrière et de les ignorer ?", + "atLeastOneCountry": "" + }, + "orderListAndDetails": { + "packlinkOrderDraft": "Brouillon de commande Packlink", + "printed": "Imprimé", + "ready": "Prêt", + "printLabel": "Imprimer étiquette", + "shipmentLabels": "Étiquettes de livraison", + "printShipmentLabels": "Imprimer les étiquettes de livraison Packlink PRO", + "shipmentDetails": "Détails de l'envoi", + "disablePopUpBlocker": "Veuillez désactiver l'outil de blocage des fenêtres pop-up sur cette page afin d'ouvrir massivement les étiquettes d'envoi.", + "date": "Date", + "number": "Numéro", + "status": "Statut", + "print": "Imprimer", + "carrierTrackingNumbers": "Numéro de tracking du transporteur", + "trackIt": "Suivez le!", + "packlinkReferenceNumber": "Numéro de référence Packlink", + "packlinkShippingPrice": "Prix de livraison Packlink", + "viewOnPacklink": "Voir sur Packlink PRO", + "createDraft": "Créer un brouillon", + "draftIsBeingCreated": "Le brouillon est en cours de création sur Packlink PRO", + "createOrderDraft": "Créer un brouillon de commande sur Packlink PRO", + "draftCreateFailed": "La tentative précédente de création d'un brouillon a échoué. Erreur: %s", + "packlinkShipping": "Packlink Shipping", + "shipmentOrderNotExist": "La commande de livraison sous le référence %s n'existe pas sur le shop.", + "orderDetailsNotFound": "Détails de commande introuvables", + "orderNotExist": "La commande sous l'ID %s n'existe pas sur le shop.", + "bulkFailCreateFail": "Création d'un dossier d'étiquettes en masse impossible.", + "draftOrderDelayed": "La création du brouillon a été retardée", + "completeServicesSetup": "You need to complete the services setup to create order draft.", + "sendWithPacklink": "Send with Packlink" + }, + "checkoutProcess": { + "choseDropOffLocation": "Ce service de transport soutient la livraison vers des lieux de dépôts prédéfinis. Veuillez choisir la localisation qui vous convient le mieux en cliquant sur le bouton \"Sélectionner le lieu de dépôt\"", + "selectDropOffLocation": "Sélectionner le lieu de dépôt.", + "changeDropOffLocation": "Changer le lieu de depôt.", + "packageDeliveredTo": "Le colis sera délivré à:", + "dropOffDeliveryAddress": "Adresse de livraison.", + "changeAddress": "Il n'y a pas de lieux de livraison disponibles pour votre adresse de livraison. Veuillez changer votre adresse.", + "shippingServiceNotFound": "Service de livraison introuvable." + }, + "locationPicker": { + "monday": "Lundi", + "tuesday": "Mardi", + "wednesday": "Mercredi", + "thursday": "Jeudi", + "friday": "Vendredi", + "saturday": "Samedi", + "sunday": "Dimanche", + "zipCode": "Code postal", + "idCode": "Code ID", + "selectThisLocation": "Sélectionner ce lieu", + "showWorkingHours": "Montrer les heures de travail", + "hideWorkingHours": "Cacher les heures d'ouverture", + "showOnMap": "Montrer sur la carte", + "searchBy": "Chercher par lieu, nom, id ou adresse" + }, + "migrationLogMessages": { + "removeControllersHooksFailed": "Echec du retrait des anciens régulateurs et crochets parce que. %s", + "deleteObsoleteFilesFailed": "La suppression des dossiers obsolètes n'a pas pu être effectuée car: %s", + "oldAPIKeyDetected.": "Ancienne clé API détectée.", + "successfullyLoggedIn": "Connecté avec succès avec la clé API existante.", + "removingObsoleteFiles.": "Retrait des dossiers osolètes.", + "cannotEnqueueUpgradeShopDetailsTask": "Mise en attente de la tâche \"Mise à jour des détails du Shop\" impossible car: %s", + "deletingOldPluginDta.": "Suppression des informations relatives à l'ancien plugin.", + "cannotRetrieveOrderReferences": "Récupération des références de commande impossible car: %s", + "createOrderReferenceFailed": "Échec de création de référence pour la commande %s", + "setLabelsFailed": "Échec de la mise en place d'étiquettes pour la commande sous la référence %s", + "setTrackingInfoFailed": "Échec de la mise en place des informations de suivi pour la commande sous la référence %s", + "orderNotFound": "La commande sous la référence %s introuvable." + }, + "systemLogMessages": { + "errorCreatingDefaultTask": "Erreur lors de la création de la configuration du gestionnaire de tâches par défaut.", + "webhookReceived": "Webhook de Packlink reçu", + "couldNotDelete": "L'entité %s dont l'ID est %s n'a pas pu être supprimée.", + "entityCannotBeSaved": "L'entité %s dont l'ID est %s n'a pas pu être enregistrée.", + "entityCannotBeUpdated": "L'entité %s dont l'ID est %s n'a pas pu être mise à jour.", + "fieldIsNotIndexed": "Le champs %s n'est pas indexé!", + "unknownOrNotIndexed": "Inconnu ou non-indexé OrderBy column %s", + "dirNotCreated": "Annuaire \"%s\" n'a pas été créé", + "failedCreatingBackup": "Impossible de créer le service de flexibilité.", + "backupServiceNotFound": "Service de flexibilité introuvable" + }, + "autoTest": { + "dbNotAccessible.": "Base de données inaccessible.", + "taskCouldNotBeStarted.": "La tâche n’a pu être lancée.", + "moduleAutoTest": "Test automatique du module PacklinkPRO", + "useThisPageTestConfiguration": "Utilisez cette page pour tester la configuration du système et les services du module PacklinkPRO.", + "start": "Démarrer", + "downloadTestLog": "Télécharger le registre de tests", + "openModule": "Ouvrir le module PacklinkPRO", + "autotestPassedSuccessfully": "Test automatique réussi avec succès !", + "testNotSuccessful": "Le test a échoué." + } +} \ No newline at end of file diff --git a/src/BusinessLogic/Resources/lang/fromCSV.php b/src/BusinessLogic/Resources/lang/fromCSV.php new file mode 100644 index 00000000..5968a5d9 --- /dev/null +++ b/src/BusinessLogic/Resources/lang/fromCSV.php @@ -0,0 +1,47 @@ +\"Chiave API Packlink Pro\" delle \"Impostazioni\".", + "validateApiKey": "Convalida la chiave API", + "chooseYourCountry": "Per iniziare la registrazione, scegli il tuo paese", + "registerMe": "Iscriviti" + }, + "countries": { + "ES": "Spagna", + "DE": "Germania", + "FR": "Francia", + "IT": "Italia", + "AT": "Austria", + "NL": "Paesi Bassi", + "BE": "Belgio", + "PT": "Portogallo", + "TR": "Turchia", + "IE": "Irlanda", + "GB": "Regno Unito", + "HU": "Ungheria", + "PL": "Polonia", + "CH": "Svizzera", + "LU": "Lussemburgo", + "AR": "Argentina", + "US": "Stati Uniti", + "BO": "Bolivia", + "MX": "Messico", + "CL": "Chile", + "CZ": "Repubblica Ceca", + "SE": "Svezia" + }, + "register": { + "logIn": "Accedi", + "email": "Email", + "password": "Password", + "haveAccount": "Hai già un account?", + "startEnjoying": "Inizia a usufruire di Packlink PRO!", + "registerAccount": "Registrati", + "getToKnowYou": "Vogliamo conoscerti meglio per adattarci a te e preparare una proposta personalizzata per te", + "monthlyShipmentVolume": "Quante spedizioni fai solitamente al mese?", + "phone": "Numero di telefono", + "termsAndConditions": "
Accetto i Termini del servizio e l’Informativa sulla privacy di Packlink
", + "marketingEmails": "Autorizzo Packlink Shipping S.L. a inviarmi comunicazioni commerciali via email", + "submit": "Registrati", + "chooseYourCountry": "Scegli il paese", + "searchCountry": "Cerca paese", + "invalidDeliveryVolume": "Il campo non contiene un volume di consegna valido." + }, + "onboardingWelcome": { + "header": "È necessario impostare alcuni dati di base così che potrai effettuare spedizioni", + "steps": "Si tratta di due semplici passaggi che dobbiamo completare per offrirti la compagnia di trasporto che più si adatta alle tue esigenze", + "stepOne": "Imposta i dettagli del pacco", + "stepTwo": "Imposta l’indirizzo del mittente", + "startSetUp": "Avvia la configurazione" + }, + "onboardingOverview": { + "header": "Ci siamo quasi! Per continuare, verifica che i dati inseriti siano corretti o completali.", + "parcelDetails": "Dettagli del pacco", + "senderAddress": "Indirizzo del mittente", + "missingInfo": "Dati mancanti", + "parcelData": "Peso %s kg | Altezza %s cm | Largezza %s cm | Lunghezza %s cm", + "warehouseData": "%s | %s | %s" + }, + "defaultParcel": { + "title-onboarding": "1. Imposta i dettagli del pacco", + "description-onboarding": "Useremo questi dati predefiniti per gli articoli per cui non vengono specificate le dimensioni e il peso. Puoi modificarli quando vuoi dalla scheda delle impostazioni.", + "title-config": "Pacco predefinito", + "description-config": "Useremo questi dati per articoli privi di dimensioni e peso definiti.", + "weight": "Peso", + "height": "Altezza", + "width": "Largezza", + "length": "Lunghezza" + }, + "defaultWarehouse": { + "title-onboarding": "2. Imposta l’indirizzo del mittente", + "description-onboarding": "Useremo questo indirizzo per creare un mittente predefinito per tutte le spedizioni. Potrai modificarlo ogni volta che vuoi dalla scheda delle impostazioni.", + "title-config": "Indirizzo del mittente predefinito", + "description-config": "Useremo questo indirizzo per creare un mittente predefinito per tutte le spedizioni. Potrai modificarlo ogni volta che vuoi.", + "alias": "Nome del mittente", + "alias-placeholder": "Magazzino principale", + "name": "Nome della persona di contatto", + "name-placeholder": "Nome", + "surname": "Cognome della persona di contatto", + "surname-placeholder": "cognome", + "company": "Nome dell'azienda", + "company-placeholder": "Azienda", + "country": "Paese", + "postal_code": "Città o codice postale", + "postal_code-placeholder": "-", + "address": "Indirizzo", + "address-placeholder": "Indirizzo", + "phone": "Numero di telefono", + "phone-placeholder": "123 456 7777", + "email": "Email", + "email-placeholder": "latuaemail@esempio.com" + }, + "configuration": { + "menu": "Impostazioni", + "title": "Impostazioni", + "orderStatus": "Stato dell’ordine", + "warehouse": "Indirizzo del mittente predefinito", + "parcel": "Pacco predefinito", + "help": "Aiuto", + "contactUs": "Contattaci:", + "systemInfo": "Informazioni del sistema" + }, + "systemInfo": { + "title": "File di informazioni del sistema
e modalità debug", + "debugMode": "Debug mode", + "download": "Scarica il file di informazioni del sistema" + }, + "orderStatusMapping": { + "title": "Stato dell’ordine", + "description": "Con Packlink, puoi aggiornare lo stato dell’ordine %s con i dati della spedizione.
Puoi modificarlo ogni volta che vuoi.", + "packlinkProShipmentStatus": "STATO DELLA SPEDIZIONE di Packlink PRO", + "systemOrderStatus": "STATO DELLA SPEDIZIONE di %s", + "none": "Nessuno", + "pending": "In attesa", + "processing": "Processando", + "readyForShipping": "Pronto per la spedizione", + "inTransit": "In transito", + "delivered": "Consegnato", + "cancelled": "Annullato" + }, + "shippingServices": { + "myServices": "I miei servizi di spedizione", + "noServicesTitle": "Dovrai solo aggiungere i tuoi servizi di spedizione", + "noServicesDescription": "Questi saranno mostrati al cliente al momento del pagamento, per consentirgli di scegliere la compagnia di trasporto che effettuerà la consegna del pacco. Configurali adesso e non dovrai più preoccuparti di nulla. Puoi modificarli ogni volta che vuoi, per aggiornare o rinnovare i servizi di spedizione.", + "addService": "Aggiungi servizio", + "addNewService": "Aggiungi nuovo servizio", + "addedSuccessTitle": "Il servizio di spedizione è stato aggiunto", + "addedSuccessDescription": "Puoi modificare il servizio dalla sezione \"I miei servizi\".", + "deletedSuccessTitle": "Il servizio di spedizione è stato eliminato", + "deletedSuccessDescription": "Puoi aggiungere nuovi servizi tramite il pulsante \"Aggiungi nuovo servizio\".", + "disableCarriersTitle": "Hai creato il tuo primo servizio di spedizione. Vuoi disattivare le compagnie di trasporto precedenti?", + "disableCarriersDescription": "Per garantirti un servizio migliore, è importante disattivare le compagnie di trasporto utilizzati in precedenza.", + "successfullyDisabledShippingMethods": "Servizi di spedizione disattivati correttamente.", + "failedToDisableShippingMethods": "Impossibile disattivare i servizi di spedizione.", + "pickShippingService": "Aggiungi servizi di consegna", + "failedGettingServicesTitle": "Si è verificato un problema durante l’acquisizione dei servizi di spedizione", + "failedGettingServicesSubtitle": "Vuoi riprovare?", + "retry": "Riprova", + "filterModalTitle": "Filtri", + "applyFilters": "Applica", + "type": "Tipo", + "national": "Nazionale", + "international": "Internazionale", + "deliveryType": "Servizio", + "economic": "Economico", + "express": "Espresso", + "parcelOrigin": "Origine del pacco", + "collection": "Ritiro a domicilio", + "dropoff": "Ritiro in punto corriere", + "parcelDestination": "Destinazione pacco", + "pickup": "Consegna in punto corriere", + "delivery": "Consegna al domicilio", + "carrierLogo": "Logo del corriere", + "carrier": "Corriere", + "serviceTitle": "Titolo del servizio", + "serviceTitleDescription": "Modifica le tue spedizioni per personalizzarle. Queste informazioni saranno visualizzate dai tuoi clienti.", + "transitTime": "Tempi di transito", + "origin": "Origine", + "destination": "Destinazione", + "myPrices": "I miei prezzi", + "packlinkPrices": "Prezzi di Packlink", + "configureService": "Configura il servizio", + "showCarrierLogo": "Mostra logo del corriere ai miei clienti", + "pricePolicy": "Politica dei prezzi", + "pricePolicyDescription": "Per impostazione predefinita, saranno i prezzi di base di Packlink, ma puoi configurarli più dettagliatamente qui di seguito.", + "configurePricePolicy": "Configura i prezzi di spedizione", + "taxClassTitle": "Scegli la tua aliquota fiscale salvata", + "tax": "Tassa", + "serviceCountriesTitle": "Disponibilità per paese di destinazione", + "serviceCountriesDescription": "Seleziona la disponibilità per i paesi supportati per i tuoi servizi di spedizione.", + "openCountries": "Vedi i paesi", + "allCountriesSelected": "Tutti i paesi selezionati", + "oneCountrySelected": "Un paese selezionato", + "selectedCountries": "%s paesi selezionati", + "firstPricePolicyDescription": "Perfetto, mancano solo due passaggi:

1. Scegli se vuoi importarli per peso e/o prezzo di acquisto.

2. Scegli una politica dei prezzi e configurala.", + "addFirstPolicy": "Aggiungi regola di prezzo", + "addAnotherPolicy": "Aggiungi un’altra regola", + "from": "Da", + "to": "A", + "price": "Prezzo", + "rangeTypeExplanation": "1. Scegli se vuoi importarli per peso e/o prezzo di acquisto.", + "rangeType": "Tipo di range", + "priceRange": "Range di prezzo", + "priceRangeWithData": "Range di prezzo: da %s€ a %s€", + "weightRange": "Range di peso", + "weightRangeWithData": "Range di peso: da %s kg a %s kg", + "weightAndPriceRange": "Range di prezzo e di peso", + "weightAndPriceRangeWithData": "Range di prezzo e di peso: da %s kg a %s kg e da %s€ a %s€", + "singlePricePolicy": "Politica dei prezzi %s", + "pricePolicyExplanation": "2. Scegli una politica dei prezzi e configurala.", + "packlinkPrice": "Prezzo di Packlink", + "percentagePacklinkPrices": "% dei prezzi di Packlink", + "percentagePacklinkPricesWithData": "% dei prezzi di Packlink: %s del %s%", + "fixedPrices": "Prezzo fisso", + "fixedPricesWithData": "Prezzo fisso: %s€", + "increaseExplanation": "Incremento sul prezzo: aggiungi una % al prezzo, che sarà pagata dal cliente.", + "reduceExplanation": "Riduzione sul prezzo: deduci una % dal prezzo, che sarà pagata da te.", + "select": "Sélectionner", + "invalidRange": "Range non valido", + "increase": "Aumenta", + "reduce": "Diminuisci", + "selectAllCountries": "Tutti i paesi selezionati", + "selectCountriesHeader": "Paesi supportati per il tuo
servizio di spedizione", + "selectCountriesSubheader": "Seleziona la disponibilità di almeno un paese e aggiungi
tutti quelli richiesti", + "usePacklinkRange": "Per tutti gli altri range, applica i prezzi di Packlink", + "discardChangesQuestion": "Alcune modifiche non sono state salvate.
Sei sicuro di voler tornare indietro e annullarle?", + "atLeastOneCountry": "" + }, + "orderListAndDetails": { + "packlinkOrderDraft": "Bozza dell'ordine Packlink", + "printed": "Stampato", + "ready": "Pronto per la spedizione", + "printLabel": "Stampa etichette", + "shipmentLabels": "Etichette di spedizione", + "printShipmentLabels": "Stampa etichette di spedizione Packlink PRO", + "shipmentDetails": "Dettagli della spedizione", + "disablePopUpBlocker": "Disabilita il blocco pop-up su questa pagina per poter aprire le etichette di spedizione insieme", + "date": "Data", + "number": "Numero", + "status": "Stato", + "print": "Stampa", + "carrierTrackingNumbers": "Numero di tracking del corriere", + "trackIt": "Traccialo!", + "packlinkReferenceNumber": "Numero di ordine Packlink", + "packlinkShippingPrice": "PPrezzo di spedizione Packlink", + "viewOnPacklink": "Vedi su Packlink PRO", + "createDraft": "Crea bozza", + "draftIsBeingCreated": "La bozza è attualmente in fase di creazione su Packlink PRO", + "createOrderDraft": "Crea una bozza di ordine su Packlink PRO", + "draftCreateFailed": "Il precedente tentativo di creare una bozza è fallito. Errore: %s", + "packlinkShipping": "Packlink Shipping", + "shipmentOrderNotExist": "L'ordine con il numero di spedizione %s non esiste nel negozio", + "orderDetailsNotFound": "Dettagli dell'ordine non trovati", + "orderNotExist": "L'ordine con ID %s non esiste nel negozio", + "bulkFailCreateFail": "Impossibile creare il file di etichette in blocco", + "draftOrderDelayed": "La creazione della bozza è stata ritardata", + "completeServicesSetup": "You need to complete the services setup to create order draft.", + "sendWithPacklink": "Send with Packlink" + }, + "checkoutProcess": { + "choseDropOffLocation": "Questo servizio di spedizione supporta la consegna a punti corriere predefiniti. Scegli la località più adatta a te, facendo clic sul pulsante \"Seleziona punto corriere\".", + "selectDropOffLocation": "Seleziona la località del punto corriere", + "changeDropOffLocation": "Cambia località del punto corriere", + "packageDeliveredTo": "La spedizione verrà consegnata a:", + "dropOffDeliveryAddress": "Indirizzo di consegna del punto corriere", + "changeAddress": "Non ci sono località di consegna disponibili per il tuo indirizzo di consegna. Per favore cambia l'indirizzo.", + "shippingServiceNotFound": "Servizio di spedizione non trovato" + }, + "locationPicker": { + "monday": "Lunedì", + "tuesday": "Martedì", + "wednesday": "Mercoledì", + "thursday": "Giovedì", + "friday": "Venerdì", + "saturday": "Sabato", + "sunday": "Domenica", + "zipCode": "Codice postale", + "idCode": "Codice ID", + "selectThisLocation": "Seleziona la località", + "showWorkingHours": "Mostra orari di apertura", + "hideWorkingHours": "Nascondi orari di apertura", + "showOnMap": "Mostra sulla mappa", + "searchBy": "Cerca nome della località per nome, id o indirizzo" + }, + "migrationLogMessages": { + "removeControllersHooksFailed": "Impossibile rimuovere vecchi controller e hook perché: %s", + "deleteObsoleteFilesFailed": "Impossibile eliminare i file obsoleti perché: %s", + "oldAPIKeyDetected.": "Rilevata vecchia API Key.", + "successfullyLoggedIn": "Accesso eseguito correttamente con la API Key esistente.", + "removingObsoleteFiles.": "Rimozione di file obsoleti.", + "cannotEnqueueUpgradeShopDetailsTask": "Impossibile richiamare UpgradeShopDetailsTask perché: %s", + "deletingOldPluginDta.": "Eliminando i vecchi dati del plug-in", + "cannotRetrieveOrderReferences": "Impossibile recuperare i riferimenti degli ordini perché: %s", + "createOrderReferenceFailed": "Impossibile creare il riferimento per l'ordine %s", + "setLabelsFailed": "Impossibile impostare le etichette per l'ordine con riferimento %s", + "setTrackingInfoFailed": "Impossibile impostare le informazioni di tracciamento per l'ordine con riferimento %s", + "orderNotFound": "Ordine con riferimento %s non trovato" + }, + "systemLogMessages": { + "errorCreatingDefaultTask": "Error creating default task runner status configuration.", + "webhookReceived": "Webhook di Packlink ricevuto", + "couldNotDelete": "Impossibile eliminare l'entità %s con ID %s", + "entityCannotBeSaved": "L'entità %s con ID %s non può essere salvata.", + "entityCannotBeUpdated": "L'entità %s con ID %s non può essere aggiornata.", + "fieldIsNotIndexed": "Il campo %s non è indicizzato!", + "unknownOrNotIndexed": "Colonna OrderBy %s sconosciuta o non indicizzata", + "dirNotCreated": "La directory \"%s\" non è stata creata", + "failedCreatingBackup": "Impossibile creare il servizio jolly.", + "backupServiceNotFound": "Servizio jolly non trovato." + }, + "autoTest": { + "dbNotAccessible.": "Database non accessibile.", + "taskCouldNotBeStarted.": "Non è stato possibile avviare l’attività.", + "moduleAutoTest": "Auto-test per il modulo di PacklinkPRO", + "useThisPageTestConfiguration": "Usa questa pagina per testare la configurazione del sistema e i servizi del modulo di PacklinkPRO.", + "start": "Avvia", + "downloadTestLog": "Scarica il registro test", + "openModule": "Apri il modulo di PacklinkPRO", + "autotestPassedSuccessfully": "L’auto-test è stato superato con successo!", + "testNotSuccessful": "Il test non è stato completato correttamente." + } +} \ No newline at end of file diff --git a/src/BusinessLogic/Resources/lang/toCSV.php b/src/BusinessLogic/Resources/lang/toCSV.php new file mode 100644 index 00000000..c4630625 --- /dev/null +++ b/src/BusinessLogic/Resources/lang/toCSV.php @@ -0,0 +1,38 @@ + $translations) { + foreach ($translations as $key => $value) { + $line = array(); + $line[] = $group; + $line[] = $key; + $line[] = $value; + $line[] = isset($es[$group][$key]) ? $es[$group][$key] : ''; + $line[] = isset($de[$group][$key]) ? $de[$group][$key] : ''; + $line[] = isset($fr[$group][$key]) ? $fr[$group][$key] : ''; + $line[] = isset($it[$group][$key]) ? $it[$group][$key] : ''; + + fputcsv($f, $line); + } + } + + fclose($f); +} + +toCsv(); \ No newline at end of file diff --git a/src/BusinessLogic/Resources/lang/translations.csv b/src/BusinessLogic/Resources/lang/translations.csv new file mode 100644 index 00000000..6f613d87 --- /dev/null +++ b/src/BusinessLogic/Resources/lang/translations.csv @@ -0,0 +1,315 @@ +Group,key,English,Spanish,German,French,Italian +general,packlinkPro,Packlink PRO Shipping,Packlink PRO Shipping,Packlink PRO Shipping,Packlink PRO Shipping,Packlink PRO Shipping +general,saveUp,"Save up to 70% on your shipping costs. No fixed fees, no minimum shipping volume required. Manage all your shipments in a single platform.","Ahorra hasta un 70% en tus gastos de envío. Sin tarifas fijas, sin volumen de envíos mínimo. Gestiona todos tus envíos en una sola plataforma.","Sparen Sie bis zu 70% bei Ihren Versandkosten. Keine festen Gebühren, kein Mindestvolumen beim Versand. Verwalten Sie alle Ihre Sendungen auf einer einzigen Plattform.","Économisez jusqu'à 70% sur vos coûts de livraison. Sans frais fixes, sans volume minimum requis. Gérez tous vos envois depuis une seule plateforme.",Risparmia fino a un 70% sui costi di spedizione. Senza costi fissi né volumi minimi di spedizione richiesti. Gestisci tutte le tue spedizioni da un'unica piattaforma. +general,packlinkShipping,Packlink Shipping S.L.,Packlink Shipping S.L.,Packlink Shipping S.L.,Packlink Shipping S.L.,Packlink Shipping S.L. +general,noContract,No contract needed! More than 300 transport services into a single platform. You can connect all your ecommerce and manage all your shipments in Packlink PRO.,"Sin contratos, accede inmediatamente a más de 300 servicios de transporte en una única plataforma. No importa cuantos puntos de venta tengas, todo lo podrás gestionar desde Packlink PRO.",Ohne Vertragsbindung – Sofortiger Zugriff auf über 300 Versanddienste in einer einzigen Plattform. Verbinden Sie Ihren Onlineshop und steuern Sie alle Sendungen über Packlink PRO.,"Sans engagement, accédez immédiatement à plus de 300 services de transport sur une seule et unique plateforme. Quelque soit le nombre de points de ventes que vous avez, vous pourrez tout gérer depuis Packlink PRO.","Senza contratti, accedi suvito a più di 300 servizi di trasporto da un'unica piattaforma. Indipendentemente da quanti punti vendita hai, puoi gestirli tutto da Packlink PRO." +general,developedAndManaged,Developed and managed by Packlink,Desarrollado y gestionado por Packlink,Entwickelt und verwaltet von Packlink,Développé et géré par Packlink,Sviluppato e gestito da Packlink +general,generalConditions,General conditions,Condiciones generales,Allgemeine Bedingungen,Conditions générales,Condizioni generali +general,basicSettings,Basic settings,Configuración,Einstellungen,Paramètres,Configurazione +general,contactUs,Contact us,Contacta con nosotros,Kontaktieren Sie uns,Contactez-nous,Contattaci +general,systemInfo,System info,Información del sistema,Systeminformationen,Informations du système,Informazioni del sistema +general,debugMode,Debug mode,Debug mode,Debug mode,Debug mode,Debug mode +general,help,Help,Ayuda,Hilfe,Aide,Aiuto +general,downloadSystemInfoFile,Download system info file,Descargar el archivo de información del sistema,Systeminfodatei herunterladen,Téléchargez le document d'information système,Scarica il file di informazioni del sistema +general,curlIsNotInstalled,cURL is not installed or enabled in your PHP installation. This is required for background task to work. Please install it and then refresh this page.,"cURL no está instalado ni habilitado en tu instalación de PHP. Es necesario para que funcione la tarea en segundo plano. Por favor, instálalo y luego actualiza esta página.","cURL ist in Ihrer PHP-Installation nicht installiert oder aktiviert. Dies ist jedoch erforderlich, damit die Hintergrundaufgabe funktioniert. Bitte installieren Sie es und aktualisieren Sie diese Seite.",cURL non installé ou non activé dans votre installation PHP. Cette démarche est nécessaire pour que la tâche de fond fonctionne. Veuillez l’installer et rafraîchir cette page.,cURL non installato o abilitato nella tua installazione PHP. È necessario per il corretto funzionamento dell’attività in background. Installalo e aggiorna la pagina. +general,loading,Loading...,Cargando...,Lädt...,Chargement en cours...,Caricamento in corso... +general,save,Save,Guardar,Speichern,Enregistrer,Salva +general,saveChanges,Save changes,Guardar cambios,Speichern,Enregistrer les modifications,Salva le modifiche +general,accept,Accept,Aceptar,Akzeptieren,Accepter,Accetto +general,cancel,Cancel,Cancelar,Abbrechen,Annuler,Annulla +general,continue,Continue,Continuar,Weiter,Continuer,Continua +general,complete,Complete,Completar,Vollständig,Compléter,Completi +general,add,Add,Añadir,Hinzufügen,Ajouter,Aggiungi +general,edit,Edit,Editar,Bearbeiten,Modifier,Modifica +general,delete,Delete,Borrar,Löschen,Supprimer,Elimina +general,discard,Discard,Ignorar,Verwerfen,Ignorer,Scarta +validation,requiredField,This field is required.,Esta campo es obligatorio.,Dies ist ein Pflichtfeld.,Ce champs est obligatoire.,Questo campo è obbligatorio. +validation,invalidField,This field is invalid.,This field is invalid.,This field is invalid.,This field is invalid.,This field is invalid. +validation,invalidEmail,This field must be valid email.,El campo debe ser un correo electrónico válido.,Das Feld muss eine gültige E-Mail-Adresse sein.,Le champs doit être une adresse email valide,Il campo deve essere un email valido. +validation,shortPassword,The password must be at least %s characters long.,La contraseña debe contener %s caracteres como mínimo.,Das Passwort muss mindestens %s Zeichen haben.,Le mot de passe doit comporter au moins %s lettres.,La password deve contenere almeno %s caratteri. +validation,invalidPhone,This field must be valid phone number.,Este campo deber ser un número de teléfono válido.,Bitte geben Sie eine gültige Telefonnummer ein.,Ce champs doit contenir un numéro de téléphone valide.,Questo campo deve contenere un numero di telefono valido. +validation,maxLength,The field cannot have more than %s characters.,Este campo no puede tener más de % caracteres.,Dieses Feld darf maximal %s Zeichen enthalten.,Ce champ ne peut contenir plus de %s caractères.,Il campo non può contenere più di %s caratteri. +validation,numeric,Value must be valid number.,Debe ser un valor numérico.,Bitte geben Sie einen numerischen Wert ein.,Cette valeur doit être numérique.,Questo valore deve essere numerico. +validation,greaterThanZero,Value must be greater than 0.,El valor debe ser mayor que 0.,Der Wert muss größer als 0 sein.,La valeur doit être spérieure à 0.,Il valore deve essere maggiore di 0. +validation,nonNegative,Field cannot have a negative value.,Field cannot have a negative value.,Das Feld darf keine negativen Werte enthalten.,Le champ ne peut contenir de valeur négative.,Il campo non può avere un valore negativo. +validation,numberOfDecimalPlaces,Field must have 2 decimal places.,El campo debe tener 2 decimales.,Das Feld muss 2 Dezimalstellen haben.,Le champs doit contenir deux décimales.,Il campo deve contenere due decimali. +validation,integer,Field must be an integer.,El campo debe ser un número entero.,Das Feld muss eine ganze Zahl sein.,Le champs doit être un nombre entier.,Il campo deve essere un numero intero. +validation,invalidCountryList,You must select destination countries.,Debes seleccionar los países de destino.,Bitte wählen Sie die Zielländer aus.,Veuillez sélectionner les pays de destination.,Devi selezionare i paesi di destinazione. +validation,invalidLanguage,Field is not a valid language.,Field is not a valid language.,Dieses Feld enthält keine gültige Sprache.,Le champ ne contient pas de langue valide.,Il campo non contiene una lingua valida. +validation,invalidPlatformCountry,Field is not a valid platform country.,Field is not a valid platform country.,Dieses Feld enthält kein gültiges Land.,Le champ ne contient pas de pays valide.,Il campo non contiene un paese della piattaforma valido. +validation,invalidUrl,Field must be a valid URL.,Field must be a valid URL.,Dieses Feld muss eine gültige URL enthalten.,Le champ doit contenir une URL valide.,Il campo deve contenere un URL valido. +validation,invalidFieldValue,"Field must be set to ""%s"".","Field must be set to ""%s"".",Dieses Feld muss auf „%s“ gesetzt werden.,Le champ doit être défini sur « %s ».,"Il campo deve essere impostato su ""%s""." +validation,invalidMaxValue,Maximal allowed value is %s.,Maximal allowed value is %s.,Der maximale Wert beträgt %s.,La valeur maximale autorisée est %s.,Il valore massimo consentito è %s. +login,apiKey,Api key,Api key,API-Schlüssel,Clé API,API Key +login,apiKeyIncorrect,API key was incorrect.,Clave de API incorrecta,Falscher API-Schlüssel,Clé API incorrecte,API Key non corretta +login,dontHaveAccount,Don't have an account?,¿No tienes una cuenta?,Sie haben noch keinen Account?,Vous n’avez pas de compte ?,Non hai un account? +login,welcome,Welcome to Packlink PRO,Te damos la bienvenida a Packlink PRO,Willkommen zu Packlink PRO,Bienvenue sur Packlink PRO,Benvenuto a Packlink PRO +login,connectYourService,"Connect your service using your key. You'll find it in the
""Packlink Pro API Key"" section of the ""Settings"".","Conecta tu servicio mediante tu clave. La encontrarás en la
sección ""Configuración"" apartado ""Packlink Pro API Key""","Verbinden Sie Ihren Dienst, indem Sie Ihren API-Schlüssel verwenden. Sie finden ihn unter
„Packlink Pro API-Schlüssel“ bei den „Einstellungen“.",Connectez votre service en utilisant votre clé. Vous la trouverez dans la section
« Clé API Packlink Pro » dans « Paramètres ».,"Usa la chiave per collegare il tuo servizio. La troverai nella sezione
""Chiave API Packlink Pro"" delle ""Impostazioni""." +login,validateApiKey,Validate API key,Validar API key,API-Schlüssel bestätigen,Valider clé API,Convalida la chiave API +login,chooseYourCountry,Choose your country to start the registration process,Elige tu país para comenzar el registro,"Wählen Sie Ihr Land aus, um mit der Registrierung beginnen zu können",Sélectionnez votre pays pour commencer votre inscription,"Per iniziare la registrazione, scegli il tuo paese" +login,registerMe,Register me,Registrarme,Registrieren,S’inscrire,Iscriviti +register,logIn,Log in,Iniciar sesión,Einloggen,Se connecter,Accedi +register,email,Email,Correo electrónico,E-Mail,Adresse électronique,Email +register,password,Password,Contraseña,Passwort,Mot de passe,Password +register,haveAccount,Already have an account?,¿Ya tienes una cuenta?,Haben Sie bereits einen Account?,Vous avez déjà un compte ?,Hai già un account? +register,startEnjoying,Start enjoying Packlink PRO!,¡Empieza a disfrutar de Packlink PRO!,Genießen Sie ab jetzt Packlink PRO!,Commencez à utiliser Packlink PRO !,Inizia a usufruire di Packlink PRO! +register,registerAccount,Register your account,Registro,Jetzt registrieren,Enregistrez-vous,Registrati +register,getToKnowYou,We would like to get to know you better in order to adapt to you and to send you an individual offer,Queremos conocerte mejor para adaptarnos a ti y hacerte una propuesta personalizada.,"Wir möchten Sie besser kennenlernen, um uns an Sie anzupassen und Ihnen ein individuelles Angebot zukommen zu lassen",Nous souhaitons mieux vous connaître afin de nous adapter à vous et de vous faire une offre sur mesure,Vogliamo conoscerti meglio per adattarci a te e preparare una proposta personalizzata per te +register,monthlyShipmentVolume,What is your monthly shipment volume?,¿Cuántos envíos sueles hacer al mes?,Wie hoch ist Ihr monatliches Sendungsaufkommen?,Combien d’envois faites-vous généralement par mois?,Quante spedizioni fai solitamente al mese? +register,phone,Phone number,Teléfono,Telefonnummer,Numéro de téléphone,Numero di telefono +register,termsAndConditions,"
I accept the Terms of Service and the Privacy Policy of Packlink
","
Acepto los Términos y condiciones y la Política de privacidad de Packlink
","
Ich akzeptiere die Nutzungsbedingungen und die Datenschutzerklärung von Packlink
","
IJ’accepte les Conditions de service et la Politique de confidentialité de Packlink
","
Accetto i Termini del servizio e l’Informativa sulla privacy di Packlink
" +register,marketingEmails,I authorise Packlink Shipping S.L. to send me commercial communications by e-mail,Autorizo a Packlink Shipping S.L. a enviarme información comercial por correo electrónico,"Ich ermächtige Packlink Shipping S.L., mir kommerzielle Mitteilungen per E-Mail zuzusenden",J’autorise Packlink Shipping S.L. à m’envoyer des communications commerciales par courriel,Autorizzo Packlink Shipping S.L. a inviarmi comunicazioni commerciali via email +register,submit,Register,Regístrate,Registrieren,S'inscrire,Registrati +register,chooseYourCountry,Choose your country,Elige tu país,Wählen Sie Ihr Land aus,Sélectionnez votre pays,Scegli il paese +register,searchCountry,Search country,Busca el país,Land suchen,Rechercher un pays,Cerca paese +register,invalidDeliveryVolume,Field is not a valid delivery volume.,El campo no contiene un volumen de entrega válido.,Das Feld enthält keinen gültigen Lieferumfang.,Le champ ne contient pas de volume de livraison valide.,Il campo non contiene un volume di consegna valido. +countries,ES,Spain,España,Spanien,Espagne,Spagna +countries,DE,Germany,Alemania,Deutschland,Allemagne,Germania +countries,FR,France,Francia,Frankreich,France,Francia +countries,IT,Italy,Italia,Italien,Italie,Italia +countries,AT,Austria,Austria,Österreich,Autriche,Austria +countries,NL,Netherlands,Países Bajos,Niederlande,Pays-Bas,Paesi Bassi +countries,BE,Belgium,Bélgica,Belgien,Belgique,Belgio +countries,PT,Portugal,Portugal,Portugal,Portugal,Portogallo +countries,TR,Turkey,Turquía,Türkei,Turquie,Turchia +countries,IE,Ireland,Irlanda,Irland,Irlande,Irlanda +countries,GB,Great Britain,Reino Unido,Vereinigtes Königreich,Royaume-Uni,Regno Unito +countries,HU,Hungary,Hungría,Ungarn,Hongrie,Ungheria +countries,PL,Poland,Polonia,Polen,Pologne,Polonia +countries,CH,Switzerland,Suiza,Schweiz,Suisse,Svizzera +countries,LU,Luxembourg,Luxemburgo,Luxemburg,Luxembourg,Lussemburgo +countries,AR,Argentina,Argentina,Argentinien,Argentine,Argentina +countries,US,United States,Estados Unidos,Vereinigte Staaten,États Unis,Stati Uniti +countries,BO,Bolivia,Bolivia,Bolivien,Bolivie,Bolivia +countries,MX,Mexico,México,Mexiko,Mexique,Messico +countries,CL,Chile,Chile,Chile,Chili,Chile +countries,CZ,Czech Republic,Republica checa,Tschechien,République Tchèque,Repubblica Ceca +countries,SE,Sweden,Suecia,Schweden,Suède,Svezia +onboardingWelcome,header,Let's set up basic information so that
you can make shipments,Vamos a configurar tu información básica
para poder hacer envíos,"Richten Sie nun die grundlegenden Informationen ein, damit
Sie Versendungen vornehmen können",Configurez vos informations de base afin
d’effectuer des envois,È necessario impostare alcuni dati di base così che
potrai effettuare spedizioni +onboardingWelcome,steps,It's just two steps that we need to go through to offer you the
carrier that best suits your needs,"Son solo dos pasos, los necesitamos para ofrecerte los servicios
de transporte que más se adaptan a tus necesidades.","In nur zwei Schritten können wir Ihnen den
Spediteur anbieten, der all Ihre Anforderungen erfüllt",Il ne vous reste que deux étapes pour savoir quel
transporteur répond le mieux à vos besoins,Si tratta di due semplici passaggi che dobbiamo completare per offrirti la
compagnia di trasporto che più si adatta alle tue esigenze +onboardingWelcome,stepOne,Set parcel details,Configurar los detalles del paquete,Paketangaben einfügen,Configurez les informations sur le colis,Imposta i dettagli del pacco +onboardingWelcome,stepTwo,Set sender's address,Configurar la dirección del remitente,Absenderadresse eingeben,Configurez l’adresse de l’émetteur,Imposta l’indirizzo del mittente +onboardingWelcome,startSetUp,Start set up,Comenzar con la configuración,Einrichten beginnen,Démarrez la configuration,Avvia la configurazione +onboardingOverview,header,Almost there! Please check that the entered information is correct or complete it in order to continue.,"Ya casi estamos. Por favor, comprueba que la información es correcta o complétala para continuar.","Fast geschafft! Bitte überprüfen Sie, ob die eingegebenen Informationen richtig sind oder vervollständigen Sie die Angaben, um fortzufahren.",Vous y êtes presque ! Veuillez vérifier que les informations saisies sont correctes ou complétez-les pour continuer.,"Ci siamo quasi! Per continuare, verifica che i dati inseriti siano corretti o completali." +onboardingOverview,parcelDetails,Parcel details,Detalle del paquete,Paketangaben,Informations sur le colis,Dettagli del pacco +onboardingOverview,senderAddress,Sender's Address,Dirección del remitente,Absenderadresse,Adresse de l’émetteur,Indirizzo del mittente +onboardingOverview,missingInfo,Missing information,Faltan datos,Fehlende Informationen,Informations manquantes,Dati mancanti +onboardingOverview,parcelData,Weight %s kg | Height %s cm | Width %s cm | Length %s cm,Peso %s kg | Alto %s cm | Ancho %s cm | Largo %s cm,Gewicht %s kg | Höhe %s cm | Breite %s cm | Länge %s cm,Poids %s kg | Hauteur %s cm | Largeur %s cm | Longueur %s cm,Peso %s kg | Altezza %s cm | Largezza %s cm | Lunghezza %s cm +onboardingOverview,warehouseData,%s | %s | %s,%s | %s | %s,%s | %s | %s,%s | %s | %s,%s | %s | %s +defaultParcel,title-onboarding,1. Set parcel details,1. Configurar los detalles del paquete,1. Paketangaben einfügen,1. Configurez les informations sur le colis,1. Imposta i dettagli del pacco +defaultParcel,description-onboarding,We will use this default data for items that do not have defined dimensions and weight. You can edit them whenever you like via the settings tab.,Utilizaremos estos datos predeterminados para los artículos que no tengan dimensiones y peso definidos. Podrás editarlos cuando quieras desde la pestaña de configuración.,"Wir werden diese Standarddaten für Artikel verwenden, die keine festgelegten Abmessungen und kein festgelegtes Gewicht haben. Sie können sie jederzeit über die Registerkarte Einstellungen bearbeiten.",Nous utiliserons ces données prédéfinies pour les articles dont les dimensions et le poids n’ont pas été définis. Vous pouvez les modifier à tout moment via l’onglet Paramètres.,Useremo questi dati predefiniti per gli articoli per cui non vengono specificate le dimensioni e il peso. Puoi modificarli quando vuoi dalla scheda delle impostazioni. +defaultParcel,title-config,Default parcel,Paquete predeterminado,Standardpaket,Colis prédéfini,Pacco predefinito +defaultParcel,description-config,We will use this data for items that do not have defined dimensions and weight.,Utilizaremos estos datos para los artículos que no tengan dimensiones y peso definidos.,"Wir werden diese Daten für Artikel verwenden, die keine festgelegten Abmessungen und kein festgelegtes Gewicht haben.",Nous utiliserons ces données pour les articles dont les dimensions et le poids n’ont pas été définis.,Useremo questi dati per articoli privi di dimensioni e peso definiti. +defaultParcel,weight,Weight,Peso,Gewicht,Poids,Peso +defaultParcel,height,Height,Alto,Höhe,Hauteur,Altezza +defaultParcel,width,Width,Ancho,Breite,Largeur,Largezza +defaultParcel,length,Length,Largo,Länge,Longueur,Lunghezza +defaultWarehouse,title-onboarding,2. Set sender’s address,2. Configurar la dirección del remitente,2. Absenderadresse eingeben,2. Configurer l’adresse de l’émetteur,2. Imposta l’indirizzo del mittente +defaultWarehouse,description-onboarding,We will use this address to create a default sender for all shipments. You will be able to edit it whenever you wish via the settings tab.,Utilizaremos esta dirección para crear un remitente por defecto que valga para todos los envíos. Podrás editarla cuando quieras desde la pestaña de configuración.,"Wir werden diese Adresse verwenden, um einen Standardabsender für alle Sendungen zu erstellen. Sie können die Adresse jederzeit über die Registerkarte Einstellungen bearbeiten.",Nous utiliserons cette adresse pour créer un émetteur prédéfini pour toutes les expéditions. Vous pourrez la modifier à tout moment via l’onglet Paramètres.,Useremo questo indirizzo per creare un mittente predefinito per tutte le spedizioni. Potrai modificarlo ogni volta che vuoi dalla scheda delle impostazioni. +defaultWarehouse,title-config,Default sender address,Dirección del remitente por defecto,Standard-Absenderadresse,Adresse de l’émetteur prédéfinie,Indirizzo del mittente predefinito +defaultWarehouse,description-config,We will use this address to create a default sender for all shipments. You can edit it at any time.,Utilizaremos esta dirección para crear un remitente por defecto que valga para todos los envíos.,"Wir werden diese Adresse verwenden, um einen Standardabsender für alle Sendungen zu erstellen. Sie können die Adresse jederzeit bearbeiten.",Nous utiliserons cette adresse pour créer un émetteur prédéfini pour toutes les expéditions. Vous pourrez la modifier à tout moment.,Useremo questo indirizzo per creare un mittente predefinito per tutte le spedizioni. Potrai modificarlo ogni volta che vuoi. +defaultWarehouse,alias,Sender name,Nombre del almacén,Absendername,Nom de l’émetteur,Nome del mittente +defaultWarehouse,alias-placeholder,Main warehouse,Almacen principal,Hauptlager,Magasin principal,Magazzino principale +defaultWarehouse,name,Name of contact person,Nombre de la persona de contacto,Name der Kontaktperson,Prénom de la personne à contacter,Nome della persona di contatto +defaultWarehouse,name-placeholder,Name,Nombe de la persona,Name,Prénom,Nome +defaultWarehouse,surname,Surname of contact person,Apellido de la persona de contacto,Nachname der Kontaktperson,Nom de la personne à contacter,Cognome della persona di contatto +defaultWarehouse,surname-placeholder,Surname,Apellido de la persona,Nachname,Nom,cognome +defaultWarehouse,company,Company name,Nombre de la empresa,Unternehmen,Nom de l'entreprise,Nome dell'azienda +defaultWarehouse,company-placeholder,Company,Empresa S.A.,Unternehmen,Société,Azienda +defaultWarehouse,country,Country,País,Land,Pays,Paese +defaultWarehouse,postal_code,City or postcode,Ciudad o código postal,Stadt oder Postleitzahl,Ville ou code postal,Città o codice postale +defaultWarehouse,postal_code-placeholder,-,-,-,-,- +defaultWarehouse,address,Address,Dirección,Straße und Hausnummer,Adresse,Indirizzo +defaultWarehouse,address-placeholder,Address,Dirección,Anschrift,Adresse,Indirizzo +defaultWarehouse,phone,Phone number,Número de teléfono,Telefonnummer,Numéro de téléphone,Numero di telefono +defaultWarehouse,phone-placeholder,123 456 7777,123 456 7777,123 456 7777,123 456 7777,123 456 7777 +defaultWarehouse,email,Email,Correo electrónico,E-Mail-Adresse,Email,Email +defaultWarehouse,email-placeholder,youremail@example.com,tucorreo@example.com,ihreemail@example.com,votreemail@exemple.com,latuaemail@esempio.com +configuration,menu,Settings,Configuración,Einstellungen,Paramètres,Impostazioni +configuration,title,Settings,Configuración,Einstellungen,Paramètres,Impostazioni +configuration,orderStatus,Order status,Estado de la orden,Bestellstatus,Statut de la commande,Stato dell’ordine +configuration,warehouse,Default sender address,Almacén por defecto,Standard-Absenderadresse,Adresse de l’émetteur prédéfinie,Indirizzo del mittente predefinito +configuration,parcel,Default parcel,Paquete predeterminado,Standardpaket,Colis par défaut,Pacco predefinito +configuration,help,Help,Ayuda,Hilfe,Aide,Aiuto +configuration,contactUs,Contact us at:,Contáctanos:,Kontaktieren Sie uns:,Contactez-nous:,Contattaci: +configuration,systemInfo,System information,Información del sistema,Systeminformationen,Informations du système,Informazioni del sistema +systemInfo,title,System information file
and debug mode,System information file
and debug mode,Systeminfodatei
und debug mode,Fichier d’informations du système
et mode débogage,File di informazioni del sistema
e modalità debug +systemInfo,debugMode,Debug mode,Debug mode,Debug mode,Debug mode,Debug mode +systemInfo,download,Download system information,Download system information,Systeminfodatei herunterladen,Téléchargez le document d'information système,Scarica il file di informazioni del sistema +orderStatusMapping,title,Order status,Estado de la orden,Bestellstatus,Statut de la commande,Stato dell’ordine +orderStatusMapping,description,With Packlink you can update your %s order status with shipping information.
You can edit it at any time.,Con Packlink puedes actualizar el estado de tu pedido de %s con la información de envío.
Puedes editarla en cualquier momento.,Mit Packlink können Sie Ihren %s Bestellstatus mit Versandinformationen aktualisieren.
Sie können die Informationen jederzeit bearbeiten.,"Avec Packlink, vous pouvez mettre à jour le statut de votre commande %s avec les informations d’expédition.
Vous pourrez les modifier à tout moment.","Con Packlink, puoi aggiornare lo stato dell’ordine %s con i dati della spedizione.
Puoi modificarlo ogni volta che vuoi." +orderStatusMapping,packlinkProShipmentStatus,Packlink PRO SHIPMENT STATUS,ESTADO DEL ENVÍO de Packlink PRO,VERSANDSTATUS von Packlink PRO,STATUT DE LIVRAISON Packlink PRO,STATO DELLA SPEDIZIONE di Packlink PRO +orderStatusMapping,systemOrderStatus,%s ORDER STATUS,ESTADO DEL PEDIDO de %s,BESTELLSTATUS von %s,STATUT DE LA COMMANDE %s,STATO DELLA SPEDIZIONE di %s +orderStatusMapping,none,None,Ninguno,Leer,Aucun,Nessuno +orderStatusMapping,pending,Pending,Pendiente,Ausstehend,En attente,In attesa +orderStatusMapping,processing,Processing,Procesando,In Bearbeitung,En cours de traitement,Processando +orderStatusMapping,readyForShipping,Ready for shipping,Listo para enviar,Versandfertig,Prêt pour la livraison,Pronto per la spedizione +orderStatusMapping,inTransit,In transit,En tránsito,Unterwegs,En transit,In transito +orderStatusMapping,delivered,Delivered,Entregado,Zugestellt,Délivré,Consegnato +orderStatusMapping,cancelled,Canceled,Cancelado,Storniert,Annulé,Annullato +shippingServices,myServices,My shipping services,Mis servicios de envío,Mein Versandservice,Mes services d’expédition,I miei servizi di spedizione +shippingServices,noServicesTitle,You just need to add your shipment services,Solo queda añadir tus servicios de envío,Sie brauchen nur Ihre Transportdienstleistung hinzuzufügen,Vous devez simplement ajouter vos services d’expédition,Dovrai solo aggiungere i tuoi servizi di spedizione +shippingServices,noServicesDescription,"They will be shown to your customer at the time of checkout, so that they can choose which carrier will deliver their parcel. Set it up now so that you don't need to worry about anything else. You can change it whenever you wish, to renew or upgrade shipping services.","Se mostrarán a tu cliente en el momento del checkout, así podrá elegir qué transportista le llevará su paquete. Lo configuras ahora y no tienes que preocuparte por nada más, puedes cambiarlo siempre que quieras para renovar o ampliar servicios de envío.","Sie werden Ihrem Kunden zum Zeitpunkt der Kaufabwicklung angezeigt, damit er auswählen kann, welcher Spediteur sein Paket ausliefert. Richten Sie es jetzt ein, damit Sie sich um nichts anderes kümmern müssen. Sie können es jederzeit ändern, um die Versanddienste zu erneuern oder zu aktualisieren.","Votre client pourra les voir au moment du paiement et choisir le transporteur qui livrera son colis. Configurez-les maintenant pour n’avoir à vous soucier de rien d’autre. Vous pourrez à tout moment modifier, mettre à jour ou améliorer vos services d’expédition.","Questi saranno mostrati al cliente al momento del pagamento, per consentirgli di scegliere la compagnia di trasporto che effettuerà la consegna del pacco. Configurali adesso e non dovrai più preoccuparti di nulla. Puoi modificarli ogni volta che vuoi, per aggiornare o rinnovare i servizi di spedizione." +shippingServices,addService,Add service,Añadir servicio,Dienstleistung hinzufügen,Ajouter un service,Aggiungi servizio +shippingServices,addNewService,Add new service,Añadir nuevo servicio,Neue Dienstleistung hinzufügen,Ajouter un nouveau service,Aggiungi nuovo servizio +shippingServices,addedSuccessTitle,Shipping service added successfully,Servicio de envío añadido correctamente,Versanddienst erfolgreich hinzugefügt,Service d’expédition ajouté avec succès,Il servizio di spedizione è stato aggiunto +shippingServices,addedSuccessDescription,"You can edit the service from the ""My services"" section.","Podrás editarlo desde la sección ""Mis servicios"".",Sie können die Dienstleistung unter „Meine Dienstleistungen“ bearbeiten.,Vous pouvez modifier le service dans la section « Mes services ».,"Puoi modificare il servizio dalla sezione ""I miei servizi""." +shippingServices,deletedSuccessTitle,Shipping service deleted successfully,Servicio de envío eliminado correctamente,Versanddienst erfolgreich gelöscht,Service d’expédition supprimé avec succès,Il servizio di spedizione è stato eliminato +shippingServices,deletedSuccessDescription,"You can add new services from the ""Add new service"" button.","Puedes añadir nuevos servicios con el botón ""Añadir nuevo servicio"".",Über die Schaltfläche „Neue Dienstleistung hinzufügen“ können Sie neue Dienstleistungen hinzufügen.,Vous pouvez ajouter de nouveaux services avec le bouton « Ajouter un nouveau service ».,"Puoi aggiungere nuovi servizi tramite il pulsante ""Aggiungi nuovo servizio""." +shippingServices,disableCarriersTitle,You have created your first shipment service. Do you want to disable previous carriers?,Has creado tu primer servicio de envío. ¿Quieres desabilitar los transportistas anteriores?,Sie haben Ihre erste Transportdienstleistung erstellt. Möchten Sie die vorherigen Spediteure deaktivieren?,Vous avez créé votre premier service d’expédition. Souhaitez-vous désactiver les anciens transporteurs ?,Hai creato il tuo primo servizio di spedizione. Vuoi disattivare le compagnie di trasporto precedenti? +shippingServices,disableCarriersDescription,"To provide you with a better service, it is important to disable the carriers you previously used.","Para ofrecerte un mejor servicio, es importante desabilitar los transportistas que tenías previamente.","Um Ihnen einen besseren Service anbieten zu können, sollten Sie die vorher genutzten Spediteure deaktivieren.","Afin de vous offrir un meilleur service, il est important de désactiver le transporteur que vous utilisiez auparavant.","Per garantirti un servizio migliore, è importante disattivare le compagnie di trasporto utilizzati in precedenza." +shippingServices,successfullyDisabledShippingMethods,Successfully disabled shipping methods.,Servicios de envío correctamente deseleccionados.,Versanddienste erfolgreich deaktiviert.,Service d'envoi désactivé avec succès.,Servizi di spedizione disattivati correttamente. +shippingServices,failedToDisableShippingMethods,Failed to disable shipping methods.,Error al deshabilitar los servicios de envío.,Versanddienste konnten nicht deaktiviert werden.,Échec de la désactivation du service d'envoi.,Impossibile disattivare i servizi di spedizione. +shippingServices,pickShippingService,Add delivery services,Añadir servicios de envío,Lieferdienst hinzufügen,Ajouter des services de livraison,Aggiungi servizi di consegna +shippingServices,failedGettingServicesTitle,We are having troubles getting shipping services,Tenemos dificultades para obtener servicios de envío,Beim Abrufen der Versanddienste ist ein Problem aufgetreten,Nous rencontrons des problèmes dans l’importation des services de livraison,Si è verificato un problema durante l’acquisizione dei servizi di spedizione +shippingServices,failedGettingServicesSubtitle,Do you want to retry?,¿Quieres volverlo a intentar?,Möchten Sie es erneut versuchen?,Souhaitez-vous réessayer ?,Vuoi riprovare? +shippingServices,retry,Retry,Volver a intentar,Erneut versuchen,Réessayer,Riprova +shippingServices,filterModalTitle,Filters,Filtros,Filter,Filtres,Filtri +shippingServices,applyFilters,Apply filters,Aplicar,Filter anwenden,Appliquer les filtres,Applica i filtri +shippingServices,type,Type,Tipo,Typ,Type,Tipo +shippingServices,national,Domestic,Nacional,National,National,Nazionale +shippingServices,international,International,Internacional,International,International,Internazionale +shippingServices,deliveryType,Type of shipment,Servicio,Versandart,Livraison,Servizio +shippingServices,economic,Budget,Económico,Standard,Économique,Economico +shippingServices,express,Express,Express,Express,Express,Espresso +shippingServices,parcelOrigin,Parcel origin,Origen,Absendeort des Pakets,Origine du colis,Origine del pacco +shippingServices,collection,Collection,Recogida,Abholung an Adresse,Collecte à domicile,Ritiro a domicilio +shippingServices,dropoff,Drop off,Drop off,Abgabe im Paketshop,Dépôt en point relais,Ritiro in punto corriere +shippingServices,parcelDestination,Parcel destination,Destino,Zielort des Pakets,Destination du colis,Destinazione pacco +shippingServices,pickup,Pick up,Pick up,Abholung im Paketshop,Point de retrait,Consegna al domicilio +shippingServices,delivery,Delivery,Entrega,Zustellung an Adresse,Livraison à domicile,Consegna in punto corriere +shippingServices,carrierLogo,Carrier logo,Logo del transportista,Logo des Versandunternehmens,Logo du transporteur,Logo del corriere +shippingServices,carrier,Carrier,Transportista,Versandpartner,Transporteur,Corriere +shippingServices,serviceTitle,Service title,Nombre del servicio,Name des Dienstes,Titre du service,Titolo del servizio +shippingServices,serviceTitleDescription,Customise your shipment by editing it. Your customers will be able to see it.,Personaliza tu envío editándolo. Será visible para tus clientes.,"Personalisieren Sie Ihren Versand, indem Sie ihn bearbeiten. Ihre Kunden werden es sehen können.",Personnalisez votre envoi en le modifiant. Vos clients pourront le voir.,Modifica le tue spedizioni per personalizzarle. Queste informazioni saranno visualizzate dai tuoi clienti. +shippingServices,transitTime,Transit Time,Tiempo de tránsito,Versandlaufzeit,Temps de transit,Tempi di transito +shippingServices,origin,Origin,Origen,Absender,Origine,Origine +shippingServices,destination,Destination,Destino,Empfänger,Destination,Destinazione +shippingServices,myPrices,My prices,Mis precios,Meine Preise,Mes prix,I miei prezzi +shippingServices,packlinkPrices,Packlink prices,Precios de Packlink,Packlink-Preise,Prix Packlink,Prezzi di Packlink +shippingServices,configureService,Set up service,Configura el servicio,Dienst einrichten,Configurer le service,Configura il servizio +shippingServices,showCarrierLogo,Show carrier logo to my customers,Mostrar logo del transportista a mis clientes.,Meinen Kunden das Logo anzeigen,Montrer le logo du transporteur à mes clients,Mostra logo del corriere ai miei clienti +shippingServices,pricePolicy,Price policy,Política de precios,Preispolitik,Politique tarifaire,Politica dei prezzi +shippingServices,pricePolicyDescription,The default will be Packlink's basic prices. But you can configure these in a more precise way below.,Por defecto serán los precios base de Packlink. Pero puedes configurarlos de manera más precisa a continuación.,Standardmäßig werden die Packlink Basispreise eingestellt sein. Sie können diese jedoch weiter unten genauer konfigurieren.,"Les tarifs de base de Packlink seront configurés par défaut, mais vous pourrez les configurer plus en détail ci-dessous.","Per impostazione predefinita, saranno i prezzi di base di Packlink, ma puoi configurarli più dettagliatamente qui di seguito." +shippingServices,configurePricePolicy,Set up my shipment prices,Configurar mis precios de envío,Meine Versandkosten einrichten,Configurer mes frais d’expédition,Configura i prezzi di spedizione +shippingServices,taxClassTitle,Choose your saved tax class,Elige tu tipo de impuesto guardado,Wählen Sie Ihre gespeicherte Steuerkategorie,Sélectionnez votre taux fiscal enregistré,Scegli la tua aliquota fiscale salvata +shippingServices,tax,Tax,Impuestos,Steuer,Taxe,Tassa +shippingServices,serviceCountriesTitle,Availability by destination country,Disponibilidad por país de destino,Verfügbarkeit nach Zielland,Disponibilité par pays de destination,Disponibilità per paese di destinazione +shippingServices,serviceCountriesDescription,Select the availability for the countries that are supported for your shipping service.,Selecciona la disponibilidad para los países que hay permitidos para tu servicio de envío.,"Wählen Sie die Verfügbarkeit der Länder, die von Ihrem Versanddienst unterstützt werden.",Sélectionnez la disponibilité des pays pris en charge par votre service d’expédition.,Seleziona la disponibilità per i paesi supportati per i tuoi servizi di spedizione. +shippingServices,openCountries,See countries,Ver países,Länder anzeigen,Voir pays,Vedi i paesi +shippingServices,allCountriesSelected,All countries selected,Todos los países seleccionados,Es wurden alle Länder ausgewählt,Tous les pays sont sélectionnés,Tutti i paesi selezionati +shippingServices,oneCountrySelected,One country selected,Un país seleccionado,Es wurde ein Land ausgewählt,Un pays est sélectionné,Un paese selezionato +shippingServices,selectedCountries,%s countries selected.,%s países seleccionados,Es wurden %s Länder ausgewählt,%s pays sont sélectionnés,%s paesi selezionati +shippingServices,firstPricePolicyDescription,"Perfect, just a couple of steps:

1. Choose whether you want to set them by weight and/or purchase price.

2. Choose a price policy and set it up.","Perfecto, son un par de pasos:

1- Elige si quieres fijarlos por el peso y/o el precio de la compra.

2- Elige una política de precios y configúrala.","Perfekt, nur noch wenige Schritte:

1. Wählen Sie aus, ob Sie diese nach Gewicht und / oder Kaufpreis einstellen möchten.

2. Wählen Sie eine Preispolitik und richten Sie diese ein.","Parfait, il ne vous reste que quelques étapes :

1. Choisissez si vous souhaitez les configurer par poids et/ou par prix d’achat.

2. Choisissez une politique tarifaire et configurez-la.","Perfetto, mancano solo due passaggi:

1. Scegli se vuoi importarli per peso e/o prezzo di acquisto.

2. Scegli una politica dei prezzi e configurala." +shippingServices,addFirstPolicy,Add price rule,Añadir regla de precio,Preisregel hinzufügen,Ajouter une règle de prix,Aggiungi regola di prezzo +shippingServices,addAnotherPolicy,Add another rule,Añadir otro precio,Weitere Regel hinzufügen,Ajouter une autre règle,Aggiungi un’altra regola +shippingServices,from,From,Desde,Von,De,Da +shippingServices,to,To,Hasta,Bis,À,A +shippingServices,price,Price,Precio,Preis,Prix,Prezzo +shippingServices,rangeTypeExplanation,1. Choose whether you want to set them by weight and/or purchase price.,1- Elige si quieres fijarlos por el peso y/o el precio de la compra.,"1. Wählen Sie aus, ob Sie diese nach Gewicht und / oder Kaufpreis einstellen möchten.",1. Choisissez si vous souhaitez les configurer par poids et/ou par prix d’achat.,1. Scegli se vuoi importarli per peso e/o prezzo di acquisto. +shippingServices,rangeType,Range type,Tipo de rango,Typenreihe,Type de fourchette,Tipo di range +shippingServices,priceRange,Price range,Rango de precio,Preisklasse,Fourchette de prix,Range di prezzo +shippingServices,priceRangeWithData,Price range: from %s€ to %s€,Rango de precio: desde %s€ hasta %s€,Preisklasse: von %s € bis %s €,Fourchette de prix : de %s € à %s €,Range di prezzo: da %s€ a %s€ +shippingServices,weightRange,Weight range,Rango de peso,Gewichtsklasse,Fourchette de poids,Range di peso +shippingServices,weightRangeWithData,Weight range: from %s kg to %s kg,Rango de peso: desde %s kg hasta %s kg,Gewichtsklasse: von %s kg bis %s kg,Fourchette de poids : de %s kg à %s kg,Range di peso: da %s kg a %s kg +shippingServices,weightAndPriceRange,Weight and price range,Rango de peso y precio,Gewichts- und Preisklasse,Fourchette de poids et de prix,Range di prezzo e di peso +shippingServices,weightAndPriceRangeWithData,Weight and price range: from %s kg to %s kg and from %s€ to %s€,Rango de peso y precio: desde %s kg hasta %s kg y desde %s€ hasta %s€,Gewichts- und Preisklasse: von %s kg bis %s kg und von %s € bis %s €,Fourchette de poids et de prix : de %s kg à %s kg et de %s € à %s €,Range di prezzo e di peso: da %s kg a %s kg e da %s€ a %s€ +shippingServices,singlePricePolicy,Price policy %s,Política de precio %s,Preispolitik %s,Politique tarifaire %s,Politica dei prezzi %s +shippingServices,pricePolicyExplanation,2. Choose a price policy and set it up.,2- Elige una política de precios y configúrala.,2. Wählen Sie eine Preispolitik und richten Sie diese ein.,2. Choisissez une politique tarifaire et configurez-la.,2. Scegli una politica dei prezzi e configurala. +shippingServices,packlinkPrice,Packlink Price,Precio Packlink,Packlink-Preise,Prix Packlink,Prezzo di Packlink +shippingServices,percentagePacklinkPrices,% of Packlink prices,% de los precio de Packlink,% der Packlink-Preise,% des prix Packlink,% dei prezzi di Packlink +shippingServices,percentagePacklinkPricesWithData,% of Packlink prices: %s by %s%,% de los precio de Packlink: %s por %s%,% der Packlink-Preise: %s von %s%,% des prix Packlink : %s de %s%,% dei prezzi di Packlink: %s del %s% +shippingServices,fixedPrices,Fixed price,Precio fijo,Festpreis,Prix fixe,Prezzo fisso +shippingServices,fixedPricesWithData,Fixed price: %s€,Precio fijo: %s€,Festpreis: %s €,Prix fixe : %s €,Prezzo fisso: %s€ +shippingServices,increaseExplanation,"Increase: add a % to the price, this will be paid by the customer.","Incrementar: añade un % al precio, lo pagará el cliente.",Erhöhen: fügen Sie % zum Preis hinzu. Dies wird dann vom Kunden bezahlt.,"Augmentation : ajoutez % au prix, c’est le client qui payera.","Incremento sul prezzo: aggiungi una % al prezzo, che sarà pagata dal cliente." +shippingServices,reduceExplanation,"Reduce: deduct a % from the price, you will pay for it yourself.","Reducir: descuenta un % al precio, lo pagarás tú.",Reduzieren: ziehen Sie % vom Preis ab. Dies werden Sie dann selbst zahlen.,"Réduction : déduisez % du prix, c’est vous qui payerez.","Riduzione sul prezzo: deduci una % dal prezzo, che sarà pagata da te." +shippingServices,select,Select,Elige,Auswählen,Sélectionner,Sélectionner +shippingServices,invalidRange,Invalid range,Rango no válido,Unzulässiger Bereich,Fourchette invalide,Range non valido +shippingServices,increase,increase,incrementar,Erhöhen,Augmenter,Aumenta +shippingServices,reduce,reduce,reducir,Reduzieren,Réduire,Diminuisci +shippingServices,selectAllCountries,All selected countries,Selecionar todos los países,Alle ausgewählten Länder,Tous les pays sélectionnés,Tutti i paesi selezionati +shippingServices,selectCountriesHeader,Countries supported for your
shipping service,Países permitidos para tu servicio
de envío,"Länder, die von Ihrem
Versanddienst unterstützt werden",Pays pris en charge par votre
service d’expédition,Paesi supportati per il tuo
servizio di spedizione +shippingServices,selectCountriesSubheader,Select availability of at least one country and add
as many required,Selecciona la disponibilidad de al menos un país y
añade tantos como necesites,Wählen Sie die Verfügbarkeit von mindestens einem Land aus und fügen Sie
beliebig viele hinzu,Sélectionnez la disponibilité d’au moins un pays et ajoutez-en
autant que nécessaire,Seleziona la disponibilità di almeno un paese e aggiungi
tutti quelli richiesti +shippingServices,usePacklinkRange,"For all other ranges, apply Packlink prices","Para el resto de rangos, aplicar precios Packlink",Für alle anderen Bereiche gelten die Packlink-Preise,"Pour toutes les autres fourchettes, appliquez les prix Packlink","Per tutti gli altri range, applica i prezzi di Packlink" +shippingServices,discardChangesQuestion,There are unsaved changes.
Are you sure you want to go back and discard them?,Algunos cambios no se han guardado.
¿Estás seguro de que quieres volver e ignorar los cambios?,"Es gibt nicht gespeicherte Änderungen.
Sind Sie sicher, dass Sie zurückgehen und sie verwerfen möchten?",Certaines modifications n’ont pas été enregistrées.
Êtes-vous sûr de vouloir revenir en arrière et de les ignorer ?,Alcune modifiche non sono state salvate.
Sei sicuro di voler tornare indietro e annullarle? +orderListAndDetails,packlinkOrderDraft,Packlink order draft,Borrador del pedido de Packlink,Packlink Auftragsentwurf,Brouillon de commande Packlink,Bozza dell'ordine Packlink +orderListAndDetails,printed,Printed,Impreso,Ausgedruckt,Imprimé,Stampato +orderListAndDetails,ready,Ready,Listo,Bereit,Prêt,Pronto per la spedizione +orderListAndDetails,printLabel,Print label,Imprimir etiquetas,Versandetikett drucken,Imprimer étiquette,Stampa etichette +orderListAndDetails,shipmentLabels,Shipment Labels,Etiquetas de los envíos,Versandetiketten,Étiquettes de livraison,Etichette di spedizione +orderListAndDetails,printShipmentLabels,Print Packlink PRO Shipment Labels,Imprimir Packlink PRO etiquetas de los envíos,Packlink PRO Versandetiketten drucken,Imprimer les étiquettes de livraison Packlink PRO,Stampa etichette di spedizione Packlink PRO +orderListAndDetails,shipmentDetails,Shipment details,Detalles del envío,Versanddetails,Détails de l'envoi,Dettagli della spedizione +orderListAndDetails,disablePopUpBlocker,Please disable pop-up blocker on this page in order to bulk open shipment labels,Deshabilita el bloqueador de elementos emergentes en esta página para abrir de forma masiva las etiquetas de envío,"Bitte deaktivieren Sie den Popup-Blocker auf dieser Seite, um alle Versandetiketten auf einmal zu öffnen.",Veuillez désactiver l'outil de blocage des fenêtres pop-up sur cette page afin d'ouvrir massivement les étiquettes d'envoi.,Disabilita il blocco pop-up su questa pagina per poter aprire le etichette di spedizione insieme +orderListAndDetails,date,Date,Fecha,Datum,Date,Data +orderListAndDetails,number,Number,Número,Nummer,Numéro,Numero +orderListAndDetails,status,Status,Estado,Status,Statut,Stato +orderListAndDetails,print,Print,Imprimir,Drucken,Imprimer,Stampa +orderListAndDetails,carrierTrackingNumbers,Carrier tracking numbers,Número de tracking del transportista,Trackingnummer des Versandunternehmens,Numéro de tracking du transporteur,Numero di tracking del corriere +orderListAndDetails,trackIt,Track it!,Seguimiento,Versand verfolgen,Suivez le!,Traccialo! +orderListAndDetails,packlinkReferenceNumber,Packlink reference number,Número de referencia Packlink,Packlink Referenznummer,Numéro de référence Packlink,Numero di ordine Packlink +orderListAndDetails,packlinkShippingPrice,Packlink shipping price,Precio de envío de Packlink,Packlink Versandpreis,Prix de livraison Packlink,PPrezzo di spedizione Packlink +orderListAndDetails,viewOnPacklink,View on Packlink PRO,Ver en Packlink PRO,Auf Packlink PRO ansehen,Voir sur Packlink PRO,Vedi su Packlink PRO +orderListAndDetails,createDraft,Create Draft,Crear borrador,Entwurf erstellen,Créer un brouillon,Crea bozza +orderListAndDetails,draftIsBeingCreated,Draft is currently being created in Packlink PRO,El borrador se está creando actualmente en Packlink PRO,Der Entwurf wird gerade in Packlink PRO erstellt,Le brouillon est en cours de création sur Packlink PRO,La bozza è attualmente in fase di creazione su Packlink PRO +orderListAndDetails,createOrderDraft,Create order draft in Packlink PRO,Crear borrador en Packlink PRO,Entwurf der Bestellung in Packlink PRO erstellen,Créer un brouillon de commande sur Packlink PRO,Crea una bozza di ordine su Packlink PRO +orderListAndDetails,draftCreateFailed,Previous attempt to create a draft failed. Error: %s,El intento anterior de crear un borrador ha fallado. Error: %s,Der Versuch der Entwurfserstellung ist fehlgeschlagen. Fehler: %s,La tentative précédente de création d'un brouillon a échoué. Erreur: %s,Il precedente tentativo di creare una bozza è fallito. Errore: %s +orderListAndDetails,packlinkShipping,Packlink Shipping,Packlink Shipping,Packlink Shipping,Packlink Shipping,Packlink Shipping +orderListAndDetails,shipmentOrderNotExist,Order with shipment reference %s doesn't exist in the shop,El pedido con referencia de envío %s no existe en la tienda,Die Bestellung mit der Versandreferenznummer %s existiert nicht im Shop,La commande de livraison sous le référence %s n'existe pas sur le shop.,L'ordine con il numero di spedizione %s non esiste nel negozio +orderListAndDetails,orderDetailsNotFound,Order details not found,Detalles del pedido no encontrados,Bestelldetails nicht gefunden,Détails de commande introuvables,Dettagli dell'ordine non trovati +orderListAndDetails,orderNotExist,Order with ID %s doesn't exist in the shop,El pedido con ID %s no existe en la tienda,Die Bestellung mit der ID %s existiert nicht im Shop,La commande sous l'ID %s n'existe pas sur le shop.,L'ordine con ID %s non esiste nel negozio +orderListAndDetails,bulkFailCreateFail,Unable to create bulk labels file,No se puede crear el archivo de etiquetas masivas,Die Datei der Versandetiketten konnte nicht erstellt werden,Création d'un dossier d'étiquettes en masse impossible.,Impossibile creare il file di etichette in blocco +orderListAndDetails,draftOrderDelayed,Creation of the draft is delayed,La creación del borrador se ha retrasado,Die Erstellung des Entwurfs verzögert sich,La création du brouillon a été retardée,La creazione della bozza è stata ritardata +orderListAndDetails,completeServicesSetup,You need to complete the services setup to create order draft.,You need to complete the services setup to create order draft.,You need to complete the services setup to create order draft.,You need to complete the services setup to create order draft.,You need to complete the services setup to create order draft. +orderListAndDetails,sendWithPacklink,Send with Packlink,Send with Packlink,Send with Packlink,Send with Packlink,Send with Packlink +checkoutProcess,choseDropOffLocation,"This shipping service supports delivery to pre-defined drop-off locations. Please choose location that suits you the most by clicking on the ""Select drop-off location"" button.","Este servicio de envío admite la entrega a ubicaciones de entrega predefinidas. Elige la ubicación que más te convenga haciendo clic en el botón ""Seleccionar ubicación de entrega""..","Dieser Versanddienst unterstützt die Zustellung an vordefinierte Abgabestellen. Bitte wählen Sie den für Sie günstigsten Ort aus, indem Sie auf ""Abgabestelle auswählen"" klicken.","Ce service de transport soutient la livraison vers des lieux de dépôts prédéfinis. Veuillez choisir la localisation qui vous convient le mieux en cliquant sur le bouton ""Sélectionner le lieu de dépôt""","Questo servizio di spedizione supporta la consegna a punti corriere predefiniti. Scegli la località più adatta a te, facendo clic sul pulsante ""Seleziona punto corriere""." +checkoutProcess,selectDropOffLocation,Select drop-off location,Seleccionar lugar de entrega,Abgabestelle auswählen,Sélectionner le lieu de dépôt.,Seleziona la località del punto corriere +checkoutProcess,changeDropOffLocation,Change drop-off location,Cambiar el lugar de entrega,Abgabestelle ändern,Changer le lieu de depôt.,Cambia località del punto corriere +checkoutProcess,packageDeliveredTo,Package will be delivered to:,El paquete será entregado a:,Das Paket wird geliefert an:,Le colis sera délivré à:,La spedizione verrà consegnata a: +checkoutProcess,dropOffDeliveryAddress,Drop-Off delivery address,Dirección de entrega,Zustelladresse,Adresse de livraison.,Indirizzo di consegna del punto corriere +checkoutProcess,changeAddress,There are no delivery locations available for your delivery address. Please change your address.,No hay servicio disponible para la dirección de entrega. Por favor cambia tu direccion.,Für Ihre Zustelladresse sind keine Zustellorte verfügbar. Bitte ändern Sie Ihre Adresse.,Il n'y a pas de lieux de livraison disponibles pour votre adresse de livraison. Veuillez changer votre adresse.,Non ci sono località di consegna disponibili per il tuo indirizzo di consegna. Per favore cambia l'indirizzo. +checkoutProcess,shippingServiceNotFound,Shipping service not found,Servicio de envío no encontrado,Versanddienst nicht gefunden,Service de livraison introuvable.,Servizio di spedizione non trovato +locationPicker,monday,Monday,Lunes,Montag,Lundi,Lunedì +locationPicker,tuesday,Tuesday,Martes,Dienstag,Mardi,Martedì +locationPicker,wednesday,Wednesday,Miércoles,Mittwoch,Mercredi,Mercoledì +locationPicker,thursday,Thursday,Jueves,Donnerstag,Jeudi,Giovedì +locationPicker,friday,Friday,Viernes,Freitag,Vendredi,Venerdì +locationPicker,saturday,Saturday,Sábado,Samstag,Samedi,Sabato +locationPicker,sunday,Sunday,Domingo,Sonntag,Dimanche,Domenica +locationPicker,zipCode,Zip code,Código postal,Postleitzahl,Code postal,Codice postale +locationPicker,idCode,ID code,Código ID,ID Code,Code ID,Codice ID +locationPicker,selectThisLocation,Select this location,Selecciona esta ubicación,Diesen Ort auswählen,Sélectionner ce lieu,Seleziona la località +locationPicker,showWorkingHours,Show working hours,Mostrar horas de apertura,Öffnungszeiten anzeigen,Montrer les heures de travail,Mostra orari di apertura +locationPicker,hideWorkingHours,Hide working hours,Ocultar horario de apertura,Öffnungszeiten ausblenden,Cacher les heures d'ouverture,Nascondi orari di apertura +locationPicker,showOnMap,Show on map,Mostrar en el mapa,Auf der Karte anzeigen,Montrer sur la carte,Mostra sulla mappa +locationPicker,searchBy,"Search by location name, id or address","Buscar por nombre de ubicación, ID o dirección","Suche nach Standortname, ID oder Adresse","Chercher par lieu, nom, id ou adresse","Cerca nome della località per nome, id o indirizzo" +migrationLogMessages,removeControllersHooksFailed,Failed to remove old controllers and hooks because: %s,Error al eliminar los controladores y enlaces antiguos porque: %s,Alte Controller und Hooks konnten nicht entfernt werden auf Grund von: %s,Echec du retrait des anciens régulateurs et crochets parce que. %s,Impossibile rimuovere vecchi controller e hook perché: %s +migrationLogMessages,deleteObsoleteFilesFailed,Could not delete obsolete files because: %s,No se pudieron eliminar los archivos antiguos porque: %s,Alte Dateien konnten nicht gelöscht werden auf Grund von: %s,La suppression des dossiers obsolètes n'a pas pu être effectuée car: %s,Impossibile eliminare i file obsoleti perché: %s +migrationLogMessages,oldAPIKeyDetected.,Old API key detected.,Se ha detectado una clave de API antigua.,Alter API-Schlüssel erkannt.,Ancienne clé API détectée.,Rilevata vecchia API Key. +migrationLogMessages,successfullyLoggedIn,Successfully logged in with existing api key.,Has iniciado correctamente la sesión con la clave de API existente.,Erfolgreich mit bestehendem API-Schlüssel angemeldet.,Connecté avec succès avec la clé API existante.,Accesso eseguito correttamente con la API Key esistente. +migrationLogMessages,removingObsoleteFiles.,Removing obsolete files.,Eliminando archivo antiguos.,Veraltete Dateien werden entfernt.,Retrait des dossiers osolètes.,Rimozione di file obsoleti. +migrationLogMessages,cannotEnqueueUpgradeShopDetailsTask,Cannot enqueue UpgradeShopDetailsTask because: %s,No se pueden poner en espera los detalles de la actualización de la tienda porque: %s,UpgradeShopDetailsTask kann aus folgenden Gründen nicht in die Warteschlange gestellt werden: %s,"Mise en attente de la tâche ""Mise à jour des détails du Shop"" impossible car: %s",Impossibile richiamare UpgradeShopDetailsTask perché: %s +migrationLogMessages,deletingOldPluginDta.,Deleting old plugin data.,Eliminando datos antiguos del plugin.,Alte Plugin-Daten werden gelöscht.,Suppression des informations relatives à l'ancien plugin.,Eliminando i vecchi dati del plug-in +migrationLogMessages,cannotRetrieveOrderReferences,Cannot retrieve order references because: %s,No se pueden recuperar referencias de pedidos porque: %s,Bestellreferenzen können nicht abgerufen werden auf Grund von: %s,Récupération des références de commande impossible car: %s,Impossibile recuperare i riferimenti degli ordini perché: %s +migrationLogMessages,createOrderReferenceFailed,Failed to create reference for order %s,Error al crear la referencia para el pedido %s,Referenz für Auftrag %s konnte nicht erstellt werden.,Échec de création de référence pour la commande %s,Impossibile creare il riferimento per l'ordine %s +migrationLogMessages,setLabelsFailed,Failed to set labels for order with reference %s,Error al generar las etiquetas para el pedido con referencia %s,Versandetiketten für Bestellung mit Referenz %s konnten nicht erstellt werden.,Échec de la mise en place d'étiquettes pour la commande sous la référence %s,Impossibile impostare le etichette per l'ordine con riferimento %s +migrationLogMessages,setTrackingInfoFailed,Failed to set tracking info for order with reference %s,Error al generar la información de seguimiento para el pedido con la referencia %s,Fehler beim Festlegen der Tracking-Informationen für die Bestellung mit Referenz %s,Échec de la mise en place des informations de suivi pour la commande sous la référence %s,Impossibile impostare le informazioni di tracciamento per l'ordine con riferimento %s +migrationLogMessages,orderNotFound,Order with reference %s not found.,Pedido con referencia %s no encontrado.,Auftrag mit Referenz %s nicht gefunden.,La commande sous la référence %s introuvable.,Ordine con riferimento %s non trovato +systemLogMessages,errorCreatingDefaultTask,Error creating default task runner status configuration.,Error creating default task runner status configuration.,Fehler beim Erstellen der Standardkonfiguration des Task-Runner-Status.,Erreur lors de la création de la configuration du gestionnaire de tâches par défaut.,Error creating default task runner status configuration. +systemLogMessages,webhookReceived,Webhook from Packlink received.,Webhook de Packlink recibido,Webhook von Packlink erhalten,Webhook de Packlink reçu,Webhook di Packlink ricevuto +systemLogMessages,couldNotDelete,Could not delete entity %s with ID %s,No se pudo eliminar la entidad %s con ID %s,Element %s mit ID %s konnte nicht gelöscht werden,L'entité %s dont l'ID est %s n'a pas pu être supprimée.,Impossibile eliminare l'entità %s con ID %s +systemLogMessages,entityCannotBeSaved,Entity %s with ID %s cannot be saved.,La entidad %s con ID %s no se puede guardar.,Element %s mit ID %s kann nicht gespeichert werden.,L'entité %s dont l'ID est %s n'a pas pu être enregistrée.,L'entità %s con ID %s non può essere salvata. +systemLogMessages,entityCannotBeUpdated,Entity %s with ID %s cannot be updated.,La entidad %s con ID %s no se puede actualizar.,Element %s mit ID %s kann nicht aktualisiert werden.,L'entité %s dont l'ID est %s n'a pas pu être mise à jour.,L'entità %s con ID %s non può essere aggiornata. +systemLogMessages,fieldIsNotIndexed,Field %s is not indexed!,¡El campo %s no está indexado!,Feld %s ist nicht indiziert!,Le champs %s n'est pas indexé!,Il campo %s non è indicizzato! +systemLogMessages,unknownOrNotIndexed,Unknown or not indexed OrderBy column %s,Desconocido o no indexado OrderBy column %s,Unbekannte oder nicht indizierte OrderBy-Spalte %s,Inconnu ou non-indexé OrderBy column %s,Colonna OrderBy %s sconosciuta o non indicizzata +systemLogMessages,dirNotCreated,"Directory ""%s"" was not created","El directorio ""%s"" no fue creado","Das Verzeichnis ""%s"" wurde nicht erstellt","Annuaire ""%s"" n'a pas été créé","La directory ""%s"" non è stata creata" +systemLogMessages,failedCreatingBackup,Failed creating backup service.,Error al crear el servicio flexible,Fehler beim Erstellen des Backup-Versanddienstes.,Impossible de créer le service de flexibilité.,Impossibile creare il servizio jolly. +systemLogMessages,backupServiceNotFound,Backup service not found.,Servicio flexible no encontrado,Backup-Versanddienst konnte nicht gefunden werden.,Service de flexibilité introuvable,Servizio jolly non trovato. +autoTest,dbNotAccessible.,Database not accessible.,DNo se puede acceder a la base de datos.,Zugriff auf Datenbank nicht möglich.,Base de données inaccessible.,Database non accessibile. +autoTest,taskCouldNotBeStarted.,Task could not be started.,No se ha podido iniciar la tarea.,Die Aufgabe konnte nicht gestartet werden.,La tâche n’a pu être lancée.,Non è stato possibile avviare l’attività. +autoTest,moduleAutoTest,PacklinkPRO module auto-test,Prueba automática del módulo PacklinkPRO,Automatischer Test PacklinkPRO-Modul,Test automatique du module PacklinkPRO,Auto-test per il modulo di PacklinkPRO +autoTest,useThisPageTestConfiguration,Use this page to test the system configuration and PacklinkPRO module services.,Utiliza esta página para probar la configuración del sistema y los servicios del módulo PacklinkPRO.,Auf dieser Seite können Sie die Systemkonfiguration und die Dienste des PacklinkPRO-Moduls testen.,Utilisez cette page pour tester la configuration du système et les services du module PacklinkPRO.,Usa questa pagina per testare la configurazione del sistema e i servizi del modulo di PacklinkPRO. +autoTest,start,Start,Inicio,Start,Démarrer,Avvia +autoTest,downloadTestLog,Download test log,Descargar el registro de pruebas,Testprotokoll herunterladen,Télécharger le registre de tests,Scarica il registro test +autoTest,openModule,Open PacklinkPRO module,Abrir el módulo PacklinkPRO,PacklinkPRO-Modul öffnen,Ouvrir le module PacklinkPRO,Apri il modulo di PacklinkPRO +autoTest,autotestPassedSuccessfully,Auto-test passed successfully!,Prueba automática superada con éxito.,Der automatische Test wurde erfolgreich abgeschlossen!,Test automatique réussi avec succès !,L’auto-test è stato superato con successo! +autoTest,testNotSuccessful,The test did not complete successfully.,La prueba no se ha completado correctamente.,Der Test wurde nicht erfolgreich abgeschlossen!,Le test a échoué.,Il test non è stato completato correttamente. \ No newline at end of file diff --git a/src/BusinessLogic/Resources/scss/animations.scss b/src/BusinessLogic/Resources/scss/animations.scss new file mode 100644 index 00000000..b6a9ccef --- /dev/null +++ b/src/BusinessLogic/Resources/scss/animations.scss @@ -0,0 +1,6 @@ +// animations +@keyframes pl-rotate { + 100% { + transform: rotate(360deg); + } +} diff --git a/src/BusinessLogic/Resources/scss/app.scss b/src/BusinessLogic/Resources/scss/app.scss new file mode 100644 index 00000000..b780e9f9 --- /dev/null +++ b/src/BusinessLogic/Resources/scss/app.scss @@ -0,0 +1,473 @@ +@import "variables"; +@import "reset"; +@import "animations"; +@import "utility"; +@import "ui-controls"; +@import "icons"; + +#pl-page { + #pl-main-header { + height: 90px; + } + + .pl-onboarding-list { + width: 100%; + justify-content: flex-start; + } + + .pl-onboarding-overview-list { + width: 100%; + + .pl-list-item { + flex-direction: row; + align-items: center; + padding-bottom: 20px; + + i { + margin-right: 10px; + } + + & + .pl-list-item { + border-top: 1px solid $color-light; + padding-top: 20px; + } + + .pl-item-details { + text-align: left; + } + + .pl-onboarding-list-title { + color: $color-gray; + text-align: left; + } + } + } + + .pl-parcel-wrapper { + align-items: flex-start; + min-width: 100%; + } + + .pl-main-logo img { + width: 200px; + } + + .pl-register-country-list-wrapper { + margin: 15px 0 0; + min-width: 300px; + width: 100%; + display: flex; + flex-direction: column; + + .pl-country { + display: flex; + flex-direction: row; + align-items: center; + width: 100%; + padding: 5px 0; + color: $color-blue; + font-size: 15px; + text-transform: uppercase; + cursor: pointer; + flex-shrink: 0; + + &:hover { + background-color: $color-little-transparent; + + .pl-country-name { + color: $color-blue; + } + } + + .pl-country-logo { + width: 60px; + } + + .pl-country-name { + white-space: nowrap; + margin-left: 15px; + align-items: flex-start; + flex-grow: 1; + } + + img { + width: 35px; + height: 35px; + } + } + } + + .pl-login-footer { + padding: 24px 30px 32px; + background-color: $color-navy-dark; + color: $color-white; + flex-shrink: 0; + + * { + color: $color-white; + } + } + + .pl-onboarding-welcome-steps { + align-items: flex-start; + width: 100%; + + .pl-list-item { + padding: 20px 0; + width: 100%; + border-bottom: 1px solid $color-light; + + &:last-child { + border-bottom: none; + } + } + } + + .pl-configuration-menu { + padding: 5px 20px; + margin-right: -20px; + font-weight: bold; + + i { + margin-left: 10px; + } + } + + .pl-configuration-page { + justify-content: space-between; + + .pl-config-list { + flex-direction: column; + width: 300px; + + .pl-config-list-item { + border-bottom: 1px solid $border-light; + padding: 17px 0 17px 26px; + flex-direction: row; + align-items: center; + width: 100%; + + &:hover { + background-color: $border-light; + cursor: pointer; + } + + a { + display: flex; + flex-direction: row; + flex-grow: 1; + align-items: center; + } + } + } + + .pl-footer { + flex-direction: row; + justify-content: space-between; + padding: 20px; + + span { + color: $color-light-gray; + margin-right: 5px; + } + } + } + + .pl-page-onboarding { + .pl-page-buttons { + text-align: center; + } + } + + .pl-page-config { + width: 100%; + flex-grow: 1; + + header.pl-sub-header h1 { + text-align: left; + margin-left: 10px; + } + + .pl-small-page, .pl-medium-page { + margin-left: 0; + margin-right: 0; + } + + .pl-page-info { + text-align: left; + } + } + + .pl-status-mapping { + width: 100%; + flex-direction: row; + border-top: 1px solid $border-light; + border-bottom: 1px solid $border-light; + + &.pl-order-status-mappings-header { + border: none; + } + + .pl-half-width { + flex-direction: row; + max-width: 50%; + min-width: 50%; + align-items: center; + margin: 0; + padding: 5px 0; + + .pl-separation-indicator { + font-size: 32px; + margin-right: 36px; + } + } + } + + .pl-services-filter-wrapper { + flex-direction: row; + width: 100%; + justify-content: flex-start; + padding: 0 20px; + flex: 0 0 auto; + + .pl-filter:not(:last-child) { + margin-right: 32px; + } + } + + .pl-pick-shipping-services-page { + #pl-open-filter-button { + display: none; + } + } + + .pl-modal .pl-filters-wrapper { + width: 300px; + } + + .pl-filter { + padding: 20px 0; + + &.pl-filter-selected { + display: none; + } + + .pl-filter-title { + text-transform: uppercase; + margin-bottom: 12px; + text-align: left; + color: $text-heading-color; + font-weight: bold; + } + + .pl-filter-options { + flex-direction: row; + justify-content: flex-start; + + .pl-filter-option { + padding: 6px 13px; + border: 1px solid $border-color; + border-radius: 13px; + cursor: pointer; + color: $input-control-color; + margin-right: 12px; + + &.pl-selected { + border-color: $color-blue; + background-color: $color-blue; + color: $color-white; + } + } + } + } + + .pl-form-group { + &:not(:first-child) { + &.pl-change-percent-wrapper { + margin-top: 0; + } + } + } + + .pl-my-shipping-services-page { + header { + background-color: #1A77C2; + color: $color-white; + font-size: 18px; + font-weight: bold; + padding: 25px 21px; + } + + .pl-page-buttons { + display: none; + } + } + + .pl-no-services { + img { + height: 176px; + } + + .pl-no-services-description { + max-width: 420px; + margin-left: 60px; + text-align: left; + + p { + margin: 20px 0; + } + + button { + padding: 16px; + margin: 0; + max-width: 200px; + } + } + } + + .pl-country-checkbox-wrapper { + padding: 15px; + } + + .pl-shipping-country-selection-wrapper { + max-height: 320px; + overflow: auto; + + > * { + border-bottom: 1px solid $border-light; + } + } + + .pl-shipping-country-selected { + background-color: $border-light; + } + + #pl-countries-alert-wrapper { + position: relative; + display: none; + &.visible { + display: flex; + } + } +} + +// mobile rules +@media (max-width: 768px) { + #pl-page { + .pl-main-logo { + width: 100%; + } + + .pl-register-country-list-wrapper { + min-width: 100%; + } + + .pl-login-footer { + padding: 16px 20px; + } + + .pl-list-item .pl-row-orientation { + flex-direction: column; + } + + .pl-configuration-page { + .pl-config-list { + width: 100%; + + .pl-config-list-item { + padding-right: 15px; + padding-left: 20px; + } + } + + .pl-footer { + flex-direction: column; + + > :first-child { + padding-bottom: 20px; + } + } + } + + .pl-my-shipping-services-page { + .pl-no-services { + flex-direction: column; + justify-content: flex-start; + padding-bottom: 0; + + img { + max-height: 40%; + } + + .pl-no-services-description { + margin: 20px 0 0; + + h1 { + text-align: center; + } + + button { + display: none; + } + + p { + margin-bottom: 0; + } + } + } + + .pl-page-buttons { + display: flex; + } + } + } +} + +// tablet and mobile rules +@media (max-width: 1280px) { + #pl-page { + .pl-services-filter-wrapper { + padding: 0 10px; + + .pl-filter:not(.pl-filter-selected) { + display: none; + } + + .pl-filter-selected { + flex-direction: row; + display: flex; + padding: 0; + + .pl-filter-options { + flex-wrap: wrap; + } + + .pl-filter-option { + flex-direction: row; + + &.pl-selected { + margin-top: 10px; + } + + &::after { + content: 'X'; + margin-left: 5px; + margin-top: 1px; + } + } + } + } + + .pl-pick-shipping-services-page { + #pl-open-filter-button { + display: flex; + } + } + } +} + +// tablet rules +@media (min-width: 769px) and (max-width: 1280px) { + #pl-page { + } +} \ No newline at end of file diff --git a/src/BusinessLogic/Resources/scss/icons.scss b/src/BusinessLogic/Resources/scss/icons.scss new file mode 100644 index 00000000..25e42f64 --- /dev/null +++ b/src/BusinessLogic/Resources/scss/icons.scss @@ -0,0 +1,18 @@ +/* Material icons */ + +//noinspection CssNoGenericFontName +#pl-page .material-icons { + font-family: 'Material Icons Outlined'; + font-weight: normal; + font-style: normal; + font-size: 24px; + line-height: 1; + letter-spacing: normal; + text-transform: none; + display: inline-block; + white-space: nowrap; + word-wrap: normal; + direction: ltr; + -webkit-font-feature-settings: 'liga'; + -webkit-font-smoothing: antialiased; +} \ No newline at end of file diff --git a/src/BusinessLogic/Resources/scss/reset.scss b/src/BusinessLogic/Resources/scss/reset.scss new file mode 100644 index 00000000..0edf1c40 --- /dev/null +++ b/src/BusinessLogic/Resources/scss/reset.scss @@ -0,0 +1,129 @@ +@font-face { + font-weight: 700; + font-family: pl-proxima-nova; + font-style: normal; + src: url("https://cdn.packlink.com/apps/giger/fonts/ProximaNova/ProximaNova-BoldWeb.woff") format("woff") +} + +@font-face { + font-weight: 600; + font-family: pl-proxima-nova; + font-style: normal; + src: url("https://cdn.packlink.com/apps/giger/fonts/ProximaNova/ProximaNova-SemiboldWeb.woff") format("woff") +} + +@font-face { + font-weight: 400; + font-family: pl-proxima-nova; + font-style: normal; + src: url("https://cdn.packlink.com/apps/giger/fonts/ProximaNova/ProximaNova-RegularWeb.woff") format("woff") +} + +// CSS reset +// We do not reset the html and body, since we need to reset only for the app's root element +#pl-page { + display: flex; + flex-direction: column; + background-color: $color-white; + flex-grow: 1; + position: relative; + + template { + display: none; + } + + * { + position: relative; + margin: 0; + padding: 0; + font-family: pl-proxima-nova, sans-serif; + font-size: 14px; + line-height: 16px; + font-weight: 400; + font-style: normal; + color: $text-color; + box-sizing: border-box; + + scrollbar-color: $color-blue $border-color; + + &::-webkit-scrollbar { + height: 10px; + width: 10px; + } + + &::-webkit-scrollbar-thumb { + border-radius: 2px; + background-color: $color-blue; + } + + &::-webkit-scrollbar-track { + background-color: $border-color; + } + + &::before, &::after { + box-sizing: border-box; + } + } + + main { + background-color: $color-white; + } + + br { + margin-bottom: 10px; + } + + h1, h2, h3, h4, h5, h6 { + color: $text-heading-color; + font-weight: bold; + + br { + margin: 0; + } + } + + h1 { + line-height: 22px; + font-size: 18px; + } + + ul, ol { + list-style: none + } + + img { + height: auto; + max-width: 100% + } + + strong { + font-weight: 700; + font-style: normal; + } + + table { + border-collapse: collapse; + border-spacing: 0; + } + + a { + color: #1a77c2; + } + + a, :link, :visited { + text-decoration: none; + } + + div { + display: flex; + flex-direction: column; + } + + button, .pl-button, input, select, textarea { + outline: none; + + &:focus, &:active { + outline: none; + } + } +} diff --git a/src/BusinessLogic/Resources/scss/ui-controls.scss b/src/BusinessLogic/Resources/scss/ui-controls.scss new file mode 100644 index 00000000..4634edb5 --- /dev/null +++ b/src/BusinessLogic/Resources/scss/ui-controls.scss @@ -0,0 +1,747 @@ +#pl-page { + header { + border-bottom: 1px solid $border-color; + padding: 20px; + display: flex; + align-items: center; + flex-shrink: 0; + + .pl-header-holder { + justify-content: space-between; + flex-direction: row; + flex-grow: 1; + align-items: center; + margin-left: 20px; + + .pl-button { + padding: 10px 13px; + text-transform: none; + margin: 0; + } + } + + &.pl-sub-header { + border-color: $border-light; + flex-direction: row; + + button { + z-index: 1; + } + + h1 { + margin: 0 0 0 -$icon-button-size; + } + } + } + + > main { + display: flex; + flex-grow: 1; + overflow: auto; + } + + button, .pl-button { + overflow: visible; + display: inline-flex; + padding: 16px; + vertical-align: middle; + -webkit-appearance: none; + -moz-appearance: none; + cursor: pointer; + color: $color-blue; + font-style: normal; + font-weight: 600; + text-align: center; + text-decoration: none; + text-shadow: none; + text-transform: uppercase; + border: 1px solid transparent; + border-radius: 4px; + box-shadow: none; + transition: background .4s, border-color .4s, color .4s; + align-items: center; + justify-content: center; + margin: 10px 0 0; + height: auto; + min-width: 170px; + + &.pl-button-primary { + background-color: $color-blue; + color: $color-white; + } + + &.pl-button-secondary { + border: 1px solid $color-blue; + background-color: $color-white; + + &:hover { + border: 1px solid $color-blue-darker; + } + } + + &.pl-button-inverted { + background-color: $color-white; + color: $color-navy-dark; + } + + &:hover { + color: $color-white; + background: $color-blue-darker; + box-shadow: none; + } + + &:disabled { + color: $color-gray; + background-color: $color-light; + pointer-events: none; + } + + &.pl-small { + padding: 5px; + width: 100px; + min-width: 100px; + margin: 5px 0; + } + } + + .pl-icon-button { + border: none; + background: transparent; + padding: 0; + width: $icon-button-size; + height: $icon-button-size; + min-width: $icon-button-size; + margin: 0; + + i { + font-size: 20px; + width: auto; + color: $color-blue; + } + + &:focus { + outline: none; + } + + &:hover { + cursor: pointer; + + i { + color: $color-white; + } + } + } + + section { + border-bottom: 1px solid $border-light; + padding: 20px 0; + + &:first-child { + padding-top: 0; + } + + .pl-section-title { + font-weight: 600; + text-transform: uppercase; + display: block; + margin-bottom: 8px; + } + + .pl-section-subtitle { + display: block; + } + + .pl-button { + padding: 8px 12px + } + } + + .pl-form-group-wrapper { + display: flex; + flex-direction: row; + margin-top: 15px; + + .pl-form-group + .pl-form-group { + margin-top: 0; + margin-left: -1px; + } + } + + .pl-button-group { + flex-direction: row; + + button { + border-radius: 0; + + &:first-child { + border-radius: 4px 0 0 4px; + } + + &:last-child { + border-radius: 0 4px 4px 0; + } + } + } + + .pl-form-group { + position: relative; + border: none; + width: 100%; + align-items: start; + flex-shrink: 0; + + &.pl-half-width, .pl-half-width { + width: 50%; + min-width: 50%; + } + + &:not(:first-child) { + margin-top: 12px; + } + + label { + position: absolute; + top: 8px; + left: 16px; + color: $color-gray; + text-transform: uppercase; + font-size: 12px; + } + + input, select { + height: $input-height; + background: $color-white; + border-radius: 0; + border: 1px solid $border-color; + color: $input-control-color; + line-height: 22px; + font-size: 14px; + width: 100%; + margin: 0; + box-shadow: unset; + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + padding: 21px 15px 13px; + max-width: none; + + &.compact { + padding-top: 13px; + } + + &:focus { + border: 1px solid $color-blue; + outline: none; + } + + &::placeholder, &::-webkit-input-placeholder { + color: #CBCBCB; + } + } + + select { + -moz-padding-start: 11px; + } + + i { + position: absolute; + right: 10px; + top: $input-height/2; + margin-top: (-1 * $icon-button-size / 2); + color: $border-color; + width: $icon-button-size; + height: $icon-button-size; + } + + select + i { + cursor: default; + pointer-events: none; + } + + .pl-autocomplete-list { + background-color: $color-white; + border: 1px solid $border-color; + width: 100%; + margin-top: $input-height; + position: absolute; + z-index: 1; + max-height: 200px; + overflow: auto; + + .pl-autocomplete-list-item { + width: 100%; + padding: 5px; + font-size: 15px; + cursor: pointer; + + &:hover, &.pl-focus { + background-color: $color-little-transparent; + color: $color-blue; + } + } + } + } + + input[type=checkbox] { + -webkit-appearance: none; + -moz-appearance: none; + appearance: none; + width: $checkbox-size; + min-width: $checkbox-size; + height: $checkbox-size; + border: 1px solid $border-color; + position: relative; + + ~ label { + margin-left: 10px; + } + + &:hover { + border-color: $color-blue; + } + + &:checked { + background-color: $color-blue; + border-color: $color-blue; + text-align: center; + + &:before { + color: $color-white; + content: '\2713'; + margin: 0; + font: bold 20px/24px pl-proxima-nova; + width: $checkbox-size; + min-width: $checkbox-size; + height: $checkbox-size; + } + } + } + + .pl-alert-wrapper { + position: absolute; + width: 100%; + padding: 10px; + } + + .pl-alert { + position: relative; + cursor: pointer; + padding: .75rem 1.25rem; + margin: auto; + border: 1px solid transparent; + border-radius: .25rem; + flex-direction: row; + align-items: center; + + &.pl-alert-danger { + color: $color-red; + background-color: #f8d7da; + border-color: #f5c6cb; + } + + &.pl-alert-success { + color: #155724; + background-color: #d4edda; + border-color: #c3e6cb; + } + + .pl-alert-text { + color: inherit; + } + + i { + color: inherit; + margin-left: 20px; + } + } + + .pl-checkbox { + margin-top: 15px; + display: flex; + flex-direction: row; + + label { + cursor: pointer; + text-align: left; + align-items: center; + display: flex; + } + + &.pl-error { + .pl-error-message { + display: none; + } + } + } + + .pl-switch { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + + .pl-switch-button { + cursor: pointer; + + .pl-switch-on, .pl-switch-off { + font-size: $input-height; + } + + .pl-switch-on { + color: $color-blue; + display: none; + } + + &.pl-selected { + .pl-switch-on { + display: block; + } + + .pl-switch-off { + display: none; + } + } + } + } + + .pl-spinner { + position: absolute; + z-index: 500; + background-color: $color-semi-transparent; + display: flex; + justify-content: center; + align-items: center; + top: 0; + left: 0; + right: 0; + bottom: 0; + + div { + width: 100px; + height: 100px; + border: 3px solid transparent; + border-right-color: $color-blue; + border-left-color: $color-blue; + border-radius: 50%; + animation: pl-rotate 1s linear 0s infinite; + } + } + + .pl-modal-mask { + position: absolute; + top: 0; + bottom: 0; + left: 0; + right: 0; + background-color: $color-little-transparent; + z-index: 100; + display: flex; + align-items: center; + justify-content: center; + + .pl-modal { + background: $color-white; + display: flex; + max-height: 90%; + max-width: 90%; + flex-flow: column; + align-items: center; + position: relative; + margin: 0 auto; + box-shadow: 0 4px 4px 0 rgba(0, 0, 0, 0.2); + + .pl-modal-close-button { + position: absolute; + width: 24px; + height: 24px; + top: 15px; + right: 12px; + font-size: 3rem; + font-weight: 600; + color: $color-blue; + cursor: pointer; + z-index: 1; + + i { + color: $color-blue; + font-weight: bold; + } + } + + .pl-modal-title { + color: $text-heading-color; + font-size: 21px; + font-weight: bold; + line-height: 22px; + text-align: center; + padding: 20px 40px 0; + } + + .pl-modal-subtitle { + color: $text-heading-light-color; + font-size: 16px; + line-height: 19px; + } + + .pl-modal-body { + padding: 20px 40px; + align-items: flex-start; + justify-content: flex-start; + width: 100%; + overflow: hidden auto; + + &.pl-full-width { + padding: 20px 0; + } + } + + .pl-modal-footer { + padding: 0 40px 20px; + display: flex; + flex-direction: row; + justify-content: center; + width: 100%; + flex-shrink: 0; + + .pl-button { + margin-top: 0; + + & + .pl-button { + margin-left: 10px; + } + } + } + } + } + + .pl-error { + input, select { + border: 1px solid $color-red !important; + } + + label, i { + color: $color-red; + } + + .pl-error-message { + margin: 8px 16px 0; + color: $color-red; + } + } + + .pl-shipping-services-table { + width: 100%; + + thead tr { + th { + background-color: $border-light; + z-index: 1; + + .pl-table-resize-handle { + width: 20px; + cursor: col-resize; + position: absolute; + right: -11px; + top: 50%; + transform: rotate(90deg); + font-size: 20px; + transform-origin: top; + color: $color-gray; + z-index: 2; + } + } + } + + th { + font-size: 12px; + font-weight: bold; + text-transform: uppercase; + position: sticky; + top: -1px; + + &.pl-text-center { + text-align: center; + } + } + + td { + font-weight: 600; + + .pl-service-name { + font-weight: 600; + margin-bottom: 10px; + } + } + + tr { + border-top: solid 1px $border-light; + border-bottom: solid 1px $border-light; + } + + th, td { + padding: 15px 20px; + text-align: left; + } + } + + .pl-shipping-services-list { + display: none; + } + + .pl-shipping-services-list-item { + border: 1px solid $border-color; + border-radius: 4px; + background-color: $color-white; + box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.1); + flex-direction: column; + margin-bottom: 10px; + padding: 13px; + + .pl-service-header { + border-bottom: 1px dashed $border-color; + flex-direction: row; + padding-bottom: 13px; + + .pl-service-name { + flex-grow: 1; + padding-left: 15px; + align-items: flex-start; + justify-content: center; + + #pl-service-name { + font-weight: 600; + margin-bottom: 8px; + } + } + } + + .pl-wrap-items { + flex-direction: row; + justify-content: space-between; + padding: 13px 0 0; + + .pl-service-property { + min-width: 170px; + margin-bottom: 13px; + + .pl-property-title { + font-weight: 600; + text-transform: uppercase; + color: $color-gray; + font-size: 12px; + letter-spacing: 0; + line-height: 16px; + } + + .pl-property-value { + color: $input-control-color; + } + } + } + + .pl-service-buttons { + flex-direction: row; + + .pl-button { + padding: 5px 15px; + min-width: auto; + } + + .pl-button:not(.pl-hidden) + .pl-button { + margin-left: 10px; + } + } + } + + .pl-button-group { + padding-right: 10px; + + button { + height: $input-height; + min-width: 110px; + } + } + + pre { + white-space: normal; + } +} + +// mobile rules +@media (max-width: 768px) { + #pl-page { + button, .pl-button { + width: 100%; + } + + section { + .pl-button { + width: auto; + } + } + + header .pl-header-holder { + justify-content: flex-end; + + .pl-button { + display: none; + } + } + + .pl-shipping-services-list-item { + .pl-wrap-items .pl-service-property { + min-width: 50%; + } + + .pl-button { + width: 100%; + + &.pl-small { + padding: 11px; + } + } + } + + .pl-modal-mask { + .pl-modal { + .pl-modal-title { + padding: 20px 20px 0; + max-width: 90%; + } + + .pl-modal-body { + padding: 20px; + } + + .pl-modal-footer { + padding: 0 20px 20px; + + .pl-button { + min-width: 50%; + } + } + } + } + } +} + +// mobile and tablet rules +@media (max-width: 1280px) { + #pl-page { + .pl-shipping-services-table { + display: none; + } + + .pl-shipping-services-list { + display: flex; + flex: 0 0 auto; + padding: 10px; + } + } +} + +// tablet rules +@media (min-width: 769px) and (max-width: 1280px) { + #pl-page { + } +} diff --git a/src/BusinessLogic/Resources/scss/utility.scss b/src/BusinessLogic/Resources/scss/utility.scss new file mode 100644 index 00000000..ed0645f1 --- /dev/null +++ b/src/BusinessLogic/Resources/scss/utility.scss @@ -0,0 +1,248 @@ +#pl-page { + .pl-inline { + display: inline; + } + + .pl-full-width { + width: 100%; + } + + .pl-half-width { + display: inline-flex; + width: 50%; + } + + .pl-small-page, .pl-medium-page { + margin-left: auto; + margin-right: auto; + } + + .pl-medium-page { + max-width: 540px; + } + + .pl-small-page { + max-width: 410px; + } + + .pl-small-width { + max-width: 410px; + } + + .pl-hidden { + display: none !important; + } + + .pl-pull-left { + float: left; + } + + .pl-text-center { + text-align: center; + } + + .pl-text-left { + text-align: left; + } + + .pl-separate-vertically { + margin-top: 26px; + margin-bottom: 26px; + } + + .pl-separate-top-small { + margin-top: 2px; + } + + .pl-separate-horizontally { + margin-left: 26px; + margin-right: 26px; + } + + .pl-top-separate { + margin-top: 20px; + } + + .pl-bottom-separate { + margin-bottom: 36px; + } + + .pl-left-separate { + margin-left: 20px; + } + + .pl-right-separate { + margin-right: 20px; + } + + .pl-page-content { + overflow: hidden auto; + margin-bottom: 10px; + } + + .pl-page-padding { + padding: 20px; + } + + .pl-flex-expand { + flex-grow: 1; + } + + .pl-no-shrink { + flex-shrink: 0; + } + + .pl-row-orientation { + display: flex; + flex-direction: row; + align-items: center; + } + + .pl-flex-left { + align-items: flex-start; + justify-content: flex-start; + } + + .pl-justify-f-start { + justify-content: flex-start; + } + + .pl-separate-content { + justify-content: space-between; + } + + .pl-no-margin { + margin: 0 !important; + } + + .pl-no-border { + border-color: transparent !important; + } + + .pl-center { + display: flex; + justify-content: center; + align-items: center; + text-align: center; + } + + #pl-origin-collection svg { + width: 24px; + height: 23px; + } + + #pl-origin-dropoff svg { + width: 25px; + height: 23px; + } + + #pl-destination-pickup svg { + width: 24px; + height: 22px; + } + + #pl-destination-delivery svg { + width: 27px; + height: 22px; + } + + #pl-navigate-warehouse svg { + width: 22px; + height: 22px; + } + + #pl-navigate-parcel svg { + width: 23px; + height: 22px; + } + + .pl-flex-start { + align-items: flex-start; + } + + .pl-wrap-items { + flex-wrap: wrap; + } + + .pl-header-text-wrapper { + line-height: 22px; + font-size: 22px; + } + + .pl-small { + padding: 5px; + width: 100px; + min-width: 100px; + margin: 0; + } + + .pl-no-wrap { + white-space: nowrap; + width: auto; + } + + .pl-page-buttons { + display: block; + //text-align: center; + padding: 0 20px 20px; + } + + .pl-error-text { + color: $color-red; + } + + .pl-icon-text { + color: $color-blue; + } + + .pl-clickable { + cursor: pointer; + } + + .pl-all-caps { + text-transform: uppercase; + } + + .pl-block { + display: block; + } + + .pl-split-content { + justify-content: space-between; + flex-direction: row; + } + + &.pl-disable-selection { + -moz-user-select: none; /* Firefox */ + -ms-user-select: none; /* Internet Explorer */ + -khtml-user-select: none; /* KHTML browsers (e.g. Konqueror) */ + -webkit-user-select: none; /* Chrome, Safari, and Opera */ + -webkit-touch-callout: none; /* Disable Android and iOS callouts*/ + } +} + +// mobile rules +@media (max-width: 768px) { + #pl-page { + .pl-desktop-only { + display: none !important; + } + + .pl-page-content { + margin-bottom: 100px; + } + + .pl-page-buttons { + position: absolute; + bottom: 0; + width: 100%; + padding: 20px; + background-color: $color-white; + } + + .pl-medium-page, .pl-small-page { + width: 100%; + max-width: 100%; + } + } +} diff --git a/src/BusinessLogic/Resources/scss/variables.scss b/src/BusinessLogic/Resources/scss/variables.scss new file mode 100644 index 00000000..eb5a492a --- /dev/null +++ b/src/BusinessLogic/Resources/scss/variables.scss @@ -0,0 +1,23 @@ +$text-color: #3a3d46; +$input-control-color: #5d5d5d; +$text-heading-color: #5E6972; +$text-heading-light-color: #737373; + +$border-color: #d7d7d7; +$border-light: #F5F5F5; + +$color-white: #ffffff; +$color-blue: #2095f2; +$color-blue-darker: rgba(32, 149, 242, .7); +$color-navy-dark: #2E4265; +$color-gray: #9B9B9B; +$color-light-gray: #717C87; +$color-light: #E1E1E1; +$color-red: #e63f3f; + +$color-little-transparent: rgba(216, 216, 216, 0.8); +$color-semi-transparent: rgba(255, 255, 255, 0.5); + +$icon-button-size: 24px; +$input-height: 56px; +$checkbox-size: 20px; diff --git a/src/BusinessLogic/Resources/templates/configuration.html b/src/BusinessLogic/Resources/templates/configuration.html new file mode 100644 index 00000000..0c7ef492 --- /dev/null +++ b/src/BusinessLogic/Resources/templates/configuration.html @@ -0,0 +1,59 @@ +
+
+
+ +

{$configuration.menu}

+
+
+
+ fact_check +
+ {$configuration.orderStatus} +
+ chevron_right +
+
+ + + +
+ {$configuration.warehouse} +
+ chevron_right +
+
+ + + + + +
+ {$configuration.parcel} +
+ chevron_right +
+ +
+
+ +
diff --git a/src/BusinessLogic/Resources/templates/countries-selection-modal.html b/src/BusinessLogic/Resources/templates/countries-selection-modal.html new file mode 100644 index 00000000..a7c267e4 --- /dev/null +++ b/src/BusinessLogic/Resources/templates/countries-selection-modal.html @@ -0,0 +1,21 @@ +
+
+
+ {$shippingServices.selectCountriesSubheader} +
+
+ {$shippingServices.atLeastOneCountry} + close +
+
+
+
+ + +
+
+
+
+
\ No newline at end of file diff --git a/src/BusinessLogic/Resources/templates/default-parcel.html b/src/BusinessLogic/Resources/templates/default-parcel.html new file mode 100644 index 00000000..f4ff6852 --- /dev/null +++ b/src/BusinessLogic/Resources/templates/default-parcel.html @@ -0,0 +1,48 @@ +
+
+ +

+
+
+
+

+

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+
+ +
+
diff --git a/src/BusinessLogic/Resources/templates/default-warehouse.html b/src/BusinessLogic/Resources/templates/default-warehouse.html new file mode 100644 index 00000000..4899c44d --- /dev/null +++ b/src/BusinessLogic/Resources/templates/default-warehouse.html @@ -0,0 +1,78 @@ +
+
+ +

+

+
+
+
+

+

+
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + expand_more + +
+
+ + + search +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+
+ +
+
diff --git a/src/BusinessLogic/Resources/templates/disable-carriers-modal.html b/src/BusinessLogic/Resources/templates/disable-carriers-modal.html new file mode 100644 index 00000000..d8420625 --- /dev/null +++ b/src/BusinessLogic/Resources/templates/disable-carriers-modal.html @@ -0,0 +1,5 @@ +
+ +

{$shippingServices.disableCarriersTitle}

+

{$shippingServices.disableCarriersDescription}

+
diff --git a/src/BusinessLogic/Resources/templates/edit-shipping-service.html b/src/BusinessLogic/Resources/templates/edit-shipping-service.html new file mode 100644 index 00000000..ef3e0a54 --- /dev/null +++ b/src/BusinessLogic/Resources/templates/edit-shipping-service.html @@ -0,0 +1,95 @@ +
+
+ +

{$shippingServices.configureService}

+
+
+
+
+
+ {$shippingServices.serviceTitle} +
+
+ {$shippingServices.serviceTitleDescription} +
+
+ + +
+
+ + +
+
+
+
+ {$shippingServices.pricePolicy} +
+
+ {$shippingServices.pricePolicyDescription} +
+
+ +
+ toggle_on + toggle_off +
+
+
+
+
+
+
+ {$shippingServices.firstPricePolicyDescription} +
+ + +
+
+
+ {$shippingServices.taxClassTitle} +
+
+ + expand_more + +
+
+
+
+ {$shippingServices.serviceCountriesTitle} +
+
+ {$shippingServices.serviceCountriesDescription} +
+
+ + + {$shippingServices.allCountriesSelected} + +
+
+
+
+
+ +
+
diff --git a/src/BusinessLogic/Resources/templates/login.html b/src/BusinessLogic/Resources/templates/login.html new file mode 100644 index 00000000..77866b4b --- /dev/null +++ b/src/BusinessLogic/Resources/templates/login.html @@ -0,0 +1,36 @@ +
+
+
+

+ {$login.welcome} +

+

+ {$login.connectYourService} +

+
+
+
+ + +
+ +
+
+ +
diff --git a/src/BusinessLogic/Resources/templates/my-shipping-services.html b/src/BusinessLogic/Resources/templates/my-shipping-services.html new file mode 100644 index 00000000..8be90763 --- /dev/null +++ b/src/BusinessLogic/Resources/templates/my-shipping-services.html @@ -0,0 +1,25 @@ +
+
+ {$shippingServices.myServices} +
+
+
+ +
+

{$shippingServices.noServicesTitle}

+

{$shippingServices.noServicesDescription}

+ +
+
+
+
+
+
+
+
+ +
+
diff --git a/src/BusinessLogic/Resources/templates/onboarding-overview.html b/src/BusinessLogic/Resources/templates/onboarding-overview.html new file mode 100644 index 00000000..01c226fe --- /dev/null +++ b/src/BusinessLogic/Resources/templates/onboarding-overview.html @@ -0,0 +1,41 @@ +
+
+
+

+ {$onboardingOverview.header} +

+
+
+
+ check +
+
+
{$onboardingOverview.parcelDetails}
+
+
+ +
+
+
+ check +
+
+
{$onboardingOverview.senderAddress}
+
+
+ +
+
+
+
+
+ +
+
diff --git a/src/BusinessLogic/Resources/templates/onboarding-welcome.html b/src/BusinessLogic/Resources/templates/onboarding-welcome.html new file mode 100644 index 00000000..cd18f66c --- /dev/null +++ b/src/BusinessLogic/Resources/templates/onboarding-welcome.html @@ -0,0 +1,26 @@ +
+
+

+ {$onboardingWelcome.header} +

+

+ {$onboardingWelcome.steps} +

+
+
+
+ 1. {$onboardingWelcome.stepOne} +
+
+ 2. {$onboardingWelcome.stepTwo} +
+
+
+
+
+ +
+
diff --git a/src/BusinessLogic/Resources/templates/order-status-mapping.html b/src/BusinessLogic/Resources/templates/order-status-mapping.html new file mode 100644 index 00000000..4f46b3e5 --- /dev/null +++ b/src/BusinessLogic/Resources/templates/order-status-mapping.html @@ -0,0 +1,42 @@ +
+
+ +

{$orderStatusMapping.title}

+
+
+
+

+ {$orderStatusMapping.description} +

+
+
+
+ {$orderStatusMapping.packlinkProShipmentStatus} +
+
+ {$orderStatusMapping.systemOrderStatus} +
+
+
+
+
+
+ +
+
+ \ No newline at end of file diff --git a/src/BusinessLogic/Resources/templates/pick-shipping-services.html b/src/BusinessLogic/Resources/templates/pick-shipping-services.html new file mode 100644 index 00000000..98b4ecbc --- /dev/null +++ b/src/BusinessLogic/Resources/templates/pick-shipping-services.html @@ -0,0 +1,76 @@ +
+
+ +
+

{$shippingServices.pickShippingService}

+ +
+
+
+
+
+
+ {$shippingServices.type} +
+
+
+ {$shippingServices.national} +
+
+ {$shippingServices.international} +
+
+
+
+
+ {$shippingServices.deliveryType} +
+
+
+ {$shippingServices.economic} +
+
+ {$shippingServices.express} +
+
+
+
+
+ {$shippingServices.parcelOrigin} +
+
+
+ {$shippingServices.collection} +
+
+ {$shippingServices.dropoff} +
+
+
+
+
+ {$shippingServices.parcelDestination} +
+
+
+ {$shippingServices.pickup} +
+
+ {$shippingServices.delivery} +
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/BusinessLogic/Resources/templates/pricing-policies-list.html b/src/BusinessLogic/Resources/templates/pricing-policies-list.html new file mode 100644 index 00000000..ec8cf30a --- /dev/null +++ b/src/BusinessLogic/Resources/templates/pricing-policies-list.html @@ -0,0 +1,24 @@ +
+ +
+ \ No newline at end of file diff --git a/src/BusinessLogic/Resources/templates/pricing-policy-modal.html b/src/BusinessLogic/Resources/templates/pricing-policy-modal.html new file mode 100644 index 00000000..afb17037 --- /dev/null +++ b/src/BusinessLogic/Resources/templates/pricing-policy-modal.html @@ -0,0 +1,101 @@ +
+
+
+
+
+ {$shippingServices.rangeTypeExplanation} +
+
+ + expand_more + +
+
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+ {$shippingServices.pricePolicyExplanation} +
+
+ + expand_more + +
+
+
{$shippingServices.increaseExplanation}
+
{$shippingServices.reduceExplanation}
+
+
+ + +
+ +
+ + +
+
+
+
+
+ + +
+
+
+
+
\ No newline at end of file diff --git a/src/BusinessLogic/Resources/templates/register-modal.html b/src/BusinessLogic/Resources/templates/register-modal.html new file mode 100644 index 00000000..e0cdf2ac --- /dev/null +++ b/src/BusinessLogic/Resources/templates/register-modal.html @@ -0,0 +1,13 @@ +
+ + search +
+
+ +
diff --git a/src/BusinessLogic/Resources/templates/register.html b/src/BusinessLogic/Resources/templates/register.html new file mode 100644 index 00000000..d8b78621 --- /dev/null +++ b/src/BusinessLogic/Resources/templates/register.html @@ -0,0 +1,92 @@ +
+
+
+

+ {$register.startEnjoying} +

+

+ {$register.registerAccount} +

+
+
+
+
+ + +
+
+ + +
+

+ {$register.getToKnowYou} +

+
+ + expand_more + +
+
+ + +
+
+
+ +
+ + +
+ + +
+
+
+ +
diff --git a/src/BusinessLogic/Resources/templates/shipping-services-header.html b/src/BusinessLogic/Resources/templates/shipping-services-header.html new file mode 100644 index 00000000..e9b67737 --- /dev/null +++ b/src/BusinessLogic/Resources/templates/shipping-services-header.html @@ -0,0 +1,4 @@ + +
+ {$configuration.menu} settings +
\ No newline at end of file diff --git a/src/BusinessLogic/Resources/templates/shipping-services-list.html b/src/BusinessLogic/Resources/templates/shipping-services-list.html new file mode 100644 index 00000000..45246ebf --- /dev/null +++ b/src/BusinessLogic/Resources/templates/shipping-services-list.html @@ -0,0 +1,47 @@ +
+ +
+ \ No newline at end of file diff --git a/src/BusinessLogic/Resources/templates/shipping-services-table.html b/src/BusinessLogic/Resources/templates/shipping-services-table.html new file mode 100644 index 00000000..2aea3fac --- /dev/null +++ b/src/BusinessLogic/Resources/templates/shipping-services-table.html @@ -0,0 +1,108 @@ + + + + + + + + + + + + + + + +
+ {$shippingServices.carrier} + + {$shippingServices.serviceTitle} + + {$shippingServices.transitTime} + + {$shippingServices.type} + + {$shippingServices.origin} + + {$shippingServices.destination} +
+ \ No newline at end of file diff --git a/src/BusinessLogic/Resources/templates/system-info-modal.html b/src/BusinessLogic/Resources/templates/system-info-modal.html new file mode 100644 index 00000000..d098f03d --- /dev/null +++ b/src/BusinessLogic/Resources/templates/system-info-modal.html @@ -0,0 +1,13 @@ +
+
+ + +
+
+ + + +
+
\ No newline at end of file diff --git a/src/BusinessLogic/Scheduler/ScheduleCheckTask.php b/src/BusinessLogic/Scheduler/ScheduleCheckTask.php index 164ff1fd..9a7bbf0b 100644 --- a/src/BusinessLogic/Scheduler/ScheduleCheckTask.php +++ b/src/BusinessLogic/Scheduler/ScheduleCheckTask.php @@ -69,6 +69,7 @@ public function execute() try { $latestTask = $queueService->findLatestByType($task->getType(), $schedule->getContext()); if ($latestTask + && $schedule->isRecurring() && in_array($latestTask->getStatus(), array(QueueItem::QUEUED, QueueItem::IN_PROGRESS), true) ) { // do not enqueue task if it is already scheduled for execution diff --git a/src/BusinessLogic/ShippingMethod/Exceptions/FixedPriceValueOutOfBoundsException.php b/src/BusinessLogic/ShippingMethod/Exceptions/FixedPriceValueOutOfBoundsException.php deleted file mode 100644 index 5c5544d8..00000000 --- a/src/BusinessLogic/ShippingMethod/Exceptions/FixedPriceValueOutOfBoundsException.php +++ /dev/null @@ -1,14 +0,0 @@ -from = $from; - $this->to = $to; - $this->amount = $amount; - } - - /** - * Transforms raw array data to this entity instance. - * - * @param array $data Raw array data. - * - * @return static Transformed entity object. - */ - public static function fromArray($data) - { - return new static($data['from'], $data['to'], $data['amount']); - } - - /** - * Transforms entity to its array format representation. - * - * @return array Entity in array format. - */ - public function toArray() - { - return array( - 'from' => round($this->from, 2), - 'to' => round($this->to, 2), - 'amount' => round($this->amount, 2), - ); - } -} diff --git a/src/BusinessLogic/ShippingMethod/Models/PercentPricePolicy.php b/src/BusinessLogic/ShippingMethod/Models/PercentPricePolicy.php deleted file mode 100644 index e9faf23d..00000000 --- a/src/BusinessLogic/ShippingMethod/Models/PercentPricePolicy.php +++ /dev/null @@ -1,62 +0,0 @@ -increase = $increase; - $this->amount = $amount; - } - - /** - * Transforms raw array data to this entity instance. - * - * @param array $data Raw array data. - * - * @return static Transformed entity object. - */ - public static function fromArray($data) - { - return new static((bool)$data['increase'], $data['amount']); - } - - /** - * Transforms entity to its array format representation. - * - * @return array Entity in array format. - */ - public function toArray() - { - return array( - 'increase' => $this->increase, - 'amount' => $this->amount, - ); - } -} diff --git a/src/BusinessLogic/ShippingMethod/Models/ShippingMethod.php b/src/BusinessLogic/ShippingMethod/Models/ShippingMethod.php index fdded610..ff4efd47 100644 --- a/src/BusinessLogic/ShippingMethod/Models/ShippingMethod.php +++ b/src/BusinessLogic/ShippingMethod/Models/ShippingMethod.php @@ -5,7 +5,7 @@ use Logeecom\Infrastructure\ORM\Configuration\EntityConfiguration; use Logeecom\Infrastructure\ORM\Configuration\IndexMap; use Logeecom\Infrastructure\ORM\Entity; -use Packlink\BusinessLogic\ShippingMethod\Validation\PricingPolicyValidator; +use Packlink\BusinessLogic\DTO\FrontDtoFactory; /** * This class represents shipping service from Packlink with specific data for integration. @@ -18,22 +18,6 @@ class ShippingMethod extends Entity * Fully qualified name of this class. */ const CLASS_NAME = __CLASS__; - /** - * Indicates that Packlink pricing policy is used. - */ - const PRICING_POLICY_PACKLINK = 1; - /** - * Indicates that percent from Packlink price pricing policy is used. - */ - const PRICING_POLICY_PERCENT = 2; - /** - * Indicates that fixed price by weight range pricing policy is used. - */ - const PRICING_POLICY_FIXED_PRICE_BY_WEIGHT = 3; - /** - * Indicates that fixed price by value range pricing policy is used. - */ - const PRICING_POLICY_FIXED_PRICE_BY_VALUE = 4; /** * Array of field names. * @@ -52,7 +36,7 @@ class ShippingMethod extends Entity 'expressDelivery', 'deliveryTime', 'national', - 'pricingPolicy', + 'usePacklinkPriceIfNotInRange', 'taxClass', 'isShipToAllCountries', 'shippingCountries', @@ -124,29 +108,17 @@ class ShippingMethod extends Entity */ protected $national = false; /** - * Pricing policy used. Defaults to @see self::PRICING_POLICY_PACKLINK. + * An array of pricing policies used. * - * @var int - */ - protected $pricingPolicy = self::PRICING_POLICY_PACKLINK; - /** - * Array of fixed price by weight policy data. - * - * @var FixedPricePolicy[] - */ - protected $fixedPriceByWeightPolicy; - /** - * Array of fixed price by value policy data. - * - * @var FixedPricePolicy[] + * @var array */ - protected $fixedPriceByValuePolicy; + protected $pricingPolicies = array(); /** - * Percent price policy data. + * Indicates whether to use Packlink prices when none of the set pricing policies is in range. * - * @var PercentPricePolicy + * @var bool */ - protected $percentPricePolicy; + protected $usePacklinkPriceIfNotInRange = true; /** * Shop tax class. * @@ -160,13 +132,13 @@ class ShippingMethod extends Entity */ protected $shippingServices = array(); /** - * Flag that denotes whether is shipping to all countries allowed. + * Flag that denotes whether is shipping to all countries selected. * * @var boolean */ protected $isShipToAllCountries; /** - * If `isShipToAllCountries` set to FALSe than this array contains list of countries where shipping is allowed. + * If `isShipToAllCountries` set to FALSE, then this array contains list of countries where shipping is allowed. * * @var array */ @@ -176,11 +148,19 @@ class ShippingMethod extends Entity * Transforms raw array data to this entity instance. * * @param array $data Raw array data. + * + * @throws \Packlink\BusinessLogic\DTO\Exceptions\FrontDtoNotRegisteredException */ public function inflate(array $data) { parent::inflate($data); + if (isset($data['usePacklinkPriceIfNotInRange']) && is_bool($data['usePacklinkPriceIfNotInRange'])) { + $this->usePacklinkPriceIfNotInRange = $data['usePacklinkPriceIfNotInRange']; + } else { + $this->usePacklinkPriceIfNotInRange = true; + } + if (isset($data['isShipToAllCountries']) && is_bool($data['isShipToAllCountries'])) { $this->isShipToAllCountries = $data['isShipToAllCountries']; } else { @@ -193,20 +173,11 @@ public function inflate(array $data) $this->shippingCountries = array(); } - if (!$this->getPricingPolicy()) { - $this->pricingPolicy = static::PRICING_POLICY_PACKLINK; - } - - if (!empty($data['fixedPriceByWeightPolicy'])) { - $this->setFixedPriceByWeightPolicy($this->inflateFixedPricePolicy($data, 'fixedPriceByWeightPolicy')); - } - - if (!empty($data['fixedPriceByValuePolicy'])) { - $this->setFixedPriceByValuePolicy($this->inflateFixedPricePolicy($data, 'fixedPriceByValuePolicy')); - } - - if (!empty($data['percentPricePolicy'])) { - $this->setPercentPricePolicy(PercentPricePolicy::fromArray($data['percentPricePolicy'])); + if (isset($data['pricingPolicies'])) { + $this->pricingPolicies = FrontDtoFactory::getFromBatch( + ShippingPricePolicy::CLASS_KEY, + $data['pricingPolicies'] + ); } if (!empty($data['shippingServices'])) { @@ -225,22 +196,12 @@ public function toArray() { $data = parent::toArray(); - if ($this->fixedPriceByWeightPolicy) { - foreach ($this->fixedPriceByWeightPolicy as $fixedPricePolicy) { - $data['fixedPriceByWeightPolicy'][] = $fixedPricePolicy->toArray(); - } - } - - if ($this->fixedPriceByValuePolicy) { - foreach ($this->fixedPriceByValuePolicy as $fixedPricePolicy) { - $data['fixedPriceByValuePolicy'][] = $fixedPricePolicy->toArray(); + if ($this->pricingPolicies) { + foreach ($this->pricingPolicies as $policy) { + $data['pricingPolicies'][] = $policy->toArray(); } } - if ($this->percentPricePolicy) { - $data['percentPricePolicy'] = $this->percentPricePolicy->toArray(); - } - if ($this->shippingServices) { foreach ($this->shippingServices as $service) { $data['shippingServices'][] = $service->toArray(); @@ -526,108 +487,31 @@ public function addShippingService($shippingService) } /** - * Gets pricing policy. Value is one of the @see self::PRICING_POLICY_FIXED, @see self::PRICING_POLICY_PERCENT - * of @see self::PRICING_POLICY_PACKLINK. + * Sets new pricing policy. * - * @return int Pricing policy code. + * @param ShippingPricePolicy $policy */ - public function getPricingPolicy() + public function addPricingPolicy(ShippingPricePolicy $policy) { - return $this->pricingPolicy; - } - - /** - * Sets data for fixed price by weight policy. - * - * @param FixedPricePolicy[] $fixedPricePolicy - * - * @throws \InvalidArgumentException - */ - public function setFixedPriceByWeightPolicy($fixedPricePolicy) - { - $fixedPricePolicy = $this->sortFixedPricePolicy($fixedPricePolicy); - - PricingPolicyValidator::validateFixedPricePolicy($fixedPricePolicy); - - $this->percentPricePolicy = null; - $this->fixedPriceByWeightPolicy = $fixedPricePolicy; - $this->fixedPriceByValuePolicy = null; - $this->pricingPolicy = self::PRICING_POLICY_FIXED_PRICE_BY_WEIGHT; - } - - /** - * Gets Fixed price by weight policy data. - * - * @return FixedPricePolicy[] FixedPricePolicy array. - */ - public function getFixedPriceByWeightPolicy() - { - return $this->fixedPriceByWeightPolicy; - } - - /** - * Sets data for fixed price by value policy. - * - * @param FixedPricePolicy[] $fixedPricePolicy - * - * @throws \InvalidArgumentException - */ - public function setFixedPriceByValuePolicy($fixedPricePolicy) - { - $fixedPricePolicy = $this->sortFixedPricePolicy($fixedPricePolicy); - - PricingPolicyValidator::validateFixedPricePolicy($fixedPricePolicy, true); - - $this->percentPricePolicy = null; - $this->fixedPriceByWeightPolicy = null; - $this->fixedPriceByValuePolicy = $fixedPricePolicy; - $this->pricingPolicy = self::PRICING_POLICY_FIXED_PRICE_BY_VALUE; - } - - /** - * Gets Fixed price by value policy data. - * - * @return FixedPricePolicy[] FixedPricePolicy array. - */ - public function getFixedPriceByValuePolicy() - { - return $this->fixedPriceByValuePolicy; - } - - /** - * Sets Percent pricing policy. - * - * @param PercentPricePolicy $percentPricePolicy Percent price policy data. - */ - public function setPercentPricePolicy($percentPricePolicy) - { - PricingPolicyValidator::validatePercentPricePolicy($percentPricePolicy); - - $this->percentPricePolicy = $percentPricePolicy; - $this->fixedPriceByWeightPolicy = null; - $this->fixedPriceByValuePolicy = null; - $this->pricingPolicy = self::PRICING_POLICY_PERCENT; + $this->pricingPolicies[] = $policy; } /** * Gets Percent pricing policy data. * - * @return PercentPricePolicy PercentPricePolicy data. + * @return ShippingPricePolicy[] PercentPricePolicy data. */ - public function getPercentPricePolicy() + public function getPricingPolicies() { - return $this->percentPricePolicy; + return $this->pricingPolicies; } /** - * Sets default Packlink pricing policy. + * Removes all pricing policies. */ - public function setPacklinkPricePolicy() + public function resetPricingPolicies() { - $this->percentPricePolicy = null; - $this->fixedPriceByWeightPolicy = null; - $this->fixedPriceByValuePolicy = null; - $this->pricingPolicy = self::PRICING_POLICY_PACKLINK; + $this->pricingPolicies = array(); } /** @@ -655,7 +539,8 @@ public function setTaxClass($taxClass) * * @param array $shippingCountries List of allowed destination countries. */ - public function setShippingCountries(array $shippingCountries) { + public function setShippingCountries(array $shippingCountries) + { $this->shippingCountries = $shippingCountries; } @@ -670,7 +555,7 @@ public function getShippingCountries() } /** - * Retrieves flag that denotes whether shipping to all countries is enabled. + * Retrieves a flag that denotes whether shipping to all countries is enabled. * * @return bool Flag that denotes whether shipping to all countries is enabled. */ @@ -680,7 +565,7 @@ public function isShipToAllCountries() } /** - * Sets flag that denotes whether shipping to all countries is enabled. + * Sets a flag that denotes whether shipping to all countries is enabled. * * @param boolean $isShipToAllCountries Flag that denotes whether shipping to all countries is enabled. */ @@ -690,39 +575,22 @@ public function setShipToAllCountries($isShipToAllCountries) } /** - * Sorts fixed price policies by ranges. + * Retrieves a flag that denotes whether Packlink price should be used when all policies are out of range. * - * @param FixedPricePolicy[] $fixedPricePolicy - * - * @return FixedPricePolicy[] Sorted array. + * @return bool Flag that denotes whether Packlink price should be used when all policies are out of range. */ - protected function sortFixedPricePolicy($fixedPricePolicy) + public function isUsePacklinkPriceIfNotInRange() { - usort( - $fixedPricePolicy, - function (FixedPricePolicy $first, FixedPricePolicy $second) { - return $first->from > $second->from; - } - ); - - return $fixedPricePolicy; + return $this->usePacklinkPriceIfNotInRange; } /** - * Inflates fixed price policies from array. - * - * @param array $data Source array. - * @param string $type Type of fixed price policy as a key for the source array. + * Sets a flag that denotes whether Packlink price should be used when all policies are out of range. * - * @return FixedPricePolicy[] + * @param bool $usePacklinkPriceIfNotInRange Out-of-range behavior. */ - protected function inflateFixedPricePolicy($data, $type) + public function setUsePacklinkPriceIfNotInRange($usePacklinkPriceIfNotInRange) { - $policies = array(); - foreach ($data[$type] as $fixedPricePolicy) { - $policies[] = FixedPricePolicy::fromArray($fixedPricePolicy); - } - - return $policies; + $this->usePacklinkPriceIfNotInRange = $usePacklinkPriceIfNotInRange; } } diff --git a/src/BusinessLogic/ShippingMethod/Models/ShippingPricePolicy.php b/src/BusinessLogic/ShippingMethod/Models/ShippingPricePolicy.php new file mode 100644 index 00000000..8bdfb317 --- /dev/null +++ b/src/BusinessLogic/ShippingMethod/Models/ShippingPricePolicy.php @@ -0,0 +1,236 @@ +rangeType = (int)static::getDataValue($data, 'range_type'); + $result->fromWeight = static::getDataValue($data, 'from_weight', null); + $result->toWeight = static::getDataValue($data, 'to_weight', null); + $result->fromPrice = static::getDataValue($data, 'from_price', null); + $result->toPrice = static::getDataValue($data, 'to_price', null); + $result->pricingPolicy = (int)static::getDataValue($data, 'pricing_policy'); + $result->increase = static::getDataValue($data, 'increase', false); + $result->changePercent = static::getDataValue($data, 'change_percent', null); + $result->fixedPrice = static::getDataValue($data, 'fixed_price', null); + + return $result; + } + + /** + * Transforms entity to its array format representation. + * + * @return array Entity in array format. + */ + public function toArray() + { + return array( + 'range_type' => $this->rangeType, + 'from_weight' => is_null($this->fromWeight) ? null : round($this->fromWeight, 2), + 'to_weight' => is_null($this->toWeight) ? null : round($this->toWeight, 2), + 'from_price' => is_null($this->fromPrice) ? null : round($this->fromPrice, 2), + 'to_price' => is_null($this->toPrice) ? null : round($this->toPrice, 2), + 'pricing_policy' => $this->pricingPolicy, + 'increase' => $this->increase, + 'change_percent' => is_null($this->changePercent) ? null : round($this->changePercent, 2), + 'fixed_price' => is_null($this->fixedPrice) ? null : round($this->fixedPrice, 2), + ); + } + + /** + * Generates validation errors for the payload. + * + * @param array $payload The payload in key-value format. + * @param ValidationError[] $validationErrors The array of errors to populate. + */ + protected static function doValidate(array $payload, array &$validationErrors) + { + parent::doValidate($payload, $validationErrors); + + if (static::validateRequiredField($payload, 'range_type', $validationErrors)) { + if (($payload['range_type'] === self::RANGE_PRICE + || $payload['range_type'] === self::RANGE_PRICE_AND_WEIGHT) + && static::validateRequiredField($payload, 'from_price', $validationErrors) + ) { + static::validateRange($payload, 'from_price', 'to_price', $validationErrors); + } + + if (($payload['range_type'] === self::RANGE_WEIGHT + || $payload['range_type'] === self::RANGE_PRICE_AND_WEIGHT) + && static::validateRequiredField($payload, 'from_weight', $validationErrors) + ) { + static::validateRange($payload, 'from_weight', 'to_weight', $validationErrors); + } + } + + if (static::validateRequiredField($payload, 'pricing_policy', $validationErrors)) { + if ($payload['pricing_policy'] === self::POLICY_PACKLINK_ADJUST) { + if (static::validateRequiredField($payload, 'change_percent', $validationErrors)) { + $changePercent = (float)$payload['change_percent']; + if ($changePercent <= 0 || ($payload['increase'] === false && $changePercent > 99.99)) { + static::setInvalidFieldError('change_percent', $validationErrors); + } + } + } elseif ($payload['pricing_policy'] === self::POLICY_FIXED_PRICE + && static::validateRequiredField($payload, 'fixed_price', $validationErrors) + && (float)$payload['fixed_price'] < 0 + ) { + static::setInvalidFieldError('fixed_price', $validationErrors); + } + } + } + + /** + * Checks whether the array element with the given key is set. + * + * @param array $payload The payload in key-value format. + * @param string $key The field key. + * + * @return bool + */ + protected static function requiredFieldSet(array $payload, $key) + { + return array_key_exists($key, $payload); + } + + /** + * Validates range. + * + * @param array $payload + * @param string $lowerBoundaryKey + * @param string $upperBoundaryKey + * @param ValidationError[] $validationErrors + */ + protected static function validateRange(array $payload, $lowerBoundaryKey, $upperBoundaryKey, &$validationErrors) + { + $lowerBoundary = (float)$payload[$lowerBoundaryKey]; + if ($lowerBoundary < 0) { + static::setInvalidFieldError($lowerBoundaryKey, $validationErrors); + } elseif (isset($payload[$upperBoundaryKey]) && $lowerBoundary >= (float)$payload[$upperBoundaryKey]) { + static::setInvalidFieldError($upperBoundaryKey, $validationErrors); + } + } +} diff --git a/src/BusinessLogic/ShippingMethod/ShippingCostCalculator.php b/src/BusinessLogic/ShippingMethod/ShippingCostCalculator.php index c4905935..1999bef1 100644 --- a/src/BusinessLogic/ShippingMethod/ShippingCostCalculator.php +++ b/src/BusinessLogic/ShippingMethod/ShippingCostCalculator.php @@ -12,8 +12,8 @@ use Packlink\BusinessLogic\Http\DTO\ShippingServiceSearch; use Packlink\BusinessLogic\Http\Proxy; use Packlink\BusinessLogic\PostalCode\PostalCodeTransformer; -use Packlink\BusinessLogic\ShippingMethod\Exceptions\FixedPriceValueOutOfBoundsException; use Packlink\BusinessLogic\ShippingMethod\Models\ShippingMethod; +use Packlink\BusinessLogic\ShippingMethod\Models\ShippingPricePolicy; use Packlink\BusinessLogic\ShippingMethod\Models\ShippingService; /** @@ -32,7 +32,7 @@ class ShippingCostCalculator * @param string $toCountry Destination country code. * @param string $toZip Destination zip code. * @param Package[] $packages Array of packages if calculation is done by weight. - * @param float $totalAmount Total cart value if calculation is done by value + * @param float $totalPrice Total cart value if calculation is done by value * * @return float Calculated shipping cost for service if found. Otherwise, 0.0; */ @@ -43,7 +43,7 @@ public static function getShippingCost( $toCountry, $toZip, array $packages, - $totalAmount + $totalPrice ) { $data = self::getShippingCosts( array($shippingMethod), @@ -52,7 +52,7 @@ public static function getShippingCost( $toCountry, $toZip, $packages, - $totalAmount + $totalPrice ); $data = !empty($data) ? current($data) : 0; @@ -60,7 +60,7 @@ public static function getShippingCost( } /** - * Returns shipping costs for all given shipping methods that support delivery with specified parameters. + * Returns shipping costs for all given shipping methods that support delivery for specified parameters. * * @param ShippingMethod[] $shippingMethods Shipping methods to do a calculation for. * @param string $fromCountry Departure country code. @@ -68,7 +68,7 @@ public static function getShippingCost( * @param string $toCountry Destination country code. * @param string $toZip Destination zip code. * @param Package[] $packages Array of packages if calculation is done by weight. - * @param float $totalAmount Total cart value if calculation is done by value + * @param float $totalPrice Total cart value if calculation is done by value * * @return array Array of shipping cost per service. Key is service id and value is shipping cost. */ @@ -79,7 +79,7 @@ public static function getShippingCosts( $toCountry, $toZip, array $packages, - $totalAmount + $totalPrice ) { if (empty($shippingMethods)) { return array(); @@ -94,7 +94,7 @@ public static function getShippingCosts( $shippingMethods, $response, $package->weight, - $totalAmount + $totalPrice ); } catch (HttpBaseException $e) { // Fallback when API is not available. @@ -104,7 +104,7 @@ public static function getShippingCosts( $fromCountry, $toCountry, $package->weight, - $totalAmount + $totalPrice ); } } @@ -113,7 +113,7 @@ public static function getShippingCosts( } /** - * Returns cheapest service in shipping method. + * Returns cheapest service in shipping method. It does not take into consideration pricing policies. * * @param ShippingMethod $method * @param string $fromCountry From country code. @@ -122,7 +122,7 @@ public static function getShippingCosts( * @param string $toZip To zip code. * @param Package[] $packages Packages for which to find service. * - * @return ShippingService Cheapest service. + * @return ShippingService|null The cheapest service if found; otherwise, null. */ public static function getCheapestShippingService( ShippingMethod $method, @@ -140,27 +140,14 @@ public static function getCheapestShippingService( $services = array(); } - /** @var ShippingService $result */ $result = null; if (!empty($services)) { foreach ($services as $service) { - foreach ($method->getShippingServices() as $methodService) { - if ($service->id === $methodService->serviceId - && ($result === null || $result->basePrice > $methodService->basePrice) - ) { - $result = $methodService; - } - } + $result = self::getCheapestService($method->getShippingServices(), $result, $service->id); } } else { // Fallback. - foreach ($method->getShippingServices() as $service) { - if ($service->destinationCountry === $toCountry) { - if ($result === null || $result->basePrice > $service->basePrice) { - $result = $service; - } - } - } + $result = self::getCheapestService($method->getShippingServices(), $result, '', $toCountry); } if ($result !== null) { @@ -233,142 +220,14 @@ protected static function getPacklinkServices($fromCountry, $fromZip, $toCountry } } - /** - * Calculates shipping method cost based on given criteria. - * - * @param ShippingMethod $shippingMethod - * @param float $totalAmount - * - * @param int $serviceId - * @param float $basePrice - * - * @param string $fromCountry - * @param string $toCountry - * - * @return float|bool Calculated cost or FALSE if cost cannot be calculated for the given criteria. - */ - protected static function calculateShippingMethodCost( - ShippingMethod $shippingMethod, - $totalAmount, - $serviceId = 0, - $basePrice = 0.0, - $fromCountry = '', - $toCountry = '' - ) { - $cost = PHP_INT_MAX; - foreach ($shippingMethod->getShippingServices() as $methodService) { - if (($serviceId !== 0 && $methodService->serviceId === $serviceId) - || ($methodService->departureCountry === $fromCountry - && $methodService->destinationCountry === $toCountry) - ) { - try { - $baseCost = self::calculateCostForShippingMethod( - $shippingMethod, - $basePrice ?: $methodService->basePrice, - $totalAmount - ); - } catch (FixedPriceValueOutOfBoundsException $e) { - return false; - } - - $cost = min($cost, $baseCost); - } - } - - return $cost !== PHP_INT_MAX ? $cost : false; - } - - /** - * Calculates shipping cost for given shipping method based on its pricing policy. - * - * @param ShippingMethod $shippingMethod Method to calculate cost for. - * @param float $baseCost Base cost from Packlink API or from default cost. - * @param float $totalAmount Total amount (weight or value). - * - * @return float Calculated shipping cost. - * - * @throws \Packlink\BusinessLogic\ShippingMethod\Exceptions\FixedPriceValueOutOfBoundsException - */ - protected static function calculateCostForShippingMethod( - ShippingMethod $shippingMethod, - $baseCost, - $totalAmount = 0.0 - ) { - $pricingPolicy = $shippingMethod->getPricingPolicy(); - if ($pricingPolicy === ShippingMethod::PRICING_POLICY_FIXED_PRICE_BY_WEIGHT - || $pricingPolicy === ShippingMethod::PRICING_POLICY_FIXED_PRICE_BY_VALUE - ) { - return round(self::calculateFixedPriceCost($shippingMethod, $totalAmount), 2); - } - - return round(self::calculateVariableCost($shippingMethod, $baseCost), 2); - } - - /** - * Calculates shipping cost for fixed price policy. - * - * @param ShippingMethod $shippingMethod Method to calculate cost for. - * @param float $total Total weight or value. - * - * @return float Calculated fixed price cost. - * - * @throws \Packlink\BusinessLogic\ShippingMethod\Exceptions\FixedPriceValueOutOfBoundsException - */ - protected static function calculateFixedPriceCost(ShippingMethod $shippingMethod, $total) - { - $fixedPricePolicies = $shippingMethod->getFixedPriceByWeightPolicy(); - if ($shippingMethod->getPricingPolicy() === ShippingMethod::PRICING_POLICY_FIXED_PRICE_BY_VALUE) { - $fixedPricePolicies = $shippingMethod->getFixedPriceByValuePolicy(); - } - - self::sortFixedPricePolicy($fixedPricePolicies); - - if ($total < $fixedPricePolicies[0]->from) { - throw new FixedPriceValueOutOfBoundsException('Fixed price value out of bounds.'); - } - - foreach ($fixedPricePolicies as $fixedPricePolicy) { - if ($fixedPricePolicy->from <= $total && $fixedPricePolicy->to > $total) { - return $fixedPricePolicy->amount; - } - } - - return $fixedPricePolicies[count($fixedPricePolicies) - 1]->amount; - } - - /** - * Calculates cost based on default value and percent or Packlink pricing policy. - * - * @param ShippingMethod $shippingMethod Method to calculate cost for. - * @param float $defaultCost Base cost on which to apply pricing policy. - * - * @return float Final cost. - */ - protected static function calculateVariableCost(ShippingMethod $shippingMethod, $defaultCost) - { - $pricingPolicy = $shippingMethod->getPricingPolicy(); - if ($pricingPolicy === ShippingMethod::PRICING_POLICY_PACKLINK) { - return $defaultCost; - } - - $policy = $shippingMethod->getPercentPricePolicy(); - $amount = $defaultCost * ($policy->amount / 100); - if ($policy->increase) { - return $defaultCost + $amount; - } - - return $defaultCost - $amount; - } - /** * Returns default shipping costs for all given shipping methods in the system. * * @param ShippingMethod[] $shippingMethods Array of shipping methods fmr the shop. - * * @param string $fromCountry Departure country code. * @param string $toCountry Destination country code. * @param float $totalWeight Package total weight. - * @param float $totalAmount Total cart value if calculation is done by value + * @param float $totalPrice Total cart value if calculation is done by value * * @return array Array of shipping cost per service. Key is service id and value is shipping cost. */ @@ -377,15 +236,15 @@ protected static function getDefaultShippingCosts( $fromCountry, $toCountry, $totalWeight, - $totalAmount + $totalPrice ) { $shippingCosts = array(); - /** @var ShippingMethod $shippingMethod */ foreach ($shippingMethods as $shippingMethod) { $cost = self::calculateShippingMethodCost( $shippingMethod, - self::getAmountBasedOnPricingPolicy($shippingMethod, $totalAmount, $totalWeight), + $totalWeight, + $totalPrice, 0, 0.0, $fromCountry, @@ -406,25 +265,29 @@ protected static function getDefaultShippingCosts( * @param ShippingMethod[] $shippingMethods Array of active shipping methods in the system. * @param ShippingServiceDetails[] $shippingServices Array of shipping services delivery details. * @param float $totalWeight Package total weight. - * @param float $totalAmount Total value if calculation is done by value + * @param float $totalPrice Total value if calculation is done by value * * @return array Array of shipping cost per service. Key is service id and value is shipping cost. */ - private static function calculateShippingCostsPerShippingMethod( + protected static function calculateShippingCostsPerShippingMethod( array $shippingMethods, array $shippingServices, $totalWeight, - $totalAmount + $totalPrice ) { $shippingCosts = array(); - /** @var ShippingMethod $method */ foreach ($shippingMethods as $method) { - $amount = self::getAmountBasedOnPricingPolicy($method, $totalAmount, $totalWeight); - $cost = PHP_INT_MAX; foreach ($shippingServices as $service) { - $baseCost = self::calculateShippingMethodCost($method, $amount, $service->id, $service->basePrice); + $baseCost = self::calculateShippingMethodCost( + $method, + $totalWeight, + $totalPrice, + $service->id, + $service->basePrice + ); + if ($baseCost !== false) { $cost = min($cost, $baseCost); } @@ -439,37 +302,111 @@ private static function calculateShippingCostsPerShippingMethod( } /** - * Gets instance of proxy. + * Calculates shipping method cost based on given criteria. * - * @return \Packlink\BusinessLogic\Http\Proxy Packlink Proxy. + * @param ShippingMethod $shippingMethod + * @param float $totalWeight + * @param float $totalPrice + * @param int $serviceId + * @param float $basePrice + * @param string $fromCountry + * @param string $toCountry + * + * @return float|bool Calculated cost or FALSE if cost cannot be calculated for the given criteria. */ - private static function getProxy() - { - /** @var Proxy $proxy */ - $proxy = ServiceRegister::getService(Proxy::CLASS_NAME); + protected static function calculateShippingMethodCost( + ShippingMethod $shippingMethod, + $totalWeight, + $totalPrice, + $serviceId = 0, + $basePrice = 0.0, + $fromCountry = '', + $toCountry = '' + ) { + $cost = PHP_INT_MAX; + // porting to array_reduce would increase complexity of the code because inner function will need a lot of + // parameters + foreach ($shippingMethod->getShippingServices() as $methodService) { + if (($serviceId !== 0 && $methodService->serviceId === $serviceId) + || ($methodService->departureCountry === $fromCountry + && $methodService->destinationCountry === $toCountry) + ) { + $baseCost = self::getCostForShippingService( + $shippingMethod, + $basePrice ?: $methodService->basePrice, + $totalWeight, + $totalPrice + ); - return $proxy; + $cost = min($cost, $baseCost); + } + } + + return $cost !== PHP_INT_MAX ? $cost : false; } /** + * Calculates shipping cost for given shipping method based on its pricing policy. + * * @param ShippingMethod $method - * @param $totalAmount - * @param $totalWeight + * @param float $baseCost Base cost from Packlink API or from default cost. + * @param float $totalWeight + * @param float $totalPrice Total amount (weight or value). * - * @return int + * @return float Calculated shipping cost. + */ + protected static function getCostForShippingService( + ShippingMethod $method, + $baseCost, + $totalWeight = 0.0, + $totalPrice = 0.0 + ) { + $pricingPolicies = $method->getPricingPolicies(); + if (empty($pricingPolicies)) { + return $baseCost; + } + + $cost = PHP_INT_MAX; + foreach ($pricingPolicies as $policy) { + if (self::canPolicyBeApplied($policy, $totalWeight, $totalPrice)) { + $cost = self::calculateCost($policy, $baseCost); + + break; + } + } + + // if no policy can be applied because of range, use base cost if it is set that way + if ($cost === PHP_INT_MAX && $method->isUsePacklinkPriceIfNotInRange()) { + $cost = $baseCost; + } + + return $cost !== PHP_INT_MAX ? $cost : false; + } + + /** + * Calculates cost based on default value and percent or Packlink pricing policy. * + * @param ShippingPricePolicy $policy Pricing policy + * @param float $defaultCost Base cost on which to apply pricing policy. + * + * @return float Final cost. */ - private static function getAmountBasedOnPricingPolicy(ShippingMethod $method, $totalAmount, $totalWeight) + protected static function calculateCost(ShippingPricePolicy $policy, $defaultCost) { - $pricingPolicy = $method->getPricingPolicy(); - $amount = 0; - if ($pricingPolicy === ShippingMethod::PRICING_POLICY_FIXED_PRICE_BY_WEIGHT) { - $amount = $totalWeight; - } elseif ($pricingPolicy === ShippingMethod::PRICING_POLICY_FIXED_PRICE_BY_VALUE) { - $amount = $totalAmount; + if ($policy->pricingPolicy === ShippingPricePolicy::POLICY_PACKLINK) { + return $defaultCost; + } + + if ($policy->pricingPolicy === ShippingPricePolicy::POLICY_FIXED_PRICE) { + return $policy->fixedPrice; + } + + $amount = $defaultCost * ($policy->changePercent / 100); + if ($policy->increase) { + return round($defaultCost + $amount, 2); } - return $amount; + return round($defaultCost - $amount, 2); } /** @@ -488,22 +425,64 @@ private static function preparePackages(array $packages = array()) } /** - * Sorts fixed price policies. + * Indicates whether the given policy can be applied by range for the given parameters. * - * @param \Packlink\BusinessLogic\ShippingMethod\Models\FixedPricePolicy[] $fixedPricePolicies - * Reference to an fixed price policy array. + * @param ShippingPricePolicy $policy + * @param float $totalWeight + * @param float $totalPrice + * + * @return bool */ - private static function sortFixedPricePolicy(&$fixedPricePolicies) + private static function canPolicyBeApplied(ShippingPricePolicy $policy, $totalWeight, $totalPrice) { - usort( - $fixedPricePolicies, - function ($a, $b) { - if ($a->from === $b->from) { - return 0; - } + $byPrice = $policy->fromPrice <= $totalPrice && (empty($policy->toPrice) || $totalPrice <= $policy->toPrice); + $byWeight = $policy->fromWeight <= $totalWeight + && (empty($policy->toWeight) || $totalWeight <= $policy->toWeight); + + switch ($policy->rangeType) { + case ShippingPricePolicy::RANGE_PRICE: + return $byPrice; + case ShippingPricePolicy::RANGE_WEIGHT: + return $byWeight; + default: + return $byPrice && $byWeight; + } + } - return $a->from < $b->from ? -1 : 1; + /** + * Gets the cheapest shipping services. + * + * @param ShippingService[] $services Shipping services to check. + * @param ShippingService|null $result The service to compare to. + * @param int|string $serviceId The ID of service to take into consideration. + * @param string $toCountry The destination country for the service. + * + * @return \Packlink\BusinessLogic\ShippingMethod\Models\ShippingService + */ + private static function getCheapestService(array $services, $result, $serviceId = '', $toCountry = '') + { + foreach ($services as $service) { + if ((!$toCountry || $service->destinationCountry === $toCountry) + && (!$serviceId || $serviceId === $service->serviceId) + && ($result === null || $result->basePrice > $service->basePrice) + ) { + $result = $service; } - ); + } + + return $result; + } + + /** + * Gets instance of proxy. + * + * @return \Packlink\BusinessLogic\Http\Proxy Packlink Proxy. + */ + private static function getProxy() + { + /** @var Proxy $proxy */ + $proxy = ServiceRegister::getService(Proxy::CLASS_NAME); + + return $proxy; } } diff --git a/src/BusinessLogic/ShippingMethod/ShippingMethodService.php b/src/BusinessLogic/ShippingMethod/ShippingMethodService.php index e3f7ade4..e0aa6c88 100644 --- a/src/BusinessLogic/ShippingMethod/ShippingMethodService.php +++ b/src/BusinessLogic/ShippingMethod/ShippingMethodService.php @@ -67,13 +67,31 @@ public function getAllMethods() } /** - * Returns all shipping methods for current user. + * Returns all configured shipping methods for current user. * - * @return ShippingMethod[] All shipping methods. + * @return ShippingMethod[] Active shipping methods. + * @noinspection PhpDocMissingThrowsInspection */ public function getActiveMethods() { $filter = $this->setFilterCondition(new QueryFilter(), 'activated', Operators::EQUALS, true); + /** @noinspection PhpUnhandledExceptionInspection */ + $filter->orderBy('carrierName'); + + return $this->select($filter); + } + + /** + * Returns all shipping methods that are not configured. + * + * @return ShippingMethod[] Inactive shipping methods. + * @noinspection PhpDocMissingThrowsInspection + */ + public function getInactiveMethods() + { + $filter = $this->setFilterCondition(new QueryFilter(), 'activated', Operators::EQUALS, false); + /** @noinspection PhpUnhandledExceptionInspection */ + $filter->orderBy('carrierName'); return $this->select($filter); } diff --git a/src/BusinessLogic/ShippingMethod/Utility/ShipmentStatus.php b/src/BusinessLogic/ShippingMethod/Utility/ShipmentStatus.php index 50dda866..2017086a 100644 --- a/src/BusinessLogic/ShippingMethod/Utility/ShipmentStatus.php +++ b/src/BusinessLogic/ShippingMethod/Utility/ShipmentStatus.php @@ -61,10 +61,29 @@ public static function getStatus($shipmentStatus) case 'CARRIER_PENDING': case 'RETRY': return self::STATUS_ACCEPTED; + case 'CANCELED': + return self::STATUS_CANCELLED; case 'AWAITING_COMPLETION': case 'READY_TO_PURCHASE': default: return self::STATUS_PENDING; } } + + /** + * Gets possible shipment statuses. + * + * @return string[] + */ + public static function getPossibleStatuses() + { + return array( + self::STATUS_PENDING, + self::STATUS_ACCEPTED, + self::STATUS_READY, + self::STATUS_IN_TRANSIT, + self::STATUS_DELIVERED, + self::STATUS_CANCELLED, + ); + } } diff --git a/src/BusinessLogic/ShippingMethod/Validation/PricingPolicyValidator.php b/src/BusinessLogic/ShippingMethod/Validation/PricingPolicyValidator.php deleted file mode 100644 index 96ac2448..00000000 --- a/src/BusinessLogic/ShippingMethod/Validation/PricingPolicyValidator.php +++ /dev/null @@ -1,78 +0,0 @@ - 0) { - $count = count($fixedPricePolicies); - $previous = $fixedPricePolicies[0]; - self::validateSingleFixedPricePolicy($previous, $previous->from, $allowZeroPrice); - - for ($i = 1; $i < $count; $i++) { - self::validateSingleFixedPricePolicy($fixedPricePolicies[$i], $previous->to, $allowZeroPrice); - $previous = $fixedPricePolicies[$i]; - } - } - } - - /** - * Validates percent price policy. - * Rules for policy: - * 1. 'amount' must be a positive number - * 2. 'amount' must be less than 100 if increase is FALSE - * - * @param PercentPricePolicy $policy Policy to validate. - * - * @throws \InvalidArgumentException When direction and/or amount are not valid. - */ - public static function validatePercentPricePolicy(PercentPricePolicy $policy) - { - if ($policy->amount <= 0 || (!$policy->increase && $policy->amount >= 100)) { - throw new \InvalidArgumentException('Percent price policy is not valid. Check direction and amounts.'); - } - } - - /** - * Validates single fixed price policy. - * - * @param FixedPricePolicy $policy Policy to validate. - * @param float $lowerBoundary Value of 'from' field. - * @param bool $allowZeroPrice Indicates whether amount can be equal to zero. - * - * @throws \InvalidArgumentException When range and/or amount are not valid. - */ - protected static function validateSingleFixedPricePolicy($policy, $lowerBoundary, $allowZeroPrice = false) - { - if ((float) $lowerBoundary < 0 - || (float)$policy->from !== (float)$lowerBoundary - || $policy->from >= $policy->to - || ($allowZeroPrice && $policy->amount < 0) - || (!$allowZeroPrice && $policy->amount <= 0) - ) { - throw new \InvalidArgumentException('Fixed price policies are not valid. Check range and amounts.'); - } - } -} diff --git a/src/BusinessLogic/Tasks/UpdateShippingServicesTask.php b/src/BusinessLogic/Tasks/UpdateShippingServicesTask.php index 08de6a33..728b2b12 100644 --- a/src/BusinessLogic/Tasks/UpdateShippingServicesTask.php +++ b/src/BusinessLogic/Tasks/UpdateShippingServicesTask.php @@ -5,7 +5,6 @@ use Logeecom\Infrastructure\ServiceRegister; use Logeecom\Infrastructure\TaskExecution\Task; use Packlink\BusinessLogic\Configuration; -use Packlink\BusinessLogic\Country\Country; use Packlink\BusinessLogic\Country\WarehouseCountryService; use Packlink\BusinessLogic\Http\DTO\Package; use Packlink\BusinessLogic\Http\DTO\ParcelInfo; @@ -214,29 +213,6 @@ protected function shouldExecute() || $this->getCountryService()->isCountrySupported($userInfo->country); } - /** - * Returns supported countries for available services. - * - * @return \Packlink\BusinessLogic\Country\Country[] - * - * @throws \Packlink\BusinessLogic\DTO\Exceptions\FrontDtoValidationException - */ - protected function getSupportedCountriesForServices() - { - $supportedCountries = $this->getCountryService()->getSupportedCountries(); - $supportedCountries['US'] = Country::fromArray( - array( - 'name' => 'United States', - 'code' => 'US', - 'postal_code' => '10001', - 'registration_link' => 'https://pro.packlink.com/register', - 'platform_country' => 'UN', - ) - ); - - return $supportedCountries; - } - /** * Gets instance of shipping method service. * diff --git a/src/BusinessLogic/Tax/TaxClass.php b/src/BusinessLogic/Tax/TaxClass.php index 8772f958..aff907e4 100644 --- a/src/BusinessLogic/Tax/TaxClass.php +++ b/src/BusinessLogic/Tax/TaxClass.php @@ -3,7 +3,6 @@ namespace Packlink\BusinessLogic\Tax; use Packlink\BusinessLogic\DTO\FrontDto; -use Packlink\BusinessLogic\DTO\ValidationError; /** * Class TaxClass. @@ -44,23 +43,4 @@ class TaxClass extends FrontDto * @var array */ protected static $requiredFields = array('label', 'value'); - - /** - * Checks the payload for mandatory fields. - * - * @param array $payload The payload in key-value format. - * @param ValidationError[] $validationErrors The array of errors to populate. - */ - protected static function validateRequiredFields(array $payload, array &$validationErrors) - { - foreach (static::$requiredFields as $field) { - if (!isset($payload[$field])) { - $validationErrors[] = static::getValidationError( - ValidationError::ERROR_REQUIRED_FIELD, - $field, - 'Field is required.' - ); - } - } - } } diff --git a/src/BusinessLogic/Utility/DtoValidator.php b/src/BusinessLogic/Utility/DtoValidator.php index 86087b4c..7071cadc 100644 --- a/src/BusinessLogic/Utility/DtoValidator.php +++ b/src/BusinessLogic/Utility/DtoValidator.php @@ -30,13 +30,8 @@ public static function isEmailValid($email) */ public static function isPhoneValid($phone) { - $regex = '/^(\ |\+|\/|\.\|-|\(|\)|\d)+$/m'; - $phoneError = !preg_match($regex, $phone); + $regex = '/^[0-9|\/\-\s \+\.\(\)]+$/i'; - $digits = '/\d/m'; - $match = preg_match_all($digits, $phone, $matches); - $phoneError |= $match === false || $match < 3; - - return !$phoneError; + return !empty($phone) && preg_match($regex, $phone); } } diff --git a/src/BusinessLogic/Utility/UrlService.php b/src/BusinessLogic/Utility/UrlService.php new file mode 100644 index 00000000..f5a19214 --- /dev/null +++ b/src/BusinessLogic/Utility/UrlService.php @@ -0,0 +1,37 @@ +getUserInfo(); + $currentLang = $configService::getCurrentLanguage(); + + if ($userInfo !== null && in_array($userInfo->country, array('ES', 'DE', 'FR', 'IT'), true)) { + $locale = $userInfo->country; + } elseif (in_array(strtoupper($currentLang), array('ES', 'DE', 'FR', 'IT'), true)) { + $locale = strtoupper($currentLang); + } + + return $locale; + } +} diff --git a/src/BusinessLogic/Warehouse/Warehouse.php b/src/BusinessLogic/Warehouse/Warehouse.php index 06b6cc93..814071a9 100644 --- a/src/BusinessLogic/Warehouse/Warehouse.php +++ b/src/BusinessLogic/Warehouse/Warehouse.php @@ -4,6 +4,7 @@ use Packlink\BusinessLogic\DTO\FrontDto; use Packlink\BusinessLogic\DTO\ValidationError; +use Packlink\BusinessLogic\Language\Translator; use Packlink\BusinessLogic\Utility\DtoValidator; /** @@ -139,7 +140,13 @@ public static function fromArray(array $raw) /** @var static $instance */ $instance = parent::fromArray($raw); $instance->default = static::getDataValue($raw, 'default_selection', false); - $instance->postalCode = static::getDataValue($raw, 'postal_code'); + $zipCity = explode(' - ', static::getDataValue($raw, 'postal_code')); + if (count($zipCity) === 2) { + $instance->postalCode = $zipCity[0]; + $instance->city = $zipCity[1]; + } else { + $instance->postalCode = static::getDataValue($raw, 'postal_code'); + } return $instance; } @@ -171,7 +178,7 @@ protected static function doValidate(array $payload, array &$validationErrors) $validationErrors[] = static::getValidationError( ValidationError::ERROR_INVALID_FIELD, 'email', - 'Field must be a valid email.' + Translator::translate('validation.invalidEmail') ); } @@ -179,7 +186,7 @@ protected static function doValidate(array $payload, array &$validationErrors) $validationErrors[] = static::getValidationError( ValidationError::ERROR_INVALID_FIELD, 'phone', - 'Field must be a valid phone number.' + Translator::translate('validation.invalidPhone') ); } } diff --git a/src/BusinessLogic/WebHook/WebHookEventHandler.php b/src/BusinessLogic/WebHook/WebHookEventHandler.php index 6082db36..e319692b 100644 --- a/src/BusinessLogic/WebHook/WebHookEventHandler.php +++ b/src/BusinessLogic/WebHook/WebHookEventHandler.php @@ -107,7 +107,10 @@ protected function handleEvent($eventData) /** @var \Packlink\BusinessLogic\Http\DTO\Shipment $shipment */ $shipment = $proxy->getShipment($eventData->shipment_reference); } catch (HttpBaseException $e) { - Logger::logError($e->getMessage(), 'Core', array('referenceId' => $eventData->shipment_reference)); + Logger::logWarning($e->getMessage(), 'Core', array( + 'referenceId' => $eventData->shipment_reference, + 'trace' => $e->getTraceAsString(), + )); return; } diff --git a/src/DemoUI/.gitignore b/src/DemoUI/.gitignore new file mode 100644 index 00000000..92a15be4 --- /dev/null +++ b/src/DemoUI/.gitignore @@ -0,0 +1,6 @@ +# Custom content +vendor +src/Views/resources/js/ +src/Views/resources/images/ +src/Views/resources/css/location/ +src/Views/resources/css/* diff --git a/src/DemoUI/Lib/Composer.php b/src/DemoUI/Lib/Composer.php new file mode 100644 index 00000000..e93b10c3 --- /dev/null +++ b/src/DemoUI/Lib/Composer.php @@ -0,0 +1,62 @@ + $toBase . 'js', + $fromBase . 'css' => $toBase . 'css', + $fromBase . 'images' => $toBase . 'images', + ); + + foreach ($map as $from => $to) { + self::copyDirectory($from, $to); + } + } + + /** + * Copies directory. + * + * @param string $src + * @param string $dst + */ + private static function copyDirectory($src, $dst) + { + $dir = opendir($src); + self::mkdir($dst); + + while (false !== ($file = readdir($dir))) { + if (($file !== '.') && ($file !== '..')) { + if (is_dir($src . '/' . $file)) { + self::mkdir($dst . '/' . $file); + + self::copyDirectory($src . '/' . $file, $dst . '/' . $file); + } else { + copy($src . '/' . $file, $dst . '/' . $file); + } + } + } + + closedir($dir); + } + + /** + * Creates directory. + * + * @param string $destination + */ + private static function mkdir($destination) + { + if (!file_exists($destination) && !mkdir($destination) && !is_dir($destination)) { + throw new RuntimeException(sprintf('Directory "%s" was not created', $destination)); + } + } +} \ No newline at end of file diff --git a/src/DemoUI/README.md b/src/DemoUI/README.md new file mode 100644 index 00000000..493b1582 --- /dev/null +++ b/src/DemoUI/README.md @@ -0,0 +1,9 @@ +# Packlink Demo App + +## Running the app +To start the demo app, open the terminal in the folder where the run.sh file is +and run it with `sh ./run.sh`. This will start the development server on the location `localhost:7000`. + +You can set up debug for it as you would for any other server. + +To stop the server, just press `Ctrl + c`. diff --git a/src/DemoUI/composer.json b/src/DemoUI/composer.json new file mode 100755 index 00000000..cbfaddbc --- /dev/null +++ b/src/DemoUI/composer.json @@ -0,0 +1,27 @@ +{ + "name": "packlink/demo-ui", + "description": "Packlink Demo UI core library", + "type": "library", + "license": "proprietary", + "require": { + "php": ">=5.3.29" + }, + "autoload": { + "psr-4": { + "Logeecom\\Infrastructure\\": "../Infrastructure", + "Logeecom\\Tests\\": "../../tests", + "Packlink\\BusinessLogic\\": "../BusinessLogic", + "Packlink\\DemoUI\\Lib\\": "Lib", + "Packlink\\DemoUI\\": "src" + } + }, + "scripts": { + "post-update-cmd": "Packlink\\DemoUI\\Lib\\Composer::postUpdate", + "post-install-cmd": "Packlink\\DemoUI\\Lib\\Composer::postUpdate" + }, + "config": { + "platform": { + "php": "5.3.29" + } + } +} diff --git a/src/DemoUI/composer.lock b/src/DemoUI/composer.lock new file mode 100644 index 00000000..4e0b565f --- /dev/null +++ b/src/DemoUI/composer.lock @@ -0,0 +1,22 @@ +{ + "_readme": [ + "This file locks the dependencies of your project to a known state", + "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", + "This file is @generated automatically" + ], + "content-hash": "2157e45d141336963654f2cb2627846c", + "packages": [], + "packages-dev": [], + "aliases": [], + "minimum-stability": "stable", + "stability-flags": [], + "prefer-stable": false, + "prefer-lowest": false, + "platform": { + "php": ">=5.3.29" + }, + "platform-dev": [], + "platform-overrides": { + "php": "5.3.29" + } +} diff --git a/src/DemoUI/run.sh b/src/DemoUI/run.sh new file mode 100644 index 00000000..15003c1c --- /dev/null +++ b/src/DemoUI/run.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +composer install + +xdg-open http://localhost:7000/Views/index.php + +cd $PWD/src && php -S localhost:7000 \ No newline at end of file diff --git a/src/DemoUI/src/Bootstrap.php b/src/DemoUI/src/Bootstrap.php new file mode 100644 index 00000000..fc89976c --- /dev/null +++ b/src/DemoUI/src/Bootstrap.php @@ -0,0 +1,200 @@ +jsonSerializer = new JsonSerializer(); + $this->httpClientService = new CurlHttpClient(); + $this->loggerService = new LoggerService(); + $this->configService = ConfigurationService::getInstance(); + $this->shopOrderService = new ShopOrderService(); + $this->carrierService = new CarrierService(); + $this->userAccountService = UserAccountService::getInstance(); + $this->registrationInfoService = new TestRegistrationInfoService(); + } + + /** + * Initializes infrastructure components. + */ + public static function init() + { + static::$instance = new static(); + + parent::init(); + } + + /** + * Initializes infrastructure services and utilities. + */ + protected static function initServices() + { + parent::initServices(); + + static::$instance->initInstanceServices(); + } + + /** + * Initializes repositories. + * + * @throws RepositoryClassException + */ + protected static function initRepositories() + { + parent::initRepositories(); + + RepositoryRegistry::registerRepository(Process::CLASS_NAME, SessionRepository::getClassName()); + RepositoryRegistry::registerRepository(ConfigEntity::CLASS_NAME, SessionRepository::getClassName()); + RepositoryRegistry::registerRepository(QueueItem::CLASS_NAME, MemoryQueueItemRepository::getClassName()); + RepositoryRegistry::registerRepository(Schedule::CLASS_NAME, SessionRepository::getClassName()); + RepositoryRegistry::registerRepository(OrderShipmentDetails::CLASS_NAME, SessionRepository::getClassName()); + RepositoryRegistry::registerRepository(ShippingMethod::CLASS_NAME, SessionRepository::getClassName()); + RepositoryRegistry::registerRepository(Entity::CLASS_NAME, SessionRepository::getClassName()); + RepositoryRegistry::registerRepository(LogData::CLASS_NAME, SessionRepository::getClassName()); + RepositoryRegistry::registerRepository(OrderSendDraftTaskMap::CLASS_NAME, SessionRepository::getClassName()); + } + + /** + * Initializes instance services. + */ + protected function initInstanceServices() + { + $instance = static::$instance; + + ServiceRegister::registerService( + Serializer::CLASS_NAME, + function () use ($instance) { + return $instance->jsonSerializer; + } + ); + + ServiceRegister::registerService( + ShopLoggerAdapter::CLASS_NAME, + function () use ($instance) { + return $instance->loggerService; + } + ); + + ServiceRegister::registerService( + Configuration::CLASS_NAME, + function () use ($instance) { + return $instance->configService; + } + ); + + ServiceRegister::registerService( + HttpClient::CLASS_NAME, + function () use ($instance) { + return $instance->httpClientService; + } + ); + + ServiceRegister::registerService( + ShopOrderServiceInterface::CLASS_NAME, + function () use ($instance) { + return $instance->shopOrderService; + } + ); + + ServiceRegister::registerService( + ShopShippingMethodService::CLASS_NAME, + function () use ($instance) { + return $instance->carrierService; + } + ); + + ServiceRegister::registerService( + UserAccountService::CLASS_NAME, + function () use ($instance) { + return $instance->userAccountService; + } + ); + + ServiceRegister::registerService( + RegistrationInfoService::CLASS_NAME, + function () use ($instance) { + return $instance->registrationInfoService; + } + ); + } +} \ No newline at end of file diff --git a/src/DemoUI/src/Controllers/AutoConfigureController.php b/src/DemoUI/src/Controllers/AutoConfigureController.php new file mode 100644 index 00000000..91e0599b --- /dev/null +++ b/src/DemoUI/src/Controllers/AutoConfigureController.php @@ -0,0 +1,23 @@ +output(array('success' => $controller->start(true))); + } +} \ No newline at end of file diff --git a/src/DemoUI/src/Controllers/BaseHttpController.php b/src/DemoUI/src/Controllers/BaseHttpController.php new file mode 100644 index 00000000..9c80db18 --- /dev/null +++ b/src/DemoUI/src/Controllers/BaseHttpController.php @@ -0,0 +1,70 @@ +requiresAuthentication || $this->getConfigService()->getAuthorizationToken(); + } + + /** + * Gets the configuration service instance. + * + * @return ConfigurationService + */ + protected function getConfigService() + { + if (!$this->configService) { + $this->configService = ServiceRegister::getService(Configuration::CLASS_NAME); + } + + return $this->configService; + } + + /** + * Outputs the given array. + * + * @param array $data + */ + protected function output(array $data) + { + echo json_encode($data); + } + + /** + * Outputs DTO entities as a JSON encoded array. + * + * @param array $data + */ + protected function outputDtoEntities(array $data) + { + $this->output( + array_map( + function ($entity) { + return $entity->toArray(); + }, + $data + ) + ); + } +} diff --git a/src/DemoUI/src/Controllers/ConfigurationController.php b/src/DemoUI/src/Controllers/ConfigurationController.php new file mode 100644 index 00000000..2206fec2 --- /dev/null +++ b/src/DemoUI/src/Controllers/ConfigurationController.php @@ -0,0 +1,25 @@ + $ctrl->getHelpLink(), + 'version' => $this->getConfigService()->getModuleVersion(), + ) + ); + } +} diff --git a/src/DemoUI/src/Controllers/CountryController.php b/src/DemoUI/src/Controllers/CountryController.php new file mode 100644 index 00000000..baf8d3cd --- /dev/null +++ b/src/DemoUI/src/Controllers/CountryController.php @@ -0,0 +1,46 @@ +getSupportedCountries(false); + + $this->outputDtoEntities($supportedCountries); + } + + /** + * Returns list of Packlink supported countries. + */ + public function getShippingCountries() + { + /** @var CountryService $countryService */ + $countryService = ServiceRegister::getService(CountryService::CLASS_NAME); + $result = array(); + + /** + * @var string $code + * @var \Packlink\BusinessLogic\Country\Country $country + */ + foreach ($countryService->getSupportedCountries() as $code => $country) { + $result[] = array('value' => $code, 'label' => $country->name); + } + + $this->output($result); + } +} \ No newline at end of file diff --git a/src/DemoUI/src/Controllers/DebugController.php b/src/DemoUI/src/Controllers/DebugController.php new file mode 100644 index 00000000..c512d927 --- /dev/null +++ b/src/DemoUI/src/Controllers/DebugController.php @@ -0,0 +1,74 @@ +controller = new CoreDebugController(); + } + + /** + * Gets the debug status and the download URL + */ + public function getStatus() + { + $this->output( + array( + 'status' => $this->controller->getStatus(), + 'downloadUrl' => UrlService::getEndpointUrl('Debug', 'getSystemInfo'), + ) + ); + } + + /** + * @param \Packlink\DemoUI\Controllers\Models\Request $request + */ + public function setStatus(Request $request) + { + $data = $request->getPayload(); + $this->controller->setStatus((bool)$data['status']); + } + + /** + * Gets the system info file. + * + * @throws \Exception + */ + public function getSystemInfo() + { + $filePath = $this->controller->getSystemInfo(); + + header('Content-Description: File Transfer'); + header('Content-Type: application/octet-stream'); + header( + 'Content-Disposition: attachment; filename=' . CoreDebugController::SYSTEM_INFO_FILE_NAME + ); + header('Expires: 0'); + header('Cache-Control: must-revalidate'); + header('Pragma: public'); + header('Content-Length: ' . filesize($filePath)); + readfile($filePath); + + http_response_code(200); + die(); + } +} \ No newline at end of file diff --git a/src/DemoUI/src/Controllers/DefaultParcelController.php b/src/DemoUI/src/Controllers/DefaultParcelController.php new file mode 100644 index 00000000..ce4cee12 --- /dev/null +++ b/src/DemoUI/src/Controllers/DefaultParcelController.php @@ -0,0 +1,53 @@ +controller = new DefaultParcelControllerBase(); + } + + /** + * Gets default parcel + */ + public function getDefaultParcel() + { + $parcel = $this->controller->getDefaultParcel(); + + $this->output($parcel ? $parcel->toArray() : array()); + } + + /** + * Sets default parcel. + * + * @param \Packlink\DemoUI\Controllers\Models\Request $request + * + * @throws \Exception + */ + public function setDefaultParcel(Request $request) + { + $data = $request->getPayload(); + + $this->controller->setDefaultParcel($data); + + $this->getDefaultParcel(); + } +} \ No newline at end of file diff --git a/src/DemoUI/src/Controllers/DefaultWarehouseController.php b/src/DemoUI/src/Controllers/DefaultWarehouseController.php new file mode 100644 index 00000000..2bd24b40 --- /dev/null +++ b/src/DemoUI/src/Controllers/DefaultWarehouseController.php @@ -0,0 +1,82 @@ +getWarehouse(); + + $this->output($warehouse ? $warehouse->toArray() : array()); + } + + /** + * Sets the default warehouse. + * + * @param \Packlink\DemoUI\Controllers\Models\Request $request + * + * @throws \Logeecom\Infrastructure\TaskExecution\Exceptions\QueueStorageUnavailableException + * @throws \Packlink\BusinessLogic\DTO\Exceptions\FrontDtoNotRegisteredException + * @throws \Packlink\BusinessLogic\DTO\Exceptions\FrontDtoValidationException + */ + public function setDefaultWarehouse(Request $request) + { + /** @var WarehouseService $warehouseService */ + $warehouseService = ServiceRegister::getService(WarehouseService::CLASS_NAME); + $warehouseService->updateWarehouseData($request->getPayload()); + + $this->getDefaultWarehouse(); + } + + /** + * Gets the list of supported countries. + */ + public function getSupportedCountries() + { + /** @var WarehouseCountryService $countryService */ + $countryService = ServiceRegister::getService(WarehouseCountryService::CLASS_NAME); + $supportedCountries = $countryService->getSupportedCountries(false); + + $this->outputDtoEntities($supportedCountries); + } + + /** + * Searches for postal codes. + * + * @param \Packlink\DemoUI\Controllers\Models\Request $request + */ + public function searchPostalCodes(Request $request) + { + $data = $request->getPayload(); + + if (empty($data['query']) || empty($data['country'])) { + return; + } + + /** @var LocationService $locationService */ + $locationService = ServiceRegister::getService(LocationService::CLASS_NAME); + + try { + $locations = $locationService->searchLocations($data['country'], $data['query']); + $this->outputDtoEntities($locations); + } catch (\Exception $e) { + } + } +} \ No newline at end of file diff --git a/src/DemoUI/src/Controllers/Index.php b/src/DemoUI/src/Controllers/Index.php new file mode 100644 index 00000000..8fd2ad2c --- /dev/null +++ b/src/DemoUI/src/Controllers/Index.php @@ -0,0 +1,16 @@ +handleAction(); \ No newline at end of file diff --git a/src/DemoUI/src/Controllers/LoginController.php b/src/DemoUI/src/Controllers/LoginController.php new file mode 100644 index 00000000..0a296a20 --- /dev/null +++ b/src/DemoUI/src/Controllers/LoginController.php @@ -0,0 +1,59 @@ +getPayload(); + $apiKey = !empty($payload['apiKey']) ? $payload['apiKey'] : null; + $controller = new \Packlink\BusinessLogic\Controllers\LoginController(); + + $success = $controller->login($apiKey); + if ($success) { + // this is only for the Demo app because there is no task runner + $task = new UpdateShippingServicesTask(); + $task->execute(); + } + + $this->output(array('success' => $success)); + } + + /** + * Terminates the session. + */ + public function logout() + { + session_destroy(); + + http_response_code(302); + header('Location: ' . UrlService::getHomepage()); + } +} \ No newline at end of file diff --git a/src/DemoUI/src/Controllers/Models/Request.php b/src/DemoUI/src/Controllers/Models/Request.php new file mode 100644 index 00000000..e548d815 --- /dev/null +++ b/src/DemoUI/src/Controllers/Models/Request.php @@ -0,0 +1,75 @@ +query = $query; + $this->payload = $payload; + $this->headers = $headers; + } + + /** + * @param string|null $key + * + * @return string|null + */ + public function getQuery($key = null) + { + if ($key !== null && isset($this->query[$key])) { + return $this->query[$key]; + } + + return null; + } + + /** + * @return array + */ + public function getPayload() + { + return $this->payload; + } + + /** + * @param string|null $key + * + * @return array + */ + public function getHeaders($key = null) + { + if ($key !== null && isset($this->headers[$key])) { + return $this->headers[$key]; + } + + return $this->headers; + } +} \ No newline at end of file diff --git a/src/DemoUI/src/Controllers/ModuleStateController.php b/src/DemoUI/src/Controllers/ModuleStateController.php new file mode 100644 index 00000000..541371cd --- /dev/null +++ b/src/DemoUI/src/Controllers/ModuleStateController.php @@ -0,0 +1,26 @@ +output($controller->getCurrentState()->toArray()); + } +} \ No newline at end of file diff --git a/src/DemoUI/src/Controllers/OnboardingController.php b/src/DemoUI/src/Controllers/OnboardingController.php new file mode 100644 index 00000000..85913cfa --- /dev/null +++ b/src/DemoUI/src/Controllers/OnboardingController.php @@ -0,0 +1,21 @@ +output($controller->getCurrentState()->toArray()); + } +} diff --git a/src/DemoUI/src/Controllers/OrderStatusMappingController.php b/src/DemoUI/src/Controllers/OrderStatusMappingController.php new file mode 100644 index 00000000..df1fe71f --- /dev/null +++ b/src/DemoUI/src/Controllers/OrderStatusMappingController.php @@ -0,0 +1,74 @@ +baseController = new OrderStatusMappingControllerBase(); + } + + /** + * Gets system order statuses and saved mappings. + */ + public function getMappingAndStatuses() + { + $mappings = $this->baseController->getMappings(); + $packlinkStatuses = $this->baseController->getPacklinkStatuses(); + $systemStatuses = $this->getSystemOrderStatuses(); + + $this->output( + array( + 'systemName' => $this->getConfigService()->getIntegrationName(), + 'mappings' => $mappings, + 'orderStatuses' => $systemStatuses, + 'packlinkStatuses' => $packlinkStatuses, + ) + ); + } + + /** + * Saves order status mappings. + * + * @param \Packlink\DemoUI\Controllers\Models\Request $request + */ + public function setMappings(Request $request) + { + $this->baseController->setMappings($request->getPayload()); + } + + /** + * Gets system order statuses. + * + * @return array + */ + private function getSystemOrderStatuses() + { + return array( + '' => Translator::translate('orderStatusMapping.none'), + 'open' => 'open', + 'await' => 'awaiting payment', + 'paid' => 'paid', + 'proc' => 'processing', + 'done' => 'completed', + ); + } +} diff --git a/src/DemoUI/src/Controllers/RegistrationController.php b/src/DemoUI/src/Controllers/RegistrationController.php new file mode 100644 index 00000000..2de11171 --- /dev/null +++ b/src/DemoUI/src/Controllers/RegistrationController.php @@ -0,0 +1,71 @@ +controller = new RegistrationControllerBase(); + } + + /** + * Handles GET request. + * + * @param \Packlink\DemoUI\Controllers\Models\Request $request + */ + public function get(Request $request) + { + $this->output($this->controller->getRegisterData($request->getQuery('country'))); + } + + /** + * Handles POST request. + * + * @param \Packlink\DemoUI\Controllers\Models\Request $request + * + * @throws \Logeecom\Infrastructure\Http\Exceptions\HttpAuthenticationException + * @throws \Logeecom\Infrastructure\Http\Exceptions\HttpCommunicationException + * @throws \Logeecom\Infrastructure\Http\Exceptions\HttpRequestException + * @throws \Logeecom\Infrastructure\ORM\Exceptions\RepositoryNotRegisteredException + * @throws \Logeecom\Infrastructure\TaskExecution\Exceptions\QueueStorageUnavailableException + * @throws \Packlink\BusinessLogic\DTO\Exceptions\FrontDtoNotRegisteredException + * @throws \Packlink\BusinessLogic\DTO\Exceptions\FrontDtoValidationException + * @throws \Packlink\BusinessLogic\Registration\Exceptions\UnableToRegisterAccountException + */ + public function post(Request $request) + { + $payload = $request->getPayload(); + $payload['ecommerces'] = array('Test'); + + $success = $this->controller->register($payload); + if ($success) { + // this is only for the Demo app because there is no task runner + $task = new UpdateShippingServicesTask(); + $task->execute(); + } + + $this->output(array('success' => $success)); + } +} diff --git a/src/DemoUI/src/Controllers/ResolverController.php b/src/DemoUI/src/Controllers/ResolverController.php new file mode 100644 index 00000000..40538adf --- /dev/null +++ b/src/DemoUI/src/Controllers/ResolverController.php @@ -0,0 +1,87 @@ +getQueryParams(); + $payload = json_decode(file_get_contents('php://input'), true) ?: array(); + $headers = getallheaders(); + + $controllerClass = __NAMESPACE__ . '\\' . $controllerName . 'Controller'; + $controller = new $controllerClass; + + header('Content-Type: application/json'); + + if (!$controller->isAuthenticated()) { + http_response_code(401); + echo json_encode( + array( + 'success' => false, + 'error' => 'Unauthorized.', + ) + ); + + return; + } + + try { + if (method_exists($controller, $action)) { + $controller->$action(new Request($query, $payload, $headers)); + } else { + throw new \RuntimeException("Controller $controllerName does not implement action $action."); + } + } catch (FrontDtoValidationException $e) { + http_response_code(400); + $result = array( + 'success' => false, + 'messages' => array(), + ); + foreach ($e->getValidationErrors() as $error) { + $result['messages'][] = $error->toArray(); + } + + echo json_encode($result); + } catch (\Exception $e) { + http_response_code(400); + + echo json_encode( + array( + 'success' => false, + 'error' => $e->getMessage(), + ) + ); + } + } + + /** + * @return array + */ + private function getQueryParams() + { + $result = array(); + + foreach (array_keys($_GET) as $arrayKey) { + if ($arrayKey !== 'controller' && $arrayKey !== 'action') { + $result[$arrayKey] = $_GET[$arrayKey]; + } + } + + return $result; + } +} \ No newline at end of file diff --git a/src/DemoUI/src/Controllers/ShippingCountriesController.php b/src/DemoUI/src/Controllers/ShippingCountriesController.php new file mode 100644 index 00000000..1a0885ba --- /dev/null +++ b/src/DemoUI/src/Controllers/ShippingCountriesController.php @@ -0,0 +1,20 @@ +controller = new ShippingMethodController(); + } + + /** + * Gets active services. + */ + public function getActive() + { + $this->outputDtoEntities($this->controller->getActive()); + } + + /** + * Gets inactive services. + */ + public function getInactive() + { + $this->outputDtoEntities($this->controller->getInactive()); + } + + /** + * Gets the status of the get services task auto configuration. + */ + public function getTaskStatus() + { + if (count($this->controller->getAll()) > 0) { + $this->output(array('status' => QueueItem::COMPLETED)); + + return; + } + + try { + $controller = new UpdateShippingServicesTaskStatusController(); + $status = $controller->getLastTaskStatus(); + } catch (BaseException $e) { + $status = QueueItem::FAILED; + } + + $this->output(array('status' => $status)); + } + + /** + * Gets a single service. + * + * @param \Packlink\DemoUI\Controllers\Models\Request $request + */ + public function getService(Request $request) + { + $method = $this->controller->getShippingMethod((int)$request->getQuery('id')); + + $this->output($method ? $method->toArray() : array()); + } + + /** + * @param \Packlink\DemoUI\Controllers\Models\Request $request + */ + public function deactivate(Request $request) + { + $payload = $request->getPayload(); + + $this->output(array('status' => $this->controller->deactivate($payload['id']))); + } + + /** + * A mockup for getting system tax classes. + * + * @throws \Packlink\BusinessLogic\DTO\Exceptions\FrontDtoValidationException + */ + public function getTaxClasses() + { + $taxClass1 = TaxClass::fromArray(array('label' => 'Full Rate (20%)', 'value' => 1)); + $taxClass2 = TaxClass::fromArray(array('label' => 'Half Rate (10%)', 'value' => 2)); + $taxClass3 = TaxClass::fromArray(array('label' => 'Tax Free', 'value' => 0)); + + $this->outputDtoEntities(array($taxClass1, $taxClass2, $taxClass3)); + } + + /** + * @param \Packlink\DemoUI\Controllers\Models\Request $request + * + * @throws \Packlink\BusinessLogic\DTO\Exceptions\FrontDtoValidationException + */ + public function save(Request $request) + { + $shippingMethod = ShippingMethodConfiguration::fromArray($request->getPayload()); + $response = $this->controller->save($shippingMethod); + + $this->output($response ? $response->toArray() : array()); + } + + /** + * Disables shop carriers. + */ + public function disableCarriers() + { + /** @var ShopShippingMethodService $carrierService */ + $carrierService = ServiceRegister::getService(ShopShippingMethodService::CLASS_NAME); + if ($carrierService->disableShopServices()) { + $this->output( + array( + 'success' => true, + 'message' => Translator::translate('shippingServices.successfullyDisabledShippingMethods'), + ) + ); + } else { + throw new \RuntimeException(Translator::translate('shippingServices.failedToDisableShippingMethods')); + } + } +} \ No newline at end of file diff --git a/src/DemoUI/src/Repository/SessionRepository.php b/src/DemoUI/src/Repository/SessionRepository.php new file mode 100644 index 00000000..82117968 --- /dev/null +++ b/src/DemoUI/src/Repository/SessionRepository.php @@ -0,0 +1,75 @@ +ensureStorage(); + $this->lastId = count($_SESSION['storage']); + } + + /** + * @inheritDoc + */ + protected function getStorage() + { + return $_SESSION['storage']; + } + + /** + * @inheritDoc + */ + protected function saveToStorage($key, $item) + { + $_SESSION['storage'][$key] = $item; + } + + /** + * @inheritDoc + */ + protected function deleteFromStorage($key) + { + unset($_SESSION['storage'][$key]); + } + + /** + * Generates a new ID. + * + * @return int + */ + protected function generateId() + { + return ++$this->lastId; + } + + /** + * Creates an array for storage in the session. + */ + private function ensureStorage() + { + if (!array_key_exists('storage', $_SESSION)) { + $_SESSION['storage'] = array(); + } + } +} \ No newline at end of file diff --git a/src/DemoUI/src/Services/BusinessLogic/CarrierService.php b/src/DemoUI/src/Services/BusinessLogic/CarrierService.php new file mode 100644 index 00000000..fa29e5dd --- /dev/null +++ b/src/DemoUI/src/Services/BusinessLogic/CarrierService.php @@ -0,0 +1,95 @@ +trackingCodes)) { + return; + } + } + + /** + * @inheritDoc + */ + public function updateShipmentStatus($orderId, $shippingStatus) + { + + } +} diff --git a/src/DemoUI/src/Services/Infrastructure/LoggerService.php b/src/DemoUI/src/Services/Infrastructure/LoggerService.php new file mode 100644 index 00000000..af362df5 --- /dev/null +++ b/src/DemoUI/src/Services/Infrastructure/LoggerService.php @@ -0,0 +1,24 @@ + + + + + + Demo UI + + + + + + + + + +
+
+ +
+
+ +
+ +
+
+
+ + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/DemoUI/src/Views/resources/.gitkeep b/src/DemoUI/src/Views/resources/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/src/Infrastructure/AutoTest/AutoTestService.php b/src/Infrastructure/AutoTest/AutoTestService.php index c65424b1..e9357c7e 100644 --- a/src/Infrastructure/AutoTest/AutoTestService.php +++ b/src/Infrastructure/AutoTest/AutoTestService.php @@ -21,6 +21,8 @@ */ class AutoTestService { + const CLASS_NAME = __CLASS__; + /** * Configuration service instance. * diff --git a/src/Infrastructure/Configuration/Configuration.php b/src/Infrastructure/Configuration/Configuration.php index feff2a8b..63951b06 100644 --- a/src/Infrastructure/Configuration/Configuration.php +++ b/src/Infrastructure/Configuration/Configuration.php @@ -18,6 +18,13 @@ */ abstract class Configuration extends Singleton { + /** + * Current language. + * + * @var string + */ + private static $currentLanguage; + /** * Fully qualified name of this interface. */ @@ -55,6 +62,26 @@ abstract class Configuration extends Singleton */ protected $repository; + /** + * Retrieves current language. + * + * @return string + */ + public static function getCurrentLanguage() + { + return self::$currentLanguage; + } + + /** + * Sets current language. + * + * @param string $currentLanguage + */ + public static function setCurrentLanguage($currentLanguage) + { + self::$currentLanguage = $currentLanguage; + } + /** * Retrieves integration name. * diff --git a/src/Infrastructure/TaskExecution/QueueItem.php b/src/Infrastructure/TaskExecution/QueueItem.php index 49c85d8a..8e0b4297 100644 --- a/src/Infrastructure/TaskExecution/QueueItem.php +++ b/src/Infrastructure/TaskExecution/QueueItem.php @@ -780,7 +780,7 @@ public function inflate(array $data) */ public static function getAvailablePriorities() { - return array(Priority::LOW, Priority::NORMAL, Priority::HIGH); + return array(Priority::HIGH, Priority::NORMAL, Priority::LOW); } /** diff --git a/tests/BusinessLogic/Common/BaseTestWithServices.php b/tests/BusinessLogic/Common/BaseTestWithServices.php index be39a91f..e469e3da 100644 --- a/tests/BusinessLogic/Common/BaseTestWithServices.php +++ b/tests/BusinessLogic/Common/BaseTestWithServices.php @@ -24,6 +24,7 @@ use Packlink\BusinessLogic\DTO\ValidationError; use Packlink\BusinessLogic\Http\DTO\ParcelInfo; use Packlink\BusinessLogic\Http\Proxy; +use Packlink\BusinessLogic\Language\TranslationService; use Packlink\BusinessLogic\Warehouse\Warehouse; use Packlink\BusinessLogic\Warehouse\WarehouseService; @@ -113,6 +114,13 @@ function () use ($wakeupService) { } ); + TestServiceRegister::registerService( + \Packlink\BusinessLogic\Language\Interfaces\TranslationService::CLASS_NAME, + function () { + return new TranslationService(); + } + ); + TestRepositoryRegistry::registerRepository(QueueItem::CLASS_NAME, MemoryQueueItemRepository::THIS_CLASS_NAME); TestFrontDtoFactory::register(Warehouse::CLASS_KEY, TestWarehouse::CLASS_NAME); diff --git a/tests/BusinessLogic/Common/TestComponents/AutoTest/MockAutoTestService.php b/tests/BusinessLogic/Common/TestComponents/AutoTest/MockAutoTestService.php new file mode 100644 index 00000000..264614da --- /dev/null +++ b/tests/BusinessLogic/Common/TestComponents/AutoTest/MockAutoTestService.php @@ -0,0 +1,46 @@ +callHistory[] = 'startAutoTest'; + + if ($this->shouldFail) { + throw new RuntimeException($this->failureMessage); + } + + return $this->startAutoTestResult; + } + + public function stopAutoTestMode($loggerInitializerDelegate) + { + $this->callHistory[] = 'stopAutoTestMode'; + + if ($this->shouldFail) { + throw new RuntimeException($this->failureMessage); + } + } + + public function getAutoTestTaskStatus($queueItemId = 0) + { + $this->callHistory[] = 'getAutoTestTaskStatus'; + + if ($this->shouldFail) { + throw new RuntimeException($this->failureMessage); + } + + return $this->getAutoTestTaskStatusResult; + } +} \ No newline at end of file diff --git a/tests/BusinessLogic/Common/TestComponents/Locations/MockLocationService.php b/tests/BusinessLogic/Common/TestComponents/Locations/MockLocationService.php new file mode 100644 index 00000000..84243d04 --- /dev/null +++ b/tests/BusinessLogic/Common/TestComponents/Locations/MockLocationService.php @@ -0,0 +1,26 @@ +callHistory[] = array('searchLocations' => array($country, $query)); + + if ($this->shouldFail) { + throw new RuntimeException($this->failMessage); + } + + return $this->searchLocationsResult; + } +} \ No newline at end of file diff --git a/tests/BusinessLogic/Common/TestComponents/Registration/MockCountryService.php b/tests/BusinessLogic/Common/TestComponents/Registration/MockCountryService.php new file mode 100644 index 00000000..feb7427e --- /dev/null +++ b/tests/BusinessLogic/Common/TestComponents/Registration/MockCountryService.php @@ -0,0 +1,20 @@ +callHistory[] = array('getSupportedCountries' => array($associative)); + + return static::$supportedCountries; + } +} \ No newline at end of file diff --git a/tests/BusinessLogic/Common/TestComponents/Warehouse/MockWarehouseService.php b/tests/BusinessLogic/Common/TestComponents/Warehouse/MockWarehouseService.php new file mode 100644 index 00000000..c8f39b61 --- /dev/null +++ b/tests/BusinessLogic/Common/TestComponents/Warehouse/MockWarehouseService.php @@ -0,0 +1,28 @@ +callHistory[] = array('getWarehouse' => array($createIfNotExist)); + + return $this->getWarehouseResult; + } + + public function updateWarehouseData(array $payload) + { + $this->callHistory[] = array('updateWarehouseData' => array($payload)); + + return $this->updateWarehouseDataResult; + } +} \ No newline at end of file diff --git a/tests/BusinessLogic/Controllers/AutoTestControllerTest.php b/tests/BusinessLogic/Controllers/AutoTestControllerTest.php new file mode 100644 index 00000000..f1621c1e --- /dev/null +++ b/tests/BusinessLogic/Controllers/AutoTestControllerTest.php @@ -0,0 +1,111 @@ +service = new MockAutoTestService(); + + $me = $this; + + TestServiceRegister::registerService( + AutoTestService::CLASS_NAME, + function () use ($me) { + return $me->service; + } + ); + + TestRepositoryRegistry::registerRepository(LogData::getClassName(), MemoryRepository::getClassName()); + + $this->controller = new AutoTestController(); + } + + public function testStartMethodCall() + { + // act + $this->controller->start(); + + // assert + $this->assertEquals(array('startAutoTest'), $this->service->callHistory); + } + + public function testStartSuccess() + { + // arrange + $expected = array('success' => true, 'itemId' => $this->service->startAutoTestResult); + + // act + $result = $this->controller->start(); + + // assert + $this->assertEquals($expected, $result); + } + + public function testStartFailed() + { + // arrange + $this->service->shouldFail = true; + $expected = array('success' => false, 'error' => $this->service->failureMessage); + + // act + $result = $this->controller->start(); + + // assert + $this->assertEquals($expected, $result); + } + + public function testStop() + { + // act + $this->controller->stop(function () {}); + + // assert + $this->assertEquals(array('stopAutoTestMode'), $this->service->callHistory); + } + + public function testCheckStatusMethodCall() + { + // arrange + $this->service->getAutoTestTaskStatusResult = new AutoTestStatus('test', true, 'Test', array()); + + // act + $this->controller->checkStatus(1); + + // assert + $this->assertEquals(array('getAutoTestTaskStatus'), $this->service->callHistory); + } + + public function testCheckStatusMethodResult() + { + // arrange + $status = new AutoTestStatus('test', true, 'Test', array()); + $this->service->getAutoTestTaskStatusResult = $status; + $expected = array( + 'finished' => $status->finished, + 'error' => $status->error, + 'logs' => $status->logs, + ); + + // act + $result = $this->controller->checkStatus(1); + + // assert + $this->assertEquals($expected, $result); + } +} \ No newline at end of file diff --git a/tests/BusinessLogic/Controllers/DefaultParcelControllerTest.php b/tests/BusinessLogic/Controllers/DefaultParcelControllerTest.php new file mode 100644 index 00000000..4caa56c1 --- /dev/null +++ b/tests/BusinessLogic/Controllers/DefaultParcelControllerTest.php @@ -0,0 +1,176 @@ +setCurrentLocalTime($nowDateTime); + + TestServiceRegister::registerService( + Configuration::CLASS_NAME, + function () use ($configuration) { + return $configuration; + } + ); + + TestServiceRegister::registerService( + TimeProvider::CLASS_NAME, + function () use ($timeProvider) { + return $timeProvider; + } + ); + + TestServiceRegister::registerService( + EventBus::CLASS_NAME, + function () { + return EventBus::getInstance(); + } + ); + + TestServiceRegister::registerService( + Serializer::CLASS_NAME, + function () { + return new NativeSerializer(); + } + ); + + TestServiceRegister::registerService( + QueueService::CLASS_NAME, + function () use ($queue) { + return $queue; + } + ); + + TestServiceRegister::registerService( + TaskRunnerWakeup::CLASS_NAME, + function () use ($taskRunnerStarter) { + return $taskRunnerStarter; + } + ); + $this->defaultParcelController = new DefaultParcelController(); + } + + /** + * Tests the case when auth key is not set. + */ + public function testDefaultParcelGet() + { + $this->defaultParcelController->setDefaultParcel( + array( + 'weight' => 10, + 'width' => 10, + 'length' => 10, + 'height' => 10, + ) + ); + + $result = $this->defaultParcelController->getDefaultParcel(); + + $this->assertEquals(10, $result->weight); + $this->assertEquals(10, $result->width); + $this->assertEquals(10, $result->length); + $this->assertEquals(10, $result->height); + } + + /** + * Tests the case when auth key is set and default warehouse is not set. + */ + public function testDefaultParcelSetValid() + { + $exceptionThrown = false; + + try { + $this->defaultParcelController->setDefaultParcel( + array( + 'weight' => 10, + 'width' => 10, + 'length' => 10, + 'height' => 10, + ) + ); + } catch (Exception $ex) { + $exceptionThrown = true; + } + + $this->assertNotTrue($exceptionThrown); + } + + /** + * Tests the case when auth key is set and default warehouse is not set. + */ + public function testDefaultParcelSetInvalid() + { + $exceptionThrown = false; + + try { + $this->defaultParcelController->setDefaultParcel( + array( + 'weight' => 'asdasds', + 'width' => 10, + 'length' => 10, + 'height' => 10, + ) + ); + } catch (Exception $ex) { + $exceptionThrown = true; + } + + $this->assertTrue($exceptionThrown); + } +} diff --git a/tests/BusinessLogic/Controllers/LocationsControllerTest.php b/tests/BusinessLogic/Controllers/LocationsControllerTest.php new file mode 100644 index 00000000..e935f33b --- /dev/null +++ b/tests/BusinessLogic/Controllers/LocationsControllerTest.php @@ -0,0 +1,141 @@ +testShopShippingMethodService = new TestShopShippingMethodService(); + + $me = $this; + + TestServiceRegister::registerService( + ShopShippingMethodService::CLASS_NAME, + function () use ($me) { + return $me->testShopShippingMethodService; + } + ); + + $this->shippingMethodService = ShippingMethodService::getInstance(); + TestServiceRegister::registerService( + ShippingMethodService::CLASS_NAME, + function () use ($me) { + return $me->shippingMethodService; + } + ); + + $this->service = MockLocationService::getInstance(); + TestServiceRegister::registerService( + LocationService::CLASS_NAME, + function () use ($me) { + return $me->service; + } + ); + + $this->controller = new LocationsController(); + } + + public function testEmptyCountry() + { + // arrange + $payload = array('query' => 'test'); + + // act + $result = $this->controller->searchLocations($payload); + + // assert + $this->assertEmpty($result); + } + + public function testEmptyQuery() + { + // arrange + $payload = array('country' => 'test'); + + // act + $result = $this->controller->searchLocations($payload); + + // assert + $this->assertEmpty($result); + } + + public function testEmptyCountryAndQuery() + { + // arrange + $payload = array(); + + // act + $result = $this->controller->searchLocations($payload); + + // assert + $this->assertEmpty($result); + } + + public function testMethodCalls() + { + // arrange + $payload = array('country' => 'test country', 'query' => 'test query'); + $expected = array(array('searchLocations' => array('test country', 'test query'))); + + // act + $this->controller->searchLocations($payload); + + // assert + $this->assertEquals($expected, $this->service->callHistory); + } + + public function testServiceFailed() + { + // arrange + $this->service->shouldFail = true; + $payload = array('country' => 'test country', 'query' => 'test query'); + + // act + $result = $this->controller->searchLocations($payload); + + // assert + $this->assertEmpty($result); + } + + public function testResult() + { + // arrange + $this->service->searchLocationsResult = array('t1', 't2', 't3'); + $payload = array('country' => 'test country', 'query' => 'test query'); + + // act + $result = $this->controller->searchLocations($payload); + + // assert + $this->assertEquals($this->service->searchLocationsResult, $result); + } + + protected function tearDown() + { + parent::tearDown(); + + MockLocationService::resetInstance(); + } +} \ No newline at end of file diff --git a/tests/BusinessLogic/Controllers/ModuleStateControllerTest.php b/tests/BusinessLogic/Controllers/ModuleStateControllerTest.php new file mode 100644 index 00000000..283911b4 --- /dev/null +++ b/tests/BusinessLogic/Controllers/ModuleStateControllerTest.php @@ -0,0 +1,144 @@ +moduleStateController = new ModuleStateController(); + } + + /** + * Tests the case when auth key is not set. + */ + public function testStateNoAuthKey() + { + $configuration = new TestShopConfiguration(); + + new TestServiceRegister( + array( + Configuration::CLASS_NAME => function () use ($configuration) { + return $configuration; + } + ) + ); + + $result = $this->moduleStateController->getCurrentState(); + + $this->assertEquals(ModuleState::LOGIN_STATE, $result->state); + } + + /** + * Tests the case when auth key is set and default warehouse is not set. + */ + public function testStateAuthKeySet_DefaultWarehouseNotSet() + { + $configuration = new TestShopConfiguration(); + + $configuration->setAuthorizationToken('validToken'); + + new TestServiceRegister( + array( + Configuration::CLASS_NAME => function () use ($configuration) { + return $configuration; + } + ) + ); + + $result = $this->moduleStateController->getCurrentState(); + + $this->assertEquals(ModuleState::ONBOARDING_STATE, $result->state); + } + + /** + * Tests the case when auth key, default warehouse and parcel are set. + */ + public function testStateAuthKeySet_DefaultWarehouseAndParcelSet() + { + $configuration = new TestShopConfiguration(); + + $configuration->setAuthorizationToken('validToken'); + $this->createDefaultParcel($configuration); + $this->createDefaultWarehouse($configuration); + + new TestServiceRegister( + array( + Configuration::CLASS_NAME => function () use ($configuration) { + return $configuration; + } + ) + ); + + $result = $this->moduleStateController->getCurrentState(); + + $this->assertEquals(ModuleState::SERVICES_STATE, $result->state); + } + + /** + * @param TestShopConfiguration $configuration + */ + protected function createDefaultParcel(TestShopConfiguration $configuration) + { + $parcel = new ParcelInfo(); + $parcel->default = true; + $parcel->weight = 20; + $parcel->height = 20; + $parcel->length = 20; + $parcel->width = 20; + $configuration->setDefaultParcel($parcel); + } + + /** + * @param TestShopConfiguration $configuration + */ + protected function createDefaultWarehouse(TestShopConfiguration $configuration) + { + $warehouse = new Warehouse(); + $warehouse->default = true; + $warehouse->address = 'test 12'; + $warehouse->city = 'Test'; + $warehouse->alias = 'Test'; + $warehouse->company = 'Test'; + $warehouse->country = 'Test'; + $warehouse->email = 'test@test.com'; + $warehouse->id = '1'; + $warehouse->phone = '011/1111111'; + $warehouse->name = 'test'; + $warehouse->postalCode = '11111'; + $warehouse->surname = 'test'; + $configuration->setDefaultWarehouse($warehouse); + } +} diff --git a/tests/BusinessLogic/Controllers/RegistrationControllerTest.php b/tests/BusinessLogic/Controllers/RegistrationControllerTest.php new file mode 100644 index 00000000..ea006927 --- /dev/null +++ b/tests/BusinessLogic/Controllers/RegistrationControllerTest.php @@ -0,0 +1,53 @@ +registrationController = new RegistrationController(); + } + + public function testGetRegisterData() + { + $data = $this->registrationController->getRegisterData('ES'); + + $this->assertEquals('test@test.com', $data['email']); + $this->assertEquals('1111111111111', $data['phone']); + $this->assertEquals('localhost:7000', $data['source']); + } +} diff --git a/tests/BusinessLogic/Controllers/RegistrationRegionsControllerTest.php b/tests/BusinessLogic/Controllers/RegistrationRegionsControllerTest.php new file mode 100644 index 00000000..ca8570c1 --- /dev/null +++ b/tests/BusinessLogic/Controllers/RegistrationRegionsControllerTest.php @@ -0,0 +1,62 @@ +service = MockCountryService::getInstance(); + + $me = $this; + + TestServiceRegister::registerService( + CountryService::CLASS_NAME, + function () use ($me) { + return $me->service; + } + ); + + $this->controller = new RegistrationRegionsController(); + } + + public function testGetRegionsMethodCalls() + { + // arrange + $expected = array(array('getSupportedCountries' => array(false))); + + // act + $this->controller->getRegions(); + + // assert + $this->assertEquals($expected, $this->service->callHistory); + } + + public function testGetRegionsResult() + { + // arrange + MockCountryService::$supportedCountries = array('t1', 't2'); + + // act + $result = $this->controller->getRegions(); + + // assert + $this->assertEquals(MockCountryService::$supportedCountries, $result); + } + + protected function tearDown() + { + MockCountryService::resetInstance(); + } +} \ No newline at end of file diff --git a/tests/BusinessLogic/Controllers/ShippingMethodControllerTest.php b/tests/BusinessLogic/Controllers/ShippingMethodControllerTest.php index 8ce16cb9..6d0a58a2 100644 --- a/tests/BusinessLogic/Controllers/ShippingMethodControllerTest.php +++ b/tests/BusinessLogic/Controllers/ShippingMethodControllerTest.php @@ -5,17 +5,16 @@ use Logeecom\Infrastructure\ORM\RepositoryRegistry; use Logeecom\Tests\BusinessLogic\Common\BaseTestWithServices; use Logeecom\Tests\BusinessLogic\ShippingMethod\TestShopShippingMethodService; +use Logeecom\Tests\BusinessLogic\Tasks\UpdateShippingServicesTaskTest; use Logeecom\Tests\Infrastructure\Common\TestComponents\ORM\MemoryRepository; use Logeecom\Tests\Infrastructure\Common\TestServiceRegister; use Packlink\BusinessLogic\Controllers\DTO\ShippingMethodConfiguration; use Packlink\BusinessLogic\Controllers\DTO\ShippingMethodResponse; use Packlink\BusinessLogic\Controllers\ShippingMethodController; use Packlink\BusinessLogic\ShippingMethod\Interfaces\ShopShippingMethodService; -use Packlink\BusinessLogic\ShippingMethod\Models\FixedPricePolicy; -use Packlink\BusinessLogic\ShippingMethod\Models\PercentPricePolicy; use Packlink\BusinessLogic\ShippingMethod\Models\ShippingMethod; +use Packlink\BusinessLogic\ShippingMethod\Models\ShippingPricePolicy; use Packlink\BusinessLogic\ShippingMethod\ShippingMethodService; -use Logeecom\Tests\BusinessLogic\Tasks\UpdateShippingServicesTaskTest; /** * Class ShippingMethodControllerTest @@ -98,7 +97,7 @@ public function testSaveChangeNameAndShowImage() $shipment->id = $first->id; $shipment->name = 'First name test'; $shipment->showLogo = !$first->showLogo; - $shipment->pricePolicy = $first->pricePolicy; + $shipment->pricingPolicies = $first->pricingPolicies; $shipment->isShipToAllCountries = true; $shipment->shippingCountries = array(); @@ -109,17 +108,26 @@ public function testSaveChangeNameAndShowImage() $this->assertEquals($shipment->id, $model->id); $this->assertEquals($shipment->name, $model->name); $this->assertEquals($shipment->showLogo, $model->showLogo); - $this->assertEquals($shipment->pricePolicy, $model->pricePolicy); + $this->assertEquals($shipment->pricingPolicies, $model->pricingPolicies); } + /** + * @throws \Packlink\BusinessLogic\DTO\Exceptions\FrontDtoValidationException + */ public function testShippingMethodConfigurationToArray() { $instance = new ShippingMethodConfiguration(); $instance->id = 12; $instance->name = 'First name test'; $instance->showLogo = true; - $instance->pricePolicy = ShippingMethod::PRICING_POLICY_FIXED_PRICE_BY_WEIGHT; - $instance->fixedPriceByWeightPolicy[] = new FixedPricePolicy(0, 10, 12); + $instance->pricingPolicies[] = ShippingPricePolicy::fromArray( + array( + 'range_type' => ShippingPricePolicy::RANGE_PRICE, + 'from_price' => 0, + 'to_price' => 20, + 'pricing_policy' => ShippingPricePolicy::POLICY_PACKLINK, + ) + ); $data = $instance->toArray(); @@ -127,78 +135,46 @@ public function testShippingMethodConfigurationToArray() self::assertEquals($instance->id, $data['id']); self::assertEquals($instance->name, $data['name']); self::assertEquals($instance->showLogo, $data['showLogo']); - self::assertEquals($instance->pricePolicy, $data['pricePolicy']); - self::assertCount(1, $data['fixedPriceByWeightPolicy']); - self::assertEquals(0, $data['fixedPriceByWeightPolicy'][0]['from']); - self::assertEquals(10, $data['fixedPriceByWeightPolicy'][0]['to']); - self::assertEquals(12, $data['fixedPriceByWeightPolicy'][0]['amount']); - - $instance->fixedPriceByValuePolicy[] = new FixedPricePolicy(0, 100, 120); - $instance->pricePolicy = ShippingMethod::PRICING_POLICY_FIXED_PRICE_BY_VALUE; - $data = $instance->toArray(); - - self::assertCount(1, $data['fixedPriceByValuePolicy']); - self::assertEquals(0, $data['fixedPriceByValuePolicy'][0]['from']); - self::assertEquals(100, $data['fixedPriceByValuePolicy'][0]['to']); - self::assertEquals(120, $data['fixedPriceByValuePolicy'][0]['amount']); - - $instance->pricePolicy = ShippingMethod::PRICING_POLICY_PERCENT; - $instance->percentPricePolicy = new PercentPricePolicy(false, 10); - $data = $instance->toArray(); - - self::assertEquals(false, $data['percentPricePolicy']['increase']); - self::assertEquals(10, $data['percentPricePolicy']['amount']); + self::assertCount(1, $instance->pricingPolicies); } + /** + * @throws \Packlink\BusinessLogic\DTO\Exceptions\FrontDtoValidationException + */ public function testShippingMethodResponseToArray() { $instance = new ShippingMethodResponse(); $instance->id = 12; $instance->name = 'First name test'; - $instance->title = 'title'; + $instance->type = 'national'; $instance->carrierName = 'carrier'; $instance->deliveryDescription = 'description'; $instance->parcelOrigin = 'pick-up'; $instance->parcelDestination = 'drop-off'; $instance->logoUrl = 'url'; $instance->showLogo = false; - $instance->selected = false; - $instance->pricePolicy = ShippingMethod::PRICING_POLICY_FIXED_PRICE_BY_WEIGHT; - $instance->fixedPriceByWeightPolicy[] = new FixedPricePolicy(0, 10, 12); + $instance->pricingPolicies[] = ShippingPricePolicy::fromArray( + array( + 'range_type' => ShippingPricePolicy::RANGE_PRICE, + 'from_price' => 0, + 'to_price' => 20, + 'pricing_policy' => ShippingPricePolicy::POLICY_PACKLINK, + ) + ); $data = $instance->toArray(); self::assertNotEmpty($data); self::assertEquals($instance->id, $data['id']); self::assertEquals($instance->name, $data['name']); - self::assertEquals($instance->title, $data['title']); + self::assertEquals($instance->type, $data['type']); self::assertEquals($instance->carrierName, $data['carrierName']); self::assertEquals($instance->deliveryDescription, $data['deliveryDescription']); self::assertEquals($instance->parcelOrigin, $data['parcelOrigin']); self::assertEquals($instance->parcelDestination, $data['parcelDestination']); self::assertEquals($instance->logoUrl, $data['logoUrl']); self::assertEquals($instance->showLogo, $data['showLogo']); - self::assertEquals($instance->selected, $data['selected']); - self::assertEquals($instance->pricePolicy, $data['pricePolicy']); - self::assertCount(1, $data['fixedPriceByWeightPolicy']); - self::assertEquals(0, $data['fixedPriceByWeightPolicy'][0]['from']); - self::assertEquals(10, $data['fixedPriceByWeightPolicy'][0]['to']); - self::assertEquals(12, $data['fixedPriceByWeightPolicy'][0]['amount']); - - $instance->pricePolicy = ShippingMethod::PRICING_POLICY_FIXED_PRICE_BY_VALUE; - $instance->fixedPriceByValuePolicy[] = new FixedPricePolicy(0, 100, 120); - $data = $instance->toArray(); - self::assertCount(1, $data['fixedPriceByValuePolicy']); - self::assertEquals(0, $data['fixedPriceByValuePolicy'][0]['from']); - self::assertEquals(100, $data['fixedPriceByValuePolicy'][0]['to']); - self::assertEquals(120, $data['fixedPriceByValuePolicy'][0]['amount']); - - $instance->pricePolicy = ShippingMethod::PRICING_POLICY_PERCENT; - $instance->percentPricePolicy = new PercentPricePolicy(false, 10); - $data = $instance->toArray(); - - self::assertEquals(false, $data['percentPricePolicy']['increase']); - self::assertEquals(10, $data['percentPricePolicy']['amount']); + self::assertCount(1, $instance->pricingPolicies); } public function testSaveNoShippingMethod() @@ -207,7 +183,6 @@ public function testSaveNoShippingMethod() $shipment->id = 1235412; $shipment->name = 'First name test'; $shipment->showLogo = true; - $shipment->pricePolicy = 1; $this->assertNull($this->controller->save($shipment)); } @@ -215,15 +190,14 @@ public function testSaveNoShippingMethod() public function testSaveInvalidMissingProperty() { $shipment = new ShippingMethodConfiguration(); - $properties = array('id', 'name', 'showLogo', 'pricePolicy'); + $properties = array('id', 'name', 'showLogo', 'pricingPolicies'); $shipment->id = 1235412; $shipment->name = 'First name test'; $shipment->showLogo = true; - $shipment->pricePolicy = 1; foreach ($properties as $property) { $value = $shipment->$property; - unset($shipment->$property); + $shipment->$property = null; $this->assertNull($this->controller->save($shipment)); @@ -234,11 +208,10 @@ public function testSaveInvalidMissingProperty() public function testSaveInvalidPropertyWrongType() { $shipment = new ShippingMethodConfiguration(); - $properties = array('id' => 'abc', 'name' => true, 'showLogo' => 12.5, 'pricePolicy' => 'abc'); + $properties = array('id' => 'abc', 'name' => true, 'showLogo' => 12.5, 'pricingPolicies' => 'asdf'); $shipment->id = 1235412; $shipment->name = 'First name test'; $shipment->showLogo = true; - $shipment->pricePolicy = 1; foreach ($properties as $property => $value) { $oldValue = $shipment->$property; @@ -250,55 +223,6 @@ public function testSaveInvalidPropertyWrongType() } } - public function testSaveInvalidMissingPricePolicy() - { - $this->importShippingMethods(); - $all = $this->controller->getAll(); - $first = $all[0]; - $shipment = new ShippingMethodConfiguration(); - $shipment->id = $first->id; - $shipment->name = 'First name test'; - $shipment->showLogo = !$first->showLogo; - - $shipment->pricePolicy = ShippingMethod::PRICING_POLICY_PERCENT; - $this->assertNull($this->controller->save($shipment)); - - $shipment->pricePolicy = ShippingMethod::PRICING_POLICY_FIXED_PRICE_BY_WEIGHT; - $this->assertNull($this->controller->save($shipment)); - - $shipment->pricePolicy = ShippingMethod::PRICING_POLICY_FIXED_PRICE_BY_VALUE; - $this->assertNull($this->controller->save($shipment)); - } - - public function testSaveCorrectPricePolicy() - { - $this->importShippingMethods(); - $all = $this->controller->getAll(); - $first = $all[0]; - $shipment = new ShippingMethodConfiguration(); - $shipment->id = $first->id; - $shipment->name = 'First name test'; - $shipment->showLogo = !$first->showLogo; - $shipment->isShipToAllCountries = true; - $shipment->shippingCountries = array(); - - $shipment->pricePolicy = ShippingMethod::PRICING_POLICY_PERCENT; - $shipment->percentPricePolicy = new PercentPricePolicy(true, 0.1); - $this->assertNotNull($this->controller->save($shipment)); - - $shipment->pricePolicy = ShippingMethod::PRICING_POLICY_FIXED_PRICE_BY_WEIGHT; - $shipment->fixedPriceByWeightPolicy = array(); - $shipment->fixedPriceByWeightPolicy[] = new FixedPricePolicy(0, 1, 1); - $shipment->fixedPriceByWeightPolicy[] = new FixedPricePolicy(1, 2.5, 1.5); - $this->assertNotNull($this->controller->save($shipment)); - - $shipment->pricePolicy = ShippingMethod::PRICING_POLICY_FIXED_PRICE_BY_VALUE; - $shipment->fixedPriceByValuePolicy = array(); - $shipment->fixedPriceByValuePolicy[] = new FixedPricePolicy(0, 1, 1); - $shipment->fixedPriceByValuePolicy[] = new FixedPricePolicy(1, 2.5, 1.5); - $this->assertNotNull($this->controller->save($shipment)); - } - public function testActivate() { $this->importShippingMethods(); diff --git a/tests/BusinessLogic/Controllers/WarehouseControllerTest.php b/tests/BusinessLogic/Controllers/WarehouseControllerTest.php new file mode 100644 index 00000000..7c359dca --- /dev/null +++ b/tests/BusinessLogic/Controllers/WarehouseControllerTest.php @@ -0,0 +1,94 @@ +service = MockWarehouseService::getInstance(); + + $me = $this; + + TestServiceRegister::registerService( + WarehouseService::CLASS_NAME, + function () use ($me) { + return $me->service; + } + ); + + $this->controller = new WarehouseController(); + } + + public function testGetWarehouseMethodCalls() + { + // arrange + $expected = array(array('getWarehouse' => array(true))); + + // act + $this->controller->getWarehouse(); + + // assert + $this->assertEquals($expected, $this->service->callHistory); + } + + public function testGetWarehouseResult() + { + // arrange + $expected = new Warehouse(); + $expected->email = 'test'; + $this->service->getWarehouseResult = $expected; + + // act + $result = $this->controller->getWarehouse(); + + // assert + $this->assertEquals($expected, $result); + } + + public function testUpdateWarehouseMethodCalls() + { + // arrange + $payload = array('t1', 't2', 't3'); + $expected = array(array('updateWarehouseData' => array($payload))); + + // act + $this->controller->updateWarehouse($payload); + + // assert + $this->assertEquals($expected, $this->service->callHistory); + } + + public function testUpdateWarehouseResult() + { + // arrange + $expected = new Warehouse(); + $expected->email = 'test'; + $this->service->updateWarehouseDataResult = $expected; + + // act + $result = $this->controller->updateWarehouse(array()); + + // assert + $this->assertEquals($expected, $result); + } + + protected function tearDown() + { + parent::tearDown(); + + MockWarehouseService::resetInstance(); + } +} \ No newline at end of file diff --git a/tests/BusinessLogic/Country/CountryServiceTest.php b/tests/BusinessLogic/Country/CountryServiceTest.php index 2c981b68..66414f23 100644 --- a/tests/BusinessLogic/Country/CountryServiceTest.php +++ b/tests/BusinessLogic/Country/CountryServiceTest.php @@ -27,7 +27,7 @@ public function testGetSupportedCountries() $this->assertEquals('ES', $countries['ES']->code); $this->assertEquals('28001', $countries['ES']->postalCode); $this->assertEquals( - 'https://auth.packlink.com/es-ES/test-system/registro?platform_country=ES&platform=PRO', + 'https://auth.packlink.com/es-ES/test-system/registro?platform=PRO&platform_country=ES', $countries['ES']->registrationLink ); } diff --git a/tests/BusinessLogic/Dto/FrontDtoTest.php b/tests/BusinessLogic/Dto/FrontDtoTest.php index 5825a4ea..325d9382 100644 --- a/tests/BusinessLogic/Dto/FrontDtoTest.php +++ b/tests/BusinessLogic/Dto/FrontDtoTest.php @@ -2,6 +2,7 @@ namespace Logeecom\Tests\BusinessLogic\Dto; +use Logeecom\Tests\BusinessLogic\Common\BaseTestWithServices; use Logeecom\Tests\BusinessLogic\Common\TestComponents\Dto\EmptyFrontDto; use Logeecom\Tests\BusinessLogic\Common\TestComponents\Dto\FooDto; use Packlink\BusinessLogic\DTO\Exceptions\FrontDtoValidationException; @@ -11,7 +12,7 @@ * * @package Logeecom\Tests\BusinessLogic\Dto */ -class FrontDtoTest extends BaseDtoTest +class FrontDtoTest extends BaseTestWithServices { /** * @throws \Packlink\BusinessLogic\DTO\Exceptions\FrontDtoValidationException diff --git a/tests/BusinessLogic/Dto/ShippingPricePolicyDtoTest.php b/tests/BusinessLogic/Dto/ShippingPricePolicyDtoTest.php new file mode 100644 index 00000000..e2dd6a85 --- /dev/null +++ b/tests/BusinessLogic/Dto/ShippingPricePolicyDtoTest.php @@ -0,0 +1,312 @@ +getValidPolicies()); + $this->assertCount(6, $policies); + $this->assertEquals(ShippingPricePolicy::RANGE_PRICE, $policies[0]->rangeType); + $this->assertEquals(ShippingPricePolicy::RANGE_WEIGHT, $policies[1]->rangeType); + + // validate all fields + $this->assertNull($policies[0]->fromWeight); + $this->assertNull($policies[0]->toWeight); + $this->assertNull($policies[0]->changePercent); + $this->assertNull($policies[0]->fixedPrice); + $this->assertNull($policies[1]->fromPrice); + $this->assertNull($policies[1]->toPrice); + $this->assertEquals(ShippingPricePolicy::RANGE_PRICE_AND_WEIGHT, $policies[2]->rangeType); + $this->assertEquals(0.5, $policies[2]->fromPrice); + $this->assertEquals(20.98, $policies[2]->toPrice); + $this->assertEquals(0.05, $policies[2]->fromWeight); + $this->assertEquals(0.6, $policies[2]->toWeight); + $this->assertEquals(ShippingPricePolicy::POLICY_PACKLINK_ADJUST, $policies[2]->pricingPolicy); + $this->assertTrue($policies[2]->increase); + $this->assertEquals(54.248, $policies[2]->changePercent); + } + + public function testToArray() + { + /** @noinspection PhpUnhandledExceptionInspection */ + $policies = TestFrontDtoFactory::getFromBatch(ShippingPricePolicy::CLASS_KEY, $this->getValidPolicies()); + $policy = $policies[2]; + + $array = $policy->toArray(); + $this->assertEquals(ShippingPricePolicy::RANGE_PRICE_AND_WEIGHT, $array['range_type']); + $this->assertEquals(0.5, $array['from_price']); + $this->assertEquals(20.98, $array['to_price']); + $this->assertEquals(0.05, $array['from_weight']); + $this->assertEquals(0.6, $array['to_weight']); + $this->assertEquals(ShippingPricePolicy::POLICY_PACKLINK_ADJUST, $array['pricing_policy']); + $this->assertTrue($array['increase']); + $this->assertEquals(54.25, $array['change_percent']); + + // assert empty values + $array = $policies[0]->toArray(); + $this->assertNull($array['from_weight']); + $this->assertNull($array['to_weight']); + $this->assertNull($array['change_percent']); + $this->assertNull($array['fixed_price']); + + $array = $policies[1]->toArray(); + $this->assertNull($array['from_price']); + $this->assertNull($array['to_price']); + } + + /** + * @throws \Packlink\BusinessLogic\DTO\Exceptions\FrontDtoNotRegisteredException + */ + public function testBaseRequiredFieldsValidation() + { + $errors = null; + try { + TestFrontDtoFactory::get(ShippingPricePolicy::CLASS_KEY, array()); + } catch (FrontDtoValidationException $e) { + $errors = $e->getValidationErrors(); + } + + $this->assertCount(2, $errors, 'Price Range and Pricing Policy fields must be validated.'); + foreach ($errors as $error) { + $this->assertEquals(ValidationError::ERROR_REQUIRED_FIELD, $error->code); + } + } + + public function testPriceRangeValidation() + { + $policy = array( + 'range_type' => ShippingPricePolicy::RANGE_PRICE, + 'pricing_policy' => ShippingPricePolicy::POLICY_PACKLINK, + ); + + $this->assertInvalidField( + $policy, + 'from_price', + 'From price is required for price range.', + ValidationError::ERROR_REQUIRED_FIELD + ); + + $policy['from_price'] = -1; + $this->assertInvalidField($policy, 'from_price', 'Negative range should be validated.'); + + $policy['from_price'] = 10; + $this->assertNull($this->getErrors($policy), 'Pricing policy is valid without upper bound.'); + + $policy['to_price'] = -1; + $this->assertInvalidField($policy, 'to_price', 'Negative range should be validated.'); + + $policy['to_price'] = 5; + $this->assertInvalidField($policy, 'to_price', 'Upper boundary must be higher than the lower boundary.'); + + $policy['to_price'] = $policy['from_price']; + $this->assertInvalidField($policy, 'to_price', 'Upper boundary must be higher than the lower boundary.'); + + $policy['to_price'] = $policy['from_price'] + 1; + $this->assertNull($this->getErrors($policy), 'Pricing policy should be valid.'); + } + + public function testWeightRangeValidation() + { + $policy = array( + 'range_type' => ShippingPricePolicy::RANGE_WEIGHT, + 'pricing_policy' => ShippingPricePolicy::POLICY_PACKLINK, + ); + + $this->assertInvalidField( + $policy, + 'from_weight', + 'From price is required for price range.', + ValidationError::ERROR_REQUIRED_FIELD + ); + + $policy['from_weight'] = -1; + $this->assertInvalidField($policy, 'from_weight', 'Negative range should be validated.'); + + $policy['from_weight'] = 10; + $this->assertNull($this->getErrors($policy), 'Pricing policy is valid without upper bound.'); + + $policy['to_weight'] = -1; + $this->assertInvalidField($policy, 'to_weight', 'Negative range should be validated.'); + + $policy['to_weight'] = 5; + $this->assertInvalidField($policy, 'to_weight', 'Upper boundary must be higher than the lower boundary.'); + + $policy['to_weight'] = $policy['from_weight']; + $this->assertInvalidField($policy, 'to_weight', 'Upper boundary must be higher than the lower boundary.'); + + $policy['to_weight'] = $policy['from_weight'] + 1; + $this->assertNull($this->getErrors($policy), 'Pricing policy should be valid.'); + } + + public function testPriceAndWeightRangeValidation() + { + $policy = array( + 'range_type' => ShippingPricePolicy::RANGE_PRICE_AND_WEIGHT, + 'pricing_policy' => ShippingPricePolicy::POLICY_PACKLINK, + ); + + $this->assertCount(2, $this->getErrors($policy), 'Both from_price and from_weight are required'); + + $policy['from_weight'] = -1; + $policy['from_price'] = -1; + $this->assertCount(2, $this->getErrors($policy), 'Both from_price and from_weight must be positive'); + + $policy['from_price'] = 0; + $this->assertInvalidField($policy, 'from_weight', 'Negative range should be validated.'); + + $policy['from_price'] = -10; + $policy['from_weight'] = 0; + $this->assertInvalidField($policy, 'from_price', 'Negative range should be validated.'); + + $policy['from_weight'] = $policy['from_price'] = 10; + $this->assertNull($this->getErrors($policy), 'Pricing policy should be valid.'); + + $policy['to_weight'] = -1; + $this->assertInvalidField($policy, 'to_weight', 'Negative range should be validated.'); + $policy['to_weight'] = 5; + $this->assertInvalidField($policy, 'to_weight', 'Upper boundary must be higher than the lower boundary.'); + $policy['to_weight'] = $policy['from_weight']; + $this->assertInvalidField($policy, 'to_weight', 'Upper boundary must be higher than the lower boundary.'); + $policy['to_weight'] = $policy['from_weight'] + 1; + $this->assertNull($this->getErrors($policy), 'Pricing policy should be valid.'); + + unset($policy['to_weight']); + + $policy['to_price'] = -1; + $this->assertInvalidField($policy, 'to_price', 'Negative range should be validated.'); + $policy['to_price'] = 5; + $this->assertInvalidField($policy, 'to_price', 'Upper boundary must be higher than the lower boundary.'); + $policy['to_price'] = $policy['from_price']; + $this->assertInvalidField($policy, 'to_price', 'Upper boundary must be higher than the lower boundary.'); + $policy['to_price'] = $policy['from_price'] + 1; + $this->assertNull($this->getErrors($policy), 'Pricing policy should be valid.'); + + $policy['to_weight'] = $policy['from_weight'] + 1; + $this->assertNull($this->getErrors($policy), 'Pricing policy should be valid.'); + } + + public function testFixedPricePolicy() + { + $policy = array( + 'range_type' => ShippingPricePolicy::RANGE_PRICE, + 'from_price' => 0, + 'pricing_policy' => ShippingPricePolicy::POLICY_FIXED_PRICE, + ); + + $this->assertInvalidField( + $policy, + 'fixed_price', + 'Fixed price is required for price range.', + ValidationError::ERROR_REQUIRED_FIELD + ); + + $policy['fixed_price'] = -1; + $this->assertInvalidField($policy, 'fixed_price', 'Negative range should be validated.'); + + $policy['fixed_price'] = 0; + $this->assertNull($this->getErrors($policy), 'Fixed price can be 0'); + + $policy['fixed_price'] = 10; + $this->assertNull($this->getErrors($policy), 'Valid float should be accepted'); + } + + private function assertInvalidField($policy, $field, $error, $errorType = ValidationError::ERROR_INVALID_FIELD) + { + $errors = $this->getErrors($policy); + $this->assertCount(1, $errors, $error); + $this->assertEquals($errorType, $errors[0]->code); + $this->assertEquals($field, $errors[0]->field); + } + + private function getValidPolicies() + { + return array( + array( + 'range_type' => ShippingPricePolicy::RANGE_PRICE, + 'from_price' => 0, + 'to_price' => 20, + 'pricing_policy' => ShippingPricePolicy::POLICY_PACKLINK, + ), + array( + 'range_type' => ShippingPricePolicy::RANGE_WEIGHT, + 'from_weight' => 34, + 'to_weight' => 45, + 'pricing_policy' => ShippingPricePolicy::POLICY_PACKLINK, + ), + array( + 'range_type' => ShippingPricePolicy::RANGE_PRICE_AND_WEIGHT, + 'from_price' => 0.5, + 'to_price' => 20.98, + 'from_weight' => 0.05, + 'to_weight' => 0.6, + 'pricing_policy' => ShippingPricePolicy::POLICY_PACKLINK_ADJUST, + 'increase' => true, + 'change_percent' => 54.248, + ), + array( + 'range_type' => ShippingPricePolicy::RANGE_PRICE, + 'from_price' => 0, + 'to_price' => 20, + 'pricing_policy' => ShippingPricePolicy::POLICY_FIXED_PRICE, + 'fixed_price' => 43.98, + ), + array( + 'range_type' => ShippingPricePolicy::RANGE_PRICE, + 'from_price' => 0, + 'to_price' => 20, + 'pricing_policy' => ShippingPricePolicy::POLICY_PACKLINK_ADJUST, + 'increase' => true, + 'change_percent' => 35, + ), + array( + 'range_type' => ShippingPricePolicy::RANGE_PRICE, + 'from_price' => 0.5, + 'to_price' => 20.76, + 'pricing_policy' => ShippingPricePolicy::POLICY_PACKLINK_ADJUST, + 'increase' => false, + 'change_percent' => 56, + ), + ); + } + + /** + * @param array $policy + * + * @return array + */ + private function getErrors(array $policy) + { + try { + /** @noinspection PhpUnhandledExceptionInspection */ + TestFrontDtoFactory::get(ShippingPricePolicy::CLASS_KEY, $policy); + } catch (FrontDtoValidationException $e) { + return $e->getValidationErrors(); + } + + return null; + } +} diff --git a/tests/BusinessLogic/Language/TestTranslationService.php b/tests/BusinessLogic/Language/TestTranslationService.php new file mode 100644 index 00000000..ad26bc6a --- /dev/null +++ b/tests/BusinessLogic/Language/TestTranslationService.php @@ -0,0 +1,21 @@ +translationService = new TestTranslationService($baseFilePath); + + $configuration = new TestShopConfiguration(); + Configuration::setCurrentLanguage('de'); + + new TestServiceRegister( + array( + Configuration::CLASS_NAME => function () use ($configuration) { + return $configuration; + } + ) + ); + } + + /** + * Tests translation function when current language is not set in config service. + */ + public function testTranslateCurrentLanguageNotSet() + { + $configuration = new TestShopConfiguration(); + Configuration::setCurrentLanguage(null); + $logger = new TestShopLogger(); + $timeProvider = new TestTimeProvider(); + + new TestServiceRegister( + array( + Configuration::CLASS_NAME => function () use ($configuration) { + return $configuration; + }, + TimeProvider::CLASS_NAME => function () use ($timeProvider) { + return $timeProvider; + }, + ShopLoggerAdapter::CLASS_NAME => function () use ($logger) { + return $logger; + } + ) + ); + + $translation = $this->translationService->translate('testKey'); + + $this->assertStringStartsWith('testValueEn', $translation); + } + + /** + * Tests translation function when non existing key is tried to be translated for not supported language. + */ + public function testTranslateNotSupportedLanguage() + { + $configuration = new TestShopConfiguration(); + Configuration::setCurrentLanguage('rs'); + $logger = new TestShopLogger(); + $timeProvider = new TestTimeProvider(); + + new TestServiceRegister( + array( + Configuration::CLASS_NAME => function () use ($configuration) { + return $configuration; + }, + TimeProvider::CLASS_NAME => function () use ($timeProvider) { + return $timeProvider; + }, + ShopLoggerAdapter::CLASS_NAME => function () use ($logger) { + return $logger; + } + ) + ); + + $translation = $this->translationService->translate('testKey'); + + $this->assertStringStartsWith('testValueEn', $translation); + } + + /** + * Tests translation function when non existing key is tried to be translated. + */ + public function testTranslateNonExistingKey() + { + $nonExistingKey = 'noKey'; + $translation = $this->translationService->translate($nonExistingKey); + + $this->assertEquals($nonExistingKey, $translation); + } + + /** + * Tests translation function when non existing key in current language is tried to be translated. Key exists in the + * fallback language. + */ + public function testTranslateFallbackToEnglish() + { + $key = 'testKey1'; + $translation = $this->translationService->translate($key); + + $this->assertEquals('testValueEn1', $translation); + } + + /** + * Tests translation function when non existing key is tried to be translated. + */ + public function testTranslateToGerman() + { + $key = 'testKey'; + $translation = $this->translationService->translate($key); + + $this->assertEquals('testValueDe', $translation); + } + + /** + * Tests translation function with existing nested key with placeholders. + */ + public function testTranslateToGermanNestedKeyWithPlaceholders() + { + $key = 'namespace.nestedKeyWithPlaceholder'; + $translation = $this->translationService->translate($key, array(1, 2)); + + $this->assertEquals('Test1 1, test2 2.', $translation); + } +} diff --git a/tests/BusinessLogic/Language/Translations/de.json b/tests/BusinessLogic/Language/Translations/de.json new file mode 100644 index 00000000..718750dc --- /dev/null +++ b/tests/BusinessLogic/Language/Translations/de.json @@ -0,0 +1,6 @@ +{ + "testKey": "testValueDe", + "namespace": { + "nestedKeyWithPlaceholder": "Test1 %s, test2 %s." + } +} \ No newline at end of file diff --git a/tests/BusinessLogic/Language/Translations/en.json b/tests/BusinessLogic/Language/Translations/en.json new file mode 100644 index 00000000..2fa71671 --- /dev/null +++ b/tests/BusinessLogic/Language/Translations/en.json @@ -0,0 +1,4 @@ +{ + "testKey": "testValueEn", + "testKey1": "testValueEn1" +} \ No newline at end of file diff --git a/tests/BusinessLogic/Location/LocationServiceTest.php b/tests/BusinessLogic/Location/LocationServiceTest.php index 02faf391..8c5ddfae 100644 --- a/tests/BusinessLogic/Location/LocationServiceTest.php +++ b/tests/BusinessLogic/Location/LocationServiceTest.php @@ -166,6 +166,23 @@ public function testGetLocationsForNonDropOffService() $this->assertEmpty($locations); } + /** + * @expectedException \Packlink\BusinessLogic\Location\Exceptions\PlatformCountryNotSupportedException + * + * @throws \Logeecom\Infrastructure\Http\Exceptions\HttpAuthenticationException + * @throws \Logeecom\Infrastructure\Http\Exceptions\HttpCommunicationException + * @throws \Logeecom\Infrastructure\Http\Exceptions\HttpRequestException + * @throws \Packlink\BusinessLogic\Location\Exceptions\PlatformCountryNotSupportedException + */ + public function testLocationSearchWithUnsupportedCountry() + { + $this->httpClient->setMockResponses(array(new HttpResponse(200, array(), '[]'))); + + /** @var LocationService $locationService */ + $locationService = TestServiceRegister::getService(LocationService::CLASS_NAME); + $locationService->searchLocations('RS', 'Test'); + } + public function testGetLocationsForInvalidPostalCode() { $this->initShippingMethod(true); @@ -197,23 +214,6 @@ public function testGetLocationsForTransformedPostalCode() $this->assertEmpty($locations); } - /** - * @expectedException \Packlink\BusinessLogic\Location\Exceptions\PlatformCountryNotSupportedException - * - * @throws \Logeecom\Infrastructure\Http\Exceptions\HttpAuthenticationException - * @throws \Logeecom\Infrastructure\Http\Exceptions\HttpCommunicationException - * @throws \Logeecom\Infrastructure\Http\Exceptions\HttpRequestException - * @throws \Packlink\BusinessLogic\Location\Exceptions\PlatformCountryNotSupportedException - */ - public function testLocationSearchWithUnsupportedCountry() - { - $this->httpClient->setMockResponses(array(new HttpResponse(200, array(), '[]'))); - - /** @var LocationService $locationService */ - $locationService = TestServiceRegister::getService(LocationService::CLASS_NAME); - $locationService->searchLocations('RS', 'Test'); - } - /** * @throws \Logeecom\Infrastructure\Http\Exceptions\HttpAuthenticationException * @throws \Logeecom\Infrastructure\Http\Exceptions\HttpCommunicationException diff --git a/tests/BusinessLogic/PostalCode/PostalCodeTransformerTest.php b/tests/BusinessLogic/PostalCode/PostalCodeTransformerTest.php index 475b7989..3fa77432 100644 --- a/tests/BusinessLogic/PostalCode/PostalCodeTransformerTest.php +++ b/tests/BusinessLogic/PostalCode/PostalCodeTransformerTest.php @@ -49,6 +49,14 @@ public function testTransformingFormattedPostalCode() self::assertEquals('1000-260', $transformedPostalCode); } + /** + * @expectedException InvalidArgumentException + */ + public function testTransformingPostalCodeWithSpecialCharacters() + { + PostalCodeTransformer::transform('GB', 'SW1Ä1'); + } + public function testTransformingNonFormattedPostalCode() { $transformedPostalCode = PostalCodeTransformer::transform('GB', 'SW1A1'); @@ -85,14 +93,6 @@ public function testTransformingNonFormattedPostalCode() self::assertEquals('1000', $transformedPostalCode); } - /** - * @expectedException InvalidArgumentException - */ - public function testTransformingPostalCodeWithSpecialCharacters() - { - PostalCodeTransformer::transform('GB', 'SW1Ä1'); - } - public function testTransformingImproperlyFormattedPostalCode() { $transformedPostalCode = PostalCodeTransformer::transform('GB', 'SW-1A-1'); @@ -114,11 +114,9 @@ public function testTransformingImproperlyFormattedPostalCode() public function testTransformingSpecialCasePostalCode() { $transformedPostalCode = PostalCodeTransformer::transform('US', '10018-0005'); - self::assertEquals('10018', $transformedPostalCode); $transformedPostalCode = PostalCodeTransformer::transform('US', '10018'); - self::assertEquals('10018', $transformedPostalCode); } -} +} \ No newline at end of file diff --git a/tests/BusinessLogic/Scheduler/ScheduleCheckTaskTest.php b/tests/BusinessLogic/Scheduler/ScheduleCheckTaskTest.php index d5e9f94f..4139781c 100644 --- a/tests/BusinessLogic/Scheduler/ScheduleCheckTaskTest.php +++ b/tests/BusinessLogic/Scheduler/ScheduleCheckTaskTest.php @@ -306,6 +306,68 @@ public function testMultipleSchedulesForAbortedTask() $this->multipleSchedulesTest(QueueItem::ABORTED, 1); } + /** + * @throws \Logeecom\Infrastructure\ORM\Exceptions\EntityClassException + * @throws \Logeecom\Infrastructure\ORM\Exceptions\QueryFilterInvalidParamException + * @throws \Logeecom\Infrastructure\ORM\Exceptions\RepositoryNotRegisteredException + * @throws \Logeecom\Infrastructure\TaskExecution\Exceptions\AbortTaskExecutionException + */ + public function testEnqueuingAllNonRecurringScheduledTasks() + { + $repository = RepositoryRegistry::getRepository(Schedule::CLASS_NAME); + + $schedule = new HourlySchedule(new FooTask()); + $schedule->setRecurring(false); + $schedule->setHour(13); + $schedule->setNextSchedule(); + $repository->save($schedule); + + $schedule = new HourlySchedule(new FooTask()); + $schedule->setRecurring(false); + $schedule->setHour(14); + $schedule->setNextSchedule(); + $repository->save($schedule); + + $nowDateTime = new \DateTime('2018-03-22T14:42:05'); + $this->timeProvider->setCurrentLocalTime($nowDateTime); + $this->syncTask->execute(); + + /** @var \Logeecom\Infrastructure\TaskExecution\QueueItem[] $queueItems */ + $queueItems = $this->queueStorage->select(); + $this->assertCount(2, $queueItems); + } + + /** + * @throws \Logeecom\Infrastructure\ORM\Exceptions\EntityClassException + * @throws \Logeecom\Infrastructure\ORM\Exceptions\QueryFilterInvalidParamException + * @throws \Logeecom\Infrastructure\ORM\Exceptions\RepositoryNotRegisteredException + * @throws \Logeecom\Infrastructure\TaskExecution\Exceptions\AbortTaskExecutionException + */ + public function testEnqueuingOnlyOneRecurringScheduledTask() + { + $repository = RepositoryRegistry::getRepository(Schedule::CLASS_NAME); + + $schedule = new HourlySchedule(new FooTask()); + $schedule->setRecurring(true); + $schedule->setHour(13); + $schedule->setNextSchedule(); + $repository->save($schedule); + + $schedule = new HourlySchedule(new FooTask()); + $schedule->setRecurring(true); + $schedule->setHour(14); + $schedule->setNextSchedule(); + $repository->save($schedule); + + $nowDateTime = new \DateTime('2018-03-22T14:42:05'); + $this->timeProvider->setCurrentLocalTime($nowDateTime); + $this->syncTask->execute(); + + /** @var \Logeecom\Infrastructure\TaskExecution\QueueItem[] $queueItems */ + $queueItems = $this->queueStorage->select(); + $this->assertCount(1, $queueItems); + } + /** * @param string $newStatus * @param int $expectedCount diff --git a/tests/BusinessLogic/ShippingMethod/ShippingMethodConfigurationTest.php b/tests/BusinessLogic/ShippingMethod/ShippingMethodConfigurationTest.php index ae04a0c0..eebf4b8e 100644 --- a/tests/BusinessLogic/ShippingMethod/ShippingMethodConfigurationTest.php +++ b/tests/BusinessLogic/ShippingMethod/ShippingMethodConfigurationTest.php @@ -4,7 +4,7 @@ use Logeecom\Tests\BusinessLogic\Common\BaseTestWithServices; use Packlink\BusinessLogic\Controllers\DTO\ShippingMethodConfiguration; -use Packlink\BusinessLogic\ShippingMethod\Models\ShippingMethod; +use Packlink\BusinessLogic\ShippingMethod\Models\ShippingPricePolicy; /** * Class ShippingMethodConfigurationTest. @@ -13,39 +13,19 @@ */ class ShippingMethodConfigurationTest extends BaseTestWithServices { + /** + * @throws \Packlink\BusinessLogic\DTO\Exceptions\FrontDtoValidationException + */ public function testShippingMethodFromArray() { - $config1 = ShippingMethodConfiguration::fromArray($this->getPacklinkPriceRawData()); - - $this->assertInstanceOf('Packlink\BusinessLogic\Controllers\DTO\ShippingMethodConfiguration', $config1); - $this->assertEquals(1, $config1->id); - $this->assertEquals('Test', $config1->name); - $this->assertEquals(true, $config1->showLogo); - $this->assertEquals(ShippingMethod::PRICING_POLICY_PACKLINK, $config1->pricePolicy); - - $config2 = ShippingMethodConfiguration::fromArray($this->getPacklinkPercentPriceRawData()); - $this->assertInstanceOf( - 'Packlink\BusinessLogic\ShippingMethod\Models\PercentPricePolicy', - $config2->percentPricePolicy - ); - $this->assertEquals(50, $config2->percentPricePolicy->amount); - $this->assertEquals(true, $config2->percentPricePolicy->increase); - - $config3 = ShippingMethodConfiguration::fromArray($this->getFixedPriceRawData(true)); - $this->assertCount(2, $config3->fixedPriceByWeightPolicy); - $policy = $config3->fixedPriceByWeightPolicy[0]; - $this->assertInstanceOf('Packlink\BusinessLogic\ShippingMethod\Models\FixedPricePolicy', $policy); - $this->assertEquals(0, $policy->from); - $this->assertEquals(10, $policy->to); - $this->assertEquals(15, $policy->amount); - - $config4 = ShippingMethodConfiguration::fromArray($this->getFixedPriceRawData(false)); - $this->assertCount(2, $config4->fixedPriceByValuePolicy); - $policy = $config4->fixedPriceByValuePolicy[1]; - $this->assertInstanceOf('Packlink\BusinessLogic\ShippingMethod\Models\FixedPricePolicy', $policy); - $this->assertEquals(10, $policy->from); - $this->assertEquals(22.5, $policy->to); - $this->assertEquals(32.13, $policy->amount); + $config = ShippingMethodConfiguration::fromArray($this->getPacklinkPriceRawData()); + + $this->assertInstanceOf('Packlink\BusinessLogic\Controllers\DTO\ShippingMethodConfiguration', $config); + $this->assertEquals(1, $config->id); + $this->assertEquals('Test', $config->name); + $this->assertTrue($config->showLogo); + $this->assertCount(1, $config->pricingPolicies); + $this->assertFalse($config->usePacklinkPriceIfNotInRange); } /** @@ -59,56 +39,13 @@ protected function getPacklinkPriceRawData() 'id' => 1, 'name' => 'Test', 'showLogo' => true, - 'pricePolicy' => ShippingMethod::PRICING_POLICY_PACKLINK, - ); - } - - /** - * Returns raw data that corresponds to Shipping method configuration with Packlink percent price policy. - * - * @return array - */ - protected function getPacklinkPercentPriceRawData() - { - return array( - 'id' => 1, - 'name' => 'Test', - 'showLogo' => true, - 'pricePolicy' => ShippingMethod::PRICING_POLICY_PERCENT, - 'percentPricePolicy' => array( - 'amount' => 50, - 'increase' => true, - ), - ); - } - - /** - * Returns raw data that corresponds to Shipping method configuration with fixed price policy. - * - * @param bool $byWeight - * - * @return array - */ - protected function getFixedPriceRawData($byWeight) - { - $policy = $byWeight ? 'fixedPriceByWeightPolicy' : 'fixedPriceByValuePolicy'; - $pricePolicy = $byWeight ? ShippingMethod::PRICING_POLICY_FIXED_PRICE_BY_WEIGHT - : ShippingMethod::PRICING_POLICY_FIXED_PRICE_BY_VALUE; - return array( - 'id' => 1, - 'name' => 'Test', - 'showLogo' => true, - 'pricePolicy' => $pricePolicy, - $policy => array( - array( - 'from' => 0, - 'to' => 10, - 'amount' => 15, - ), + 'usePacklinkPriceIfNotInRange' => false, + 'pricingPolicies' => array( array( - 'from' => 10, - 'to' => 22.5, - 'amount' => 32.13, + 'range_type' => ShippingPricePolicy::RANGE_PRICE, + 'from_price' => 0, + 'to_price' => 20, + 'pricing_policy' => ShippingPricePolicy::POLICY_PACKLINK, ), ), ); diff --git a/tests/BusinessLogic/ShippingMethod/ShippingMethodEntityTest.php b/tests/BusinessLogic/ShippingMethod/ShippingMethodEntityTest.php index 7ae0d088..c034547c 100644 --- a/tests/BusinessLogic/ShippingMethod/ShippingMethodEntityTest.php +++ b/tests/BusinessLogic/ShippingMethod/ShippingMethodEntityTest.php @@ -7,6 +7,7 @@ use Logeecom\Infrastructure\Http\HttpClient; use Logeecom\Infrastructure\Http\HttpResponse; use Logeecom\Infrastructure\ORM\RepositoryRegistry; +use Logeecom\Tests\BusinessLogic\Common\BaseTestWithServices; use Logeecom\Tests\BusinessLogic\Common\TestComponents\Dto\TestFrontDtoFactory; use Logeecom\Tests\Infrastructure\Common\TestComponents\ORM\MemoryRepository; use Logeecom\Tests\Infrastructure\Common\TestComponents\TestHttpClient; @@ -16,21 +17,19 @@ use Packlink\BusinessLogic\Http\DTO\Package; use Packlink\BusinessLogic\Http\DTO\ParcelInfo; use Packlink\BusinessLogic\Http\Proxy; -use Packlink\BusinessLogic\ShippingMethod\Models\FixedPricePolicy; -use Packlink\BusinessLogic\ShippingMethod\Models\PercentPricePolicy; use Packlink\BusinessLogic\ShippingMethod\Models\ShippingMethod; +use Packlink\BusinessLogic\ShippingMethod\Models\ShippingPricePolicy; use Packlink\BusinessLogic\ShippingMethod\Models\ShippingService; use Packlink\BusinessLogic\ShippingMethod\PackageTransformer; use Packlink\BusinessLogic\ShippingMethod\ShippingCostCalculator; use Packlink\BusinessLogic\Warehouse\Warehouse; -use PHPUnit\Framework\TestCase; /** * Class ShippingMethodEntityTest. * * @package Logeecom\Tests\BusinessLogic\ShippingMethod */ -class ShippingMethodEntityTest extends TestCase +class ShippingMethodEntityTest extends BaseTestWithServices { /** * @var \Logeecom\Tests\Infrastructure\Common\TestComponents\TestHttpClient @@ -73,6 +72,7 @@ function () { TestFrontDtoFactory::register(ValidationError::CLASS_KEY, ValidationError::CLASS_NAME); TestFrontDtoFactory::register(Warehouse::CLASS_KEY, Warehouse::CLASS_NAME); TestFrontDtoFactory::register(ParcelInfo::CLASS_KEY, ParcelInfo::CLASS_NAME); + TestFrontDtoFactory::register(ShippingPricePolicy::CLASS_KEY, ShippingPricePolicy::CLASS_NAME); } protected function tearDown() @@ -116,403 +116,7 @@ public function testProperties() self::assertEquals('title', $method->getTitle()); } - public function testDefaultPricingPolicy() - { - $method = new ShippingMethod(); - - self::assertEmpty($method->getFixedPriceByWeightPolicy()); - self::assertEmpty($method->getFixedPriceByValuePolicy()); - self::assertEmpty($method->getPercentPricePolicy()); - self::assertEquals(ShippingMethod::PRICING_POLICY_PACKLINK, $method->getPricingPolicy()); - } - - public function testFixedPricingByWeightPolicyOneValid() - { - $method = new ShippingMethod(); - - /** @var FixedPricePolicy[] $fixedPricePolicies */ - $fixedPricePolicies[] = new FixedPricePolicy(0, 10, 12); - - $method->setFixedPriceByWeightPolicy($fixedPricePolicies); - - self::assertNotEmpty($method->getFixedPriceByWeightPolicy()); - self::assertEmpty($method->getPercentPricePolicy()); - self::assertEmpty($method->getFixedPriceByValuePolicy()); - self::assertEquals(ShippingMethod::PRICING_POLICY_FIXED_PRICE_BY_WEIGHT, $method->getPricingPolicy()); - } - - public function testFixedPricingByValuePolicyOneValid() - { - $method = new ShippingMethod(); - - /** @var FixedPricePolicy[] $fixedPricePolicies */ - $fixedPricePolicies[] = new FixedPricePolicy(0, 10, 12); - - $method->setFixedPriceByValuePolicy($fixedPricePolicies); - - self::assertNotEmpty($method->getFixedPriceByValuePolicy()); - self::assertEmpty($method->getPercentPricePolicy()); - self::assertEmpty($method->getFixedPriceByWeightPolicy()); - self::assertEquals(ShippingMethod::PRICING_POLICY_FIXED_PRICE_BY_VALUE, $method->getPricingPolicy()); - } - - /** - * @expectedException \InvalidArgumentException - */ - public function testFixedPricingPolicyValidationAmountNotSetOnFirst() - { - $method = new ShippingMethod(); - - /** @var FixedPricePolicy[] $fixedPricePolicies */ - $fixedPricePolicies[] = new FixedPricePolicy(0, 10, 0); - - // amount must be set - $method->setFixedPriceByWeightPolicy($fixedPricePolicies); - } - - /** - * @expectedException \InvalidArgumentException - */ - public function testFixedPricingPolicyValidationNegativeAmountOnFirst() - { - $method = new ShippingMethod(); - - /** @var FixedPricePolicy[] $fixedPricePolicies */ - $fixedPricePolicies[] = new FixedPricePolicy(0, 10, -10); - - // amount must be positive - $method->setFixedPriceByWeightPolicy($fixedPricePolicies); - } - - /** - * @expectedException \InvalidArgumentException - */ - public function testFixedPricingPolicyValidationZeroAmountOnFirst() - { - $method = new ShippingMethod(); - - /** @var FixedPricePolicy[] $fixedPricePolicies */ - $fixedPricePolicies[] = new FixedPricePolicy(0, 10, 0); - - // amount must be positive - $method->setFixedPriceByWeightPolicy($fixedPricePolicies); - } - - /** - * @expectedException \InvalidArgumentException - */ - public function testFixedPricingPolicyValidationFromNegative() - { - $method = new ShippingMethod(); - - /** @var FixedPricePolicy[] $fixedPricePolicies */ - $fixedPricePolicies[] = new FixedPricePolicy(-3, 10, 10); - - // from for first policy must be 0 - $method->setFixedPriceByWeightPolicy($fixedPricePolicies); - } - - /** - * @expectedException \InvalidArgumentException - */ - public function testFixedPricingPolicyValidationInvalidFromBetweenPolicies() - { - $method = new ShippingMethod(); - - /** @var FixedPricePolicy[] $fixedPricePolicies */ - $fixedPricePolicies[] = new FixedPricePolicy(0, 10, 12); - $fixedPricePolicies[] = new FixedPricePolicy(11, 13, 10); - - // second policy must have "from" bigger from previous for exactly 0.001 - $method->setFixedPriceByWeightPolicy($fixedPricePolicies); - } - - /** - * @expectedException \InvalidArgumentException - */ - public function testFixedPricingPolicyValidationNegativeAmountOnSecondPolicy() - { - $method = new ShippingMethod(); - - /** @var FixedPricePolicy[] $fixedPricePolicies */ - $fixedPricePolicies[] = new FixedPricePolicy(0, 10, 12); - $fixedPricePolicies[] = new FixedPricePolicy(10, 13, -10); - - // second policy must have amount bigger than 0 - $method->setFixedPriceByWeightPolicy($fixedPricePolicies); - } - - public function testFixedPricingPolicyValidationValidZeroAmountOnFirst() - { - $method = new ShippingMethod(); - - /** @var FixedPricePolicy[] $fixedPricePolicies */ - $fixedPricePolicies[] = new FixedPricePolicy(0, 10, 0); - - $method->setFixedPriceByValuePolicy($fixedPricePolicies); - self::assertNotEmpty($method->getFixedPriceByValuePolicy()); - self::assertCount(1, $method->getFixedPriceByValuePolicy()); - } - - public function testFixedPricingPolicyValidationValidZeroAmountOnLast() - { - $method = new ShippingMethod(); - - /** @var FixedPricePolicy[] $fixedPricePolicies */ - $fixedPricePolicies[] = new FixedPricePolicy(0, 10, 10); - $fixedPricePolicies[] = new FixedPricePolicy(10, 100, 0); - - $method->setFixedPriceByValuePolicy($fixedPricePolicies); - self::assertNotEmpty($method->getFixedPriceByValuePolicy()); - self::assertCount(2, $method->getFixedPriceByValuePolicy()); - } - - public function testFixedPricingByWeightPolicyValidationValidTwoPolicies() - { - $method = new ShippingMethod(); - - /** @var FixedPricePolicy[] $fixedPricePolicies */ - $fixedPricePolicies[] = new FixedPricePolicy(0, 10, 12); - $fixedPricePolicies[] = new FixedPricePolicy(10, 13, 10); - - $method->setFixedPriceByWeightPolicy($fixedPricePolicies); - self::assertNotEmpty($method->getFixedPriceByWeightPolicy()); - self::assertCount(2, $method->getFixedPriceByWeightPolicy()); - } - - public function testFixedPricingByWeightPolicyValidationValidDifferentOrder() - { - $method = new ShippingMethod(); - - /** @var FixedPricePolicy[] $fixedPricePolicies */ - $fixedPricePolicies[] = new FixedPricePolicy(10, 13, 10); - $fixedPricePolicies[] = new FixedPricePolicy(13, 50, 6); - $fixedPricePolicies[] = new FixedPricePolicy(0, 10, 12); - - $method->setFixedPriceByWeightPolicy($fixedPricePolicies); - $policies = $method->getFixedPriceByWeightPolicy(); - self::assertNotEmpty($policies); - self::assertCount(3, $policies); - } - - public function testFixedPricingByWeightPolicyValidationValidTwoPoliciesSecondWithoutUpperBound() - { - $method = new ShippingMethod(); - - /** @var FixedPricePolicy[] $fixedPricePolicies */ - $fixedPricePolicies[] = new FixedPricePolicy(0, 10, 12); - $fixedPricePolicies[] = new FixedPricePolicy(10, 20, 10); - - $method->setFixedPriceByWeightPolicy($fixedPricePolicies); - self::assertNotEmpty($method->getFixedPriceByWeightPolicy()); - self::assertCount(2, $method->getFixedPriceByWeightPolicy()); - } - - public function testFixedPricingPolicyValidationValidBulk() - { - $method = new ShippingMethod(); - - /** @var FixedPricePolicy[] $fixedPricePolicies */ - $fixedPricePolicies[] = new FixedPricePolicy(0, 13, 1000); - - for ($i = 1; $i < 100; $i++) { - $fixedPricePolicies[] = new FixedPricePolicy( - $fixedPricePolicies[$i - 1]->to, - $fixedPricePolicies[$i - 1]->to + 10, - $fixedPricePolicies[$i - 1]->amount - 10 - ); - } - - $method->setFixedPriceByWeightPolicy($fixedPricePolicies); - self::assertCount(100, $method->getFixedPriceByWeightPolicy()); - } - - public function testPercentPricingPolicy() - { - $method = new ShippingMethod(); - - $policy = new PercentPricePolicy(true, 10); - $method->setPercentPricePolicy($policy); - - self::assertNotEmpty($method->getPercentPricePolicy()); - self::assertEquals(ShippingMethod::PRICING_POLICY_PERCENT, $method->getPricingPolicy()); - self::assertEmpty($method->getFixedPriceByWeightPolicy()); - - $policy = new PercentPricePolicy(false, 10); - $method->setPercentPricePolicy($policy); - - self::assertNotEmpty($method->getPercentPricePolicy()); - self::assertEquals(ShippingMethod::PRICING_POLICY_PERCENT, $method->getPricingPolicy()); - } - - /** - * @expectedException \InvalidArgumentException - */ - public function testPercentPricingPolicyZeroAmount() - { - $method = new ShippingMethod(); - - $policy = new PercentPricePolicy(false, 0); - - $method->setPercentPricePolicy($policy); - } - - /** - * @expectedException \InvalidArgumentException - */ - public function testPercentPricingPolicyNegativeAmount() - { - $method = new ShippingMethod(); - - $policy = new PercentPricePolicy(true, -10); - - $method->setPercentPricePolicy($policy); - } - - /** - * @expectedException \InvalidArgumentException - */ - public function testPercentPricingPolicyDecreaseFor100() - { - $method = new ShippingMethod(); - - $policy = new PercentPricePolicy(false, 100); - - $method->setPercentPricePolicy($policy); - } - - /** - * @expectedException \InvalidArgumentException - */ - public function testPercentPricingPolicyDecreaseForMoreThan100() - { - $method = new ShippingMethod(); - - $policy = new PercentPricePolicy(false, 120); - - $method->setPercentPricePolicy($policy); - } - - public function testPercentPricingPolicyAfterFixedPricePolicy() - { - $method = new ShippingMethod(); - - $fixedPricePolicies[] = new FixedPricePolicy(0, 10, 12); - - $method->setFixedPriceByWeightPolicy($fixedPricePolicies); - - self::assertNotEmpty($method->getFixedPriceByWeightPolicy()); - self::assertEmpty($method->getPercentPricePolicy()); - self::assertEquals(ShippingMethod::PRICING_POLICY_FIXED_PRICE_BY_WEIGHT, $method->getPricingPolicy()); - - $method->setPercentPricePolicy(new PercentPricePolicy(true, 10)); - - self::assertNotEmpty($method->getPercentPricePolicy()); - self::assertEquals(ShippingMethod::PRICING_POLICY_PERCENT, $method->getPricingPolicy()); - self::assertEmpty($method->getFixedPriceByWeightPolicy()); - } - - public function testFixedPricingPolicyAfterPercentPricePolicy() - { - $method = new ShippingMethod(); - - $fixedPricePolicies[] = new FixedPricePolicy(0, 10, 12); - - $method->setPercentPricePolicy(new PercentPricePolicy(true, 10)); - - self::assertNotEmpty($method->getPercentPricePolicy()); - self::assertEquals(ShippingMethod::PRICING_POLICY_PERCENT, $method->getPricingPolicy()); - self::assertEmpty($method->getFixedPriceByWeightPolicy()); - - $method->setFixedPriceByWeightPolicy($fixedPricePolicies); - - self::assertNotEmpty($method->getFixedPriceByWeightPolicy()); - self::assertEmpty($method->getPercentPricePolicy()); - self::assertEquals(ShippingMethod::PRICING_POLICY_FIXED_PRICE_BY_WEIGHT, $method->getPricingPolicy()); - } - - public function testResetAfterFixedPricingPolicy() - { - $method = new ShippingMethod(); - - $fixedPricePolicies[] = new FixedPricePolicy(0, 10, 12); - - $method->setFixedPriceByWeightPolicy($fixedPricePolicies); - - self::assertNotEmpty($method->getFixedPriceByWeightPolicy()); - self::assertEmpty($method->getPercentPricePolicy()); - self::assertEquals(ShippingMethod::PRICING_POLICY_FIXED_PRICE_BY_WEIGHT, $method->getPricingPolicy()); - - $method->setPacklinkPricePolicy(); - self::assertEmpty($method->getFixedPriceByWeightPolicy()); - self::assertEmpty($method->getPercentPricePolicy()); - self::assertEquals(ShippingMethod::PRICING_POLICY_PACKLINK, $method->getPricingPolicy()); - } - - public function testResetAfterPercentPricingPolicy() - { - $method = new ShippingMethod(); - $method->setPercentPricePolicy(new PercentPricePolicy(true, 10)); - - self::assertNotEmpty($method->getPercentPricePolicy()); - self::assertEquals(ShippingMethod::PRICING_POLICY_PERCENT, $method->getPricingPolicy()); - self::assertEmpty($method->getFixedPriceByWeightPolicy()); - - $method->setPacklinkPricePolicy(); - self::assertEmpty($method->getFixedPriceByWeightPolicy()); - self::assertEmpty($method->getPercentPricePolicy()); - self::assertEquals(ShippingMethod::PRICING_POLICY_PACKLINK, $method->getPricingPolicy()); - } - - public function testToArrayPacklinkPricingPolicy() - { - $this->assertBasicDataToArray(); - } - - public function testToArrayPercentPricingPolicy() - { - $method = $this->assertBasicDataToArray(); - - $policy = new PercentPricePolicy(true, 10); - $method->setPercentPricePolicy($policy); - - $result = $method->toArray(); - self::assertEquals(ShippingMethod::PRICING_POLICY_PERCENT, $result['pricingPolicy']); - self::assertEquals($policy->increase, $result['percentPricePolicy']['increase']); - self::assertEquals($policy->amount, $result['percentPricePolicy']['amount']); - } - - public function testToArrayFixedPricingByWeightPolicy() - { - $method = $this->assertBasicDataToArray(); - - $fixedPricePolicies[] = new FixedPricePolicy(0, 10, 12); - $fixedPricePolicies[] = new FixedPricePolicy(10, 13, 10); - $method->setFixedPriceByWeightPolicy($fixedPricePolicies); - - $result = $method->toArray(); - self::assertEquals(ShippingMethod::PRICING_POLICY_FIXED_PRICE_BY_WEIGHT, $result['pricingPolicy']); - self::assertCount(2, $result['fixedPriceByWeightPolicy']); - self::assertEquals(0, $result['fixedPriceByWeightPolicy'][0]['from']); - self::assertEquals(10, $result['fixedPriceByWeightPolicy'][1]['from']); - } - - public function testToArrayFixedPricingByValuePolicy() - { - $method = $this->assertBasicDataToArray(); - - $fixedPricePolicies[] = new FixedPricePolicy(0, 10, 12); - $fixedPricePolicies[] = new FixedPricePolicy(10, 13, 10); - $method->setFixedPriceByValuePolicy($fixedPricePolicies); - - $result = $method->toArray(); - self::assertEquals(ShippingMethod::PRICING_POLICY_FIXED_PRICE_BY_VALUE, $result['pricingPolicy']); - self::assertCount(2, $result['fixedPriceByValuePolicy']); - self::assertEquals(0, $result['fixedPriceByValuePolicy'][0]['from']); - self::assertEquals(10, $result['fixedPriceByValuePolicy'][1]['from']); - } - - public function testFromArrayShippingCosts() + public function testFromArrayShippingService() { $data = array( 'serviceId' => '20339', @@ -534,130 +138,6 @@ public function testFromArrayShippingCosts() self::assertEquals('DE', $method->destinationCountry); } - public function testFromArrayShippingMethodShippingCosts() - { - $data = $this->getShippingMethodData(); - - $method = ShippingMethod::fromArray($data); - $costs = $method->getShippingServices(); - self::assertCount(1, $costs); - self::assertEquals(3, $costs[0]->totalPrice); - self::assertEquals(2, $costs[0]->basePrice); - self::assertEquals(1, $costs[0]->taxPrice); - - $method->setShippingServices($costs); - $costs = $method->getShippingServices(); - self::assertCount(1, $costs); - } - - public function testFromArrayPacklinkPricingPolicy() - { - $data = $this->getShippingMethodData(); - - $method = ShippingMethod::fromArray($data); - self::assertEquals($data['carrierName'], $method->getCarrierName()); - self::assertEquals($data['title'], $method->getTitle()); - self::assertEquals($data['enabled'], $method->isEnabled()); - self::assertEquals($data['activated'], $method->isActivated()); - self::assertEquals($data['logoUrl'], $method->getLogoUrl()); - self::assertEquals($data['displayLogo'], $method->isDisplayLogo()); - self::assertEquals($data['departureDropOff'], $method->isDepartureDropOff()); - self::assertEquals($data['destinationDropOff'], $method->isDestinationDropOff()); - self::assertEquals($data['expressDelivery'], $method->isExpressDelivery()); - self::assertEquals($data['deliveryTime'], $method->getDeliveryTime()); - self::assertEquals($data['national'], $method->isNational()); - self::assertEquals(ShippingMethod::PRICING_POLICY_PACKLINK, $method->getPricingPolicy()); - } - - public function testFromArrayPercentPricingPolicy() - { - $data = $this->getShippingMethodData(); - $data['percentPricePolicy']['increase'] = false; - $data['percentPricePolicy']['amount'] = 20; - - $method = ShippingMethod::fromArray($data); - $policy = $method->getPercentPricePolicy(); - self::assertEquals(ShippingMethod::PRICING_POLICY_PERCENT, $method->getPricingPolicy()); - self::assertEquals(false, $policy->increase); - self::assertEquals(20, $policy->amount); - } - - /** - * @expectedException \InvalidArgumentException - */ - public function testFromArrayInvalidPercentPricingPolicy() - { - $data = $this->getShippingMethodData(); - $data['percentPricePolicy']['increase'] = false; - $data['percentPricePolicy']['amount'] = 200; - - ShippingMethod::fromArray($data); - } - - public function testFromArrayFixedPricingByWeightPolicy() - { - $data = $this->getShippingMethodData(); - $data['fixedPriceByWeightPolicy'][0] = array( - 'from' => 10, - 'to' => 20, - 'amount' => 100, - ); - $data['fixedPriceByWeightPolicy'][1] = array( - 'from' => 0, - 'to' => 10, - 'amount' => 120, - ); - - $method = ShippingMethod::fromArray($data); - $policy = $method->getFixedPriceByWeightPolicy(); - self::assertEquals(ShippingMethod::PRICING_POLICY_FIXED_PRICE_BY_WEIGHT, $method->getPricingPolicy()); - self::assertCount(2, $policy); - self::assertEquals(0, $policy[0]->from); - self::assertEquals(10, $policy[1]->from); - } - - public function testFromArrayFixedPricingByValuePolicy() - { - $data = $this->getShippingMethodData(); - $data['fixedPriceByValuePolicy'][0] = array( - 'from' => 10, - 'to' => 20, - 'amount' => 100, - ); - $data['fixedPriceByValuePolicy'][1] = array( - 'from' => 0, - 'to' => 10, - 'amount' => 120, - ); - - $method = ShippingMethod::fromArray($data); - $policy = $method->getFixedPriceByValuePolicy(); - self::assertEquals(ShippingMethod::PRICING_POLICY_FIXED_PRICE_BY_VALUE, $method->getPricingPolicy()); - self::assertCount(2, $policy); - self::assertEquals(0, $policy[0]->from); - self::assertEquals(10, $policy[1]->from); - } - - /** - * @expectedException \InvalidArgumentException - */ - public function testFromArrayInvalidFixedPricingPolicy() - { - $data = $this->getShippingMethodData(); - $data['fixedPriceByWeightPolicy'][0] = array( - 'from' => 10, - 'to' => 20, - 'amount' => 100, - ); - $data['fixedPriceByWeightPolicy'][1] = array( - 'from' => 0, - 'to' => 5, - 'amount' => 120, - ); - - ShippingMethod::fromArray($data); - } - public function testCheapestService() { $method = $this->assertBasicDataToArray(); @@ -718,6 +198,7 @@ public function testCheapestServiceProxyResponse() * Asserts basic shipping method data. * * @return \Packlink\BusinessLogic\ShippingMethod\Models\ShippingMethod + * @throws \Packlink\BusinessLogic\DTO\Exceptions\FrontDtoValidationException */ private function assertBasicDataToArray() { @@ -736,6 +217,7 @@ private function assertBasicDataToArray() $method->setDeliveryTime($data['deliveryTime']); $method->setNational($data['national']); $method->addShippingService(ShippingService::fromArray($data['shippingServices'][0])); + $method->addPricingPolicy(ShippingPricePolicy::fromArray($data['pricingPolicies'][0])); $result = $method->toArray(); self::assertEquals($data['carrierName'], $result['carrierName']); @@ -749,8 +231,8 @@ private function assertBasicDataToArray() self::assertEquals($data['expressDelivery'], $result['expressDelivery']); self::assertEquals($data['deliveryTime'], $result['deliveryTime']); self::assertEquals($data['national'], $result['national']); - self::assertEquals(ShippingMethod::PRICING_POLICY_PACKLINK, $result['pricingPolicy']); self::assertEquals($data['shippingServices'], $result['shippingServices']); + self::assertEquals($data['pricingPolicies'], $result['pricingPolicies']); return $method; } @@ -783,6 +265,19 @@ private function getShippingMethodData() 'taxPrice' => 1, ), ), + 'pricingPolicies' => array( + array( + 'range_type' => ShippingPricePolicy::RANGE_PRICE, + 'from_price' => 0, + 'to_price' => null, + 'pricing_policy' => ShippingPricePolicy::POLICY_PACKLINK, + 'from_weight' => null, + 'to_weight' => null, + 'increase' => false, + 'change_percent' => null, + 'fixed_price' => null, + ), + ), ); } } diff --git a/tests/BusinessLogic/ShippingMethod/ShippingMethodServiceCostsTest.php b/tests/BusinessLogic/ShippingMethod/ShippingMethodServiceCostsTest.php index 19c741fd..7652eff9 100644 --- a/tests/BusinessLogic/ShippingMethod/ShippingMethodServiceCostsTest.php +++ b/tests/BusinessLogic/ShippingMethod/ShippingMethodServiceCostsTest.php @@ -1,20 +1,19 @@ shopConfig->setAuthorizationToken('test_token'); @@ -114,15 +114,7 @@ public function testGetCostFromUnknownService() $this->httpClient->setMockResponses(array(new HttpResponse(200, array(), $response))); // since method is not inserted to database, this should return 0; - $cost = $this->shippingMethodService->getShippingCost( - 20339, - 'IT', - '00118', - 'IT', - '00118', - array(Package::defaultPackage()), - 10 - ); + $cost = $this->getShippingCosts(null, 20339, '00118'); self::assertEquals(0, $cost, 'Shipping cost should not be returned for unknown service.'); self::assertNull($this->httpClient->getHistory(), 'API should not be called for unknown service.'); @@ -136,8 +128,7 @@ public function testGetCostsFromUnknownService() ); $this->httpClient->setMockResponses(array(new HttpResponse(200, array(), $response))); // since method is not inserted to database, returned array should not have costs for this method. - $package = Package::defaultPackage(); - $costs = $this->shippingMethodService->getShippingCosts('IT', '00118', 'IT', '00118', array($package), 10); + $costs = $this->getShippingCosts(); self::assertArrayNotHasKey(20339, $costs); } @@ -152,16 +143,7 @@ public function testGetCostFromProxy() ); $this->httpClient->setMockResponses(array(new HttpResponse(200, array(), $response))); - $package = Package::defaultPackage(); - $cost = $this->shippingMethodService->getShippingCost( - $shippingMethod->getId(), - 'IT', - '00118', - 'IT', - '00118', - array($package), - 10 - ); + $cost = $this->getShippingCosts(null, $shippingMethod->getId(), '00118'); self::assertEquals(4.94, $cost, 'Failed to get cost from API!'); } @@ -176,16 +158,7 @@ public function testGetCostForInactiveService() ); $this->httpClient->setMockResponses(array(new HttpResponse(200, array(), $response))); - $package = Package::defaultPackage(); - $cost = $this->shippingMethodService->getShippingCost( - 20339, - 'IT', - '00118', - 'IT', - '00118', - array($package), - 10 - ); + $cost = $this->getShippingCosts(null, 20339, '00118'); self::assertEquals(0, $cost, 'Failed to get cost from API!'); self::assertEmpty($this->httpClient->getHistory(), 'API should not be called for inactive service'); @@ -201,8 +174,7 @@ public function testGetCostsFromProxy() ); $this->httpClient->setMockResponses(array(new HttpResponse(200, array(), $response))); - $package = Package::defaultPackage(); - $costs = $this->shippingMethodService->getShippingCosts('IT', '00118', 'IT', '00118', array($package), 10); + $costs = $this->getShippingCosts(null, '', '00118'); self::assertEquals(5.06, $costs[$shippingMethod->getId()], 'Failed to get cost from API!'); } @@ -215,8 +187,7 @@ public function testGetCostsFromProxyForMultipleServices() ); $this->httpClient->setMockResponses(array(new HttpResponse(200, array(), $response))); - $package = Package::defaultPackage(); - $costs = $this->shippingMethodService->getShippingCosts('IT', '00118', 'IT', '00118', array($package), 10); + $costs = $this->getShippingCosts(null, '', '00118'); self::assertArrayHasKey(1, $costs, 'Shipping cost for one of the services missing!'); self::assertArrayHasKey(2, $costs, 'Shipping cost for one of the services missing!'); @@ -241,15 +212,7 @@ public function testGetCostFromProxyForMultiplePackages() $firstPackage = new Package(1, 10, 10, 10); $secondPackage = new Package(10, 10, 10, 10); - $cost = $this->shippingMethodService->getShippingCost( - $method->getId(), - 'IT', - '00118', - 'IT', - '00118', - array($firstPackage, $secondPackage), - 10 - ); + $cost = $this->getShippingCosts(array($firstPackage, $secondPackage), $method->getId(), '00118'); self::assertEquals(14.16, $cost, 'Failed to get cost from API!'); } @@ -266,15 +229,9 @@ public function testGetCostsFromProxyForMultiplePackages() $firstPackage = new Package(1, 10, 10, 10); $secondPackage = new Package(10, 10, 10, 10); + $packages = array($firstPackage, $secondPackage, $secondPackage, $secondPackage); - $costs = $this->shippingMethodService->getShippingCosts( - 'IT', - '00118', - 'IT', - '00118', - array($firstPackage, $secondPackage, $secondPackage, $secondPackage), - 10 - ); + $costs = $this->getShippingCosts($packages, '', '00118'); self::assertEquals(41.43, $costs[$method->getId()], 'Failed to get cost from API!'); } @@ -310,16 +267,7 @@ public function testGetCostFallbackToShippingMethod() $this->httpClient->setMockResponses(array(new HttpResponse(500, array(), ''))); - $packages = array(Package::defaultPackage()); - $cost = $this->shippingMethodService->getShippingCost( - $method->getId(), - 'IT', - '00118', - 'IT', - '00118', - $packages, - 10 - ); + $cost = $this->getShippingCosts(null, $method->getId(), '00118'); $costs = $method->getShippingServices(); self::assertEquals($costs[0]->basePrice, $cost, 'Failed to get default cost from local method!'); @@ -332,8 +280,7 @@ public function testGetCostsFallbackToShippingMethod() $this->httpClient->setMockResponses(array(new HttpResponse(500, array(), ''))); - $packages = array(Package::defaultPackage()); - $costs = $this->shippingMethodService->getShippingCosts('IT', '00118', 'IT', '00118', $packages, 10); + $costs = $this->getShippingCosts(null, '', '00118'); $defaultCosts = $shippingMethod->getShippingServices(); self::assertEquals( @@ -350,16 +297,7 @@ public function testGetCostNoFallback() $this->httpClient->setMockResponses(array(new HttpResponse(400, array(), ''))); - $packages = array(Package::defaultPackage()); - $cost = $this->shippingMethodService->getShippingCost( - $method->getId(), - 'IT', - '00118', - 'IT', - '00118', - $packages, - 10 - ); + $cost = $this->getShippingCosts(null, $method->getId(), '00118'); $costs = $method->getShippingServices(); self::assertNotEquals($costs[0]->basePrice, $cost, 'Failed to get default cost!'); @@ -372,26 +310,18 @@ public function testGetCostsNoFallback() $this->httpClient->setMockResponses(array(new HttpResponse(400, array(), ''))); - $packages = array(Package::defaultPackage()); - $costs = $this->shippingMethodService->getShippingCosts('IT', '00118', 'IT', '00118', $packages, 10); + $costs = $this->getShippingCosts(null, '', '00118'); self::assertArrayNotHasKey($method->getId(), $costs); } public function testCalculateCostFixedPricingPolicy() { - $shippingMethod = $this->prepareFixedPricePolicyShippingMethod(1, array(new FixedPricePolicy(0, 10, 12))); + $fixedPricePolicies[] = array(0, 10, 12); + $shippingMethod = $this->prepareFixedPricePolicyShippingMethod(1, $fixedPricePolicies); $this->httpClient->setMockResponses(array(new HttpResponse(404, array(), ''))); - $cost = $this->shippingMethodService->getShippingCost( - $shippingMethod->getId(), - 'IT', - '00127', - 'IT', - '00127', - array(Package::defaultPackage()), - 10 - ); + $cost = $this->getShippingCosts(null, $shippingMethod->getId()); $costs = $shippingMethod->getShippingServices(); // cost should be calculated, and not default @@ -401,24 +331,14 @@ public function testCalculateCostFixedPricingPolicy() public function testCalculateFixedPricingPolicyOutOfBounds() { - $shippingMethod = $this->prepareFixedPricePolicyShippingMethod( - 1, - array( - new FixedPricePolicy(10, 20, 12), - new FixedPricePolicy(5, 10, 12), - ) - ); + $fixedPricePolicies[] = array(10, 20, 12); + $fixedPricePolicies[] = array(5, 10, 12); + $shippingMethod = $this->prepareFixedPricePolicyShippingMethod(1, $fixedPricePolicies); + $shippingMethod->setUsePacklinkPriceIfNotInRange(false); + $this->shippingMethodService->save($shippingMethod); $this->httpClient->setMockResponses(array(new HttpResponse(404, array(), ''))); - $cost = $this->shippingMethodService->getShippingCost( - $shippingMethod->getId(), - 'IT', - '00127', - 'IT', - '00127', - array(Package::defaultPackage()), - 2 - ); + $cost = $this->getShippingCosts(null, $shippingMethod->getId(), '00127', 'IT', 2); $this->assertEmpty($cost); } @@ -433,183 +353,123 @@ public function testCalculateCostsFixedPricingByValuePolicy() $this->calculateCostsFixedPricingPolicy(false); } - /** - * Calculates fixed price cost. - * - * @param float $byWeight - */ - private function calculateCostsFixedPricingPolicy($byWeight) + public function testCalculateCostsByWeightAndPriceRange() { - $serviceId = 20339; - $shippingMethod = $this->prepareFixedPricePolicyShippingMethod( - $serviceId, - array(new FixedPricePolicy(0, 10, 12)), - $byWeight - ); + $shippingMethod = $this->getMethodWithBothRanges(); - $this->httpClient->setMockResponses(array(new HttpResponse(404, array(), ''))); - $costs = $this->shippingMethodService->getShippingCosts( - 'IT', - '00127', - 'IT', - '00127', - array(Package::defaultPackage()), - 10 - ); + $cost = $this->getShippingCosts(null, $shippingMethod->getId()); - $defaultCosts = $shippingMethod->getShippingServices(); - self::assertNotEquals( - $defaultCosts[0]->basePrice, - $costs[$shippingMethod->getId()], - 'Default cost used when calculation should be performed!' - ); - self::assertEquals(12, $costs[$shippingMethod->getId()], 'Calculated cost is wrong!'); + $this->assertEquals(5.55, $cost, 'Calculated cost should be taken.'); } - public function testCalculateCostFixedPricingPolicyOutOfRange() + public function testCalculateCostsByWeightAndPriceRangeInvalidWeight() { - $fixedPricePolicies[] = new FixedPricePolicy(0, 10, 12); - $shippingMethod = $this->prepareFixedPricePolicyShippingMethod(1, $fixedPricePolicies); + $shippingMethod = $this->getMethodWithBothRanges(); - $packages = array(new Package(100, 10, 10, 10)); - $this->httpClient->setMockResponses($this->getBadHttpResponses(2)); - $cost = $this->shippingMethodService->getShippingCost( - $shippingMethod->getId(), - 'IT', - '00127', - 'IT', - '00127', - $packages, - 10 - ); + // weight is out of range + $packages = array(new Package(5, 20, 20, 20)); + $cost = $this->getShippingCosts($packages, $shippingMethod->getId()); - $costs = $shippingMethod->getShippingServices(); - // cost should be calculated, and not default - self::assertNotEquals($costs[0]->basePrice, $cost, 'Default cost used when calculation should be performed!'); - self::assertEquals(12, $cost, 'Calculated cost is wrong!'); + $this->assertEquals(10.76, $cost, 'Calculated cost should NOT be taken.'); + } - $fixedPricePolicies[] = new FixedPricePolicy(10, 20, 10); - $fixedPricePolicies[] = new FixedPricePolicy(20, 30, 8); - $shippingMethod->setFixedPriceByWeightPolicy($fixedPricePolicies); - $this->shippingMethodService->save($shippingMethod); + public function testCalculateCostsByWeightAndPriceRangeInvalidPrice() + { + $shippingMethod = $this->getMethodWithBothRanges(); - $cost = $this->shippingMethodService->getShippingCost( - $shippingMethod->getId(), - 'IT', - '00127', - 'IT', - '00127', - $packages, - 10 - ); - self::assertEquals(8, $cost, 'Calculated cost is wrong!'); + // price is out of range + $cost = $this->getShippingCosts(null, $shippingMethod->getId(), '00118', 'IT', 100); + + $this->assertEquals(10.76, $cost, 'Calculated cost should NOT be taken.'); } - public function testCalculateCostsFixedPricingPolicyOutOfRange() + public function testCalculateCostFixedPricingPolicyOutOfRange() { - $serviceId = 20339; - $fixedPricePolicies[] = new FixedPricePolicy(0, 10, 12); - $shippingMethod = $this->prepareFixedPricePolicyShippingMethod($serviceId, $fixedPricePolicies); + $fixedPricePolicies[] = array(0, 10, 12); + $shippingMethod = $this->prepareFixedPricePolicyShippingMethod(1, $fixedPricePolicies); - $package = new Package(100, 10, 10, 10); + $packages = array(new Package(100, 10, 10, 10)); $this->httpClient->setMockResponses($this->getBadHttpResponses(2)); - $costs = $this->shippingMethodService->getShippingCosts('IT', '00127', 'IT', '00127', array($package), 10); + $cost = $this->getShippingCosts($packages, $shippingMethod->getId()); - $defaultCosts = $shippingMethod->getShippingServices(); - self::assertNotEquals( - $defaultCosts[0]->basePrice, - $costs[$shippingMethod->getId()], - 'Default cost used when calculation should be performed!' - ); - self::assertEquals(12, $costs[$shippingMethod->getId()], 'Calculated cost is wrong!'); + $services = $shippingMethod->getShippingServices(); + // default cost should be used when out of range + self::assertEquals($services[0]->basePrice, $cost, 'Calculation should not be performed!'); - $fixedPricePolicies[] = new FixedPricePolicy(10, 20, 10); - $fixedPricePolicies[] = new FixedPricePolicy(20, 30, 8); - $shippingMethod->setFixedPriceByWeightPolicy($fixedPricePolicies); + $shippingMethod->addPricingPolicy($this->getFixedPricePolicy(true, 10, 20, 10)); + $shippingMethod->addPricingPolicy($this->getFixedPricePolicy(true, 20, 30, 8)); $this->shippingMethodService->save($shippingMethod); - $costs = $this->shippingMethodService->getShippingCosts('IT', '00127', 'IT', '00127', array($package), 10); - self::assertEquals(8, $costs[$shippingMethod->getId()], 'Calculated cost is wrong!'); + $cost = $this->getShippingCosts($packages, $shippingMethod->getId()); + self::assertEquals($services[0]->basePrice, $cost, 'Calculation should not be performed!'); } public function testCalculateCostFixedPricingPolicyInRange() { - $method = $this->prepareFixedPricePolicyShippingMethod( - 1, - array( - new FixedPricePolicy(0, 10, 12), - new FixedPricePolicy(10, 20, 10), - new FixedPricePolicy(20, 30, 8), - ) - ); + $fixedPricePolicies[] = array(0, 10, 12); + $fixedPricePolicies[] = array(10, 20, 10); + $fixedPricePolicies[] = array(20, 30, 8); + $method = $this->prepareFixedPricePolicyShippingMethod(1, $fixedPricePolicies); $id = $method->getId(); - $package = new Package(8, 10, 10, 10); + $packages = array(new Package(8, 10, 10, 10)); $this->httpClient->setMockResponses($this->getBadHttpResponses(5)); - $cost = $this->shippingMethodService->getShippingCost($id, 'IT', '00127', 'IT', '00127', array($package), 10); + $cost = $this->getShippingCosts($packages, $id); self::assertEquals(12, $cost, 'Calculated cost is wrong!'); - $package->weight = 10; - $cost = $this->shippingMethodService->getShippingCost($id, 'IT', '00127', 'IT', '00127', array($package), 10); - self::assertEquals(10, $cost, 'Calculated cost is wrong!'); + $packages[0]->weight = 10; + $cost = $this->getShippingCosts($packages, $id); + self::assertEquals(12, $cost, 'Calculated cost is wrong!'); - $package->weight = 14; - $cost = $this->shippingMethodService->getShippingCost($id, 'IT', '00127', 'IT', '00127', array($package), 10); + $packages[0]->weight = 14; + $cost = $this->getShippingCosts($packages, $id); self::assertEquals(10, $cost, 'Calculated cost is wrong!'); - $package->weight = 20; - $cost = $this->shippingMethodService->getShippingCost($id, 'IT', '00127', 'IT', '00127', array($package), 10); - self::assertEquals(8, $cost, 'Calculated cost is wrong!'); + $packages[0]->weight = 20; + $cost = $this->getShippingCosts($packages, $id); + self::assertEquals(10, $cost, 'Calculated cost is wrong!'); - $package->weight = 25; - $cost = $this->shippingMethodService->getShippingCost($id, 'IT', '00127', 'IT', '00127', array($package), 10); + $packages[0]->weight = 25; + $cost = $this->getShippingCosts($packages, $id); self::assertEquals(8, $cost, 'Calculated cost is wrong!'); } public function testCalculateCostsFixedPricingPolicyInRange() { - $method = $this->prepareFixedPricePolicyShippingMethod( - 20339, - array( - new FixedPricePolicy(0, 10, 12), - new FixedPricePolicy(10, 20, 10), - new FixedPricePolicy(20, 30, 8), - ) - ); + $fixedPricePolicies[] = array(0, 10, 12); + $fixedPricePolicies[] = array(10, 20, 10); + $fixedPricePolicies[] = array(20, 30, 8); + $method = $this->prepareFixedPricePolicyShippingMethod(20339, $fixedPricePolicies); - $package = new Package(8, 10, 10, 10); + $packages = array(new Package(8, 10, 10, 10)); $this->httpClient->setMockResponses($this->getBadHttpResponses(5)); - $costs = $this->shippingMethodService->getShippingCosts('IT', '00127', 'IT', '00127', array($package), 10); + $costs = $this->getShippingCosts($packages); self::assertEquals(12, $costs[$method->getId()], 'Calculated cost is wrong!'); - $package->weight = 10; - $costs = $this->shippingMethodService->getShippingCosts('IT', '00127', 'IT', '00127', array($package), 10); - self::assertEquals(10, $costs[$method->getId()], 'Calculated cost is wrong!'); + $packages[0]->weight = 10; + $costs = $this->getShippingCosts($packages); + self::assertEquals(12, $costs[$method->getId()], 'Calculated cost is wrong!'); - $package->weight = 14; - $costs = $this->shippingMethodService->getShippingCosts('IT', '00127', 'IT', '00127', array($package), 10); + $packages[0]->weight = 14; + $costs = $this->getShippingCosts($packages); self::assertEquals(10, $costs[$method->getId()], 'Calculated cost is wrong!'); - $package->weight = 20; - $costs = $this->shippingMethodService->getShippingCosts('IT', '00127', 'IT', '00127', array($package), 10); - self::assertEquals(8, $costs[$method->getId()], 'Calculated cost is wrong!'); + $packages[0]->weight = 20; + $costs = $this->getShippingCosts($packages); + self::assertEquals(10, $costs[$method->getId()], 'Calculated cost is wrong!'); - $package->weight = 25; - $costs = $this->shippingMethodService->getShippingCosts('IT', '00127', 'IT', '00127', array($package), 10); + $packages[0]->weight = 25; + $costs = $this->getShippingCosts($packages); self::assertEquals(8, $costs[$method->getId()], 'Calculated cost is wrong!'); } public function testCalculateCostFixedPricingPolicyInRangeMultiple() { // this method has costs of 10.76 - $method = $this->prepareFixedPricePolicyShippingMethod( - 1, - array( - new FixedPricePolicy(0, 10, 12), - new FixedPricePolicy(10, 20, 10), - new FixedPricePolicy(20, 30, 8), - ) - ); + $fixedPricePolicies[] = array(0, 10, 12); + $fixedPricePolicies[] = array(10, 20, 10); + $fixedPricePolicies[] = array(20, 30, 8); + $method = $this->prepareFixedPricePolicyShippingMethod(20339, $fixedPricePolicies); $this->httpClient->setMockResponses($this->getBadHttpResponses(3)); $packages = array(); @@ -642,45 +502,22 @@ public function testCalculateCostPercentPricingPolicyIncreased() { $method = $this->preparePercentPricePolicyShippingMethod(1, true); - $package = Package::defaultPackage(); $this->httpClient->setMockResponses($this->getBadHttpResponses(3)); - $cost = $this->shippingMethodService->getShippingCost( - $method->getId(), - 'IT', - '00127', - 'IT', - '00127', - array($package), - 10 - ); + $cost = $this->getShippingCosts(null, $method->getId()); self::assertEquals(12.27, $cost, 'Calculated cost is wrong!'); - $method->setPercentPricePolicy(new PercentPricePolicy(true, 50)); + $method->resetPricingPolicies(); + $method->addPricingPolicy($this->getPercentPricePolicy(50, true)); $this->shippingMethodService->save($method); - $cost = $this->shippingMethodService->getShippingCost( - $method->getId(), - 'IT', - '00127', - 'IT', - '00127', - array($package), - 10 - ); + $cost = $this->getShippingCosts(null, $method->getId()); self::assertEquals(16.14, $cost, 'Calculated cost is wrong!'); - $method->setPercentPricePolicy(new PercentPricePolicy(true, 120)); + $method->resetPricingPolicies(); + $method->addPricingPolicy($this->getPercentPricePolicy(120, true)); $this->shippingMethodService->save($method); - $cost = $this->shippingMethodService->getShippingCost( - $method->getId(), - 'IT', - '00127', - 'IT', - '00127', - array($package), - 10 - ); + $cost = $this->getShippingCosts(null, $method->getId()); self::assertEquals(23.67, $cost, 'Calculated cost is wrong!'); } @@ -689,34 +526,29 @@ public function testCalculateCostsPercentPricingPolicyIncreased() $method = $this->preparePercentPricePolicyShippingMethod(20339, true); $this->httpClient->setMockResponses(array(new HttpResponse(404, array(), ''))); - $costs = $this->shippingMethodService->getShippingCosts( - 'IT', - '00127', - 'IT', - '00127', - array(Package::defaultPackage()), - 10 - ); + $costs = $this->getShippingCosts(); self::assertEquals(12.27, $costs[$method->getId()], 'Calculated cost is wrong!'); } public function testCalculateCostPercentPricingPolicyDecreased() { $method = $this->preparePercentPricePolicyShippingMethod(); + $id = $method->getId(); $this->httpClient->setMockResponses($this->getBadHttpResponses(3)); $packages = array(Package::defaultPackage()); - $this->checkShippingCostMatchesExpectedCost($method->getId(), $packages, 9.25); + $this->checkShippingCostMatchesExpectedCost($id, $packages, 9.25); - $method->setPercentPricePolicy(new PercentPricePolicy(false, 50)); + $method->resetPricingPolicies(); + $method->addPricingPolicy($this->getPercentPricePolicy(50, false)); $this->shippingMethodService->save($method); - $this->checkShippingCostMatchesExpectedCost($method->getId(), $packages, 5.38); + $this->checkShippingCostMatchesExpectedCost($id, $packages, 5.38); - $method->setPercentPricePolicy(new PercentPricePolicy(false, 80)); + $method->resetPricingPolicies(); + $method->addPricingPolicy($this->getPercentPricePolicy(80, false)); $this->shippingMethodService->save($method); - $this->checkShippingCostMatchesExpectedCost($method->getId(), $packages, 2.15); } @@ -724,7 +556,7 @@ public function testCalculateCostsPercentPricingPolicyDecreased() { $method = $this->preparePercentPricePolicyShippingMethod(20339); - $this->httpClient->setMockResponses(array(new HttpResponse(404, array(), ''))); + $this->httpClient->setMockResponses($this->getBadHttpResponses(1)); $this->checkShippingCostsMatchExpectedCost(array(Package::defaultPackage()), 9.25, $method->getId()); } @@ -738,6 +570,18 @@ public function testNoMethodsCalculation() self::assertEmpty($this->shippingMethodService->getShippingCosts('', '', '', '', array(), 10)); } + public function testCostCalculationForUnknownDepartureAndDestination() + { + $this->httpClient->setMockResponses(array(new HttpResponse(400, array(), ''))); + + $packages = array(Package::defaultPackage()); + $costs = $this->shippingMethodService->getShippingCosts('KK', '00118', 'IT', '00118', $packages, 10); + self::assertEmpty($costs); + + $costs = $this->shippingMethodService->getShippingCosts('IT', '00118', 'KK', '00118', $packages, 10); + self::assertEmpty($costs); + } + public function testCostCalculationForInvalidDestinationPostalCode() { $packages = array(Package::defaultPackage()); @@ -762,18 +606,6 @@ public function testCostCalculationForInvalidTransformedPostalCode() self::assertEmpty($this->shippingMethodService->getShippingCosts('IT', '00118', 'US', '10018-0005', $packages, 10)); } - public function testCostCalculationForUnknownDepartureAndDestination() - { - $this->httpClient->setMockResponses(array(new HttpResponse(400, array(), ''))); - - $packages = array(Package::defaultPackage()); - $costs = $this->shippingMethodService->getShippingCosts('KK', '00118', 'IT', '00118', $packages, 10); - self::assertEmpty($costs); - - $costs = $this->shippingMethodService->getShippingCosts('IT', '00118', 'KK', '00118', $packages, 10); - self::assertEmpty($costs); - } - /** * @dataProvider wrongParametersProvider * @@ -797,6 +629,29 @@ public function testMissingShippingParameters($fromCountry, $fromZip, $toCountry $this->assertEmpty($result); } + /** + * Calculates fixed price cost. + * + * @param float $byWeight + */ + private function calculateCostsFixedPricingPolicy($byWeight) + { + $serviceId = 20339; + $fixedPricePolicies[] = array(0, 10, 12); + $shippingMethod = $this->prepareFixedPricePolicyShippingMethod($serviceId, $fixedPricePolicies, $byWeight); + + $this->httpClient->setMockResponses(array(new HttpResponse(404, array(), ''))); + $costs = $this->getShippingCosts(); + + $defaultCosts = $shippingMethod->getShippingServices(); + self::assertNotEquals( + $defaultCosts[0]->basePrice, + $costs[$shippingMethod->getId()], + 'Default cost used when calculation should be performed!' + ); + self::assertEquals(12, $costs[$shippingMethod->getId()], 'Calculated cost is wrong!'); + } + /** * @param int $methodId * @param Package[] $packages @@ -805,7 +660,7 @@ public function testMissingShippingParameters($fromCountry, $fromZip, $toCountry */ protected function checkShippingCostMatchesExpectedCost($methodId, array $packages, $expectedCost, $to = 'IT') { - $cost = $this->shippingMethodService->getShippingCost($methodId, 'IT', '00127', $to, '00127', $packages, 10); + $cost = $this->getShippingCosts($packages, $methodId, '00127', $to); self::assertEquals($expectedCost, $cost, 'Calculated cost is wrong!'); } @@ -817,29 +672,27 @@ protected function checkShippingCostMatchesExpectedCost($methodId, array $packag */ protected function checkShippingCostsMatchExpectedCost(array $packages, $expectedCost, $methodId) { - $costs = $this->shippingMethodService->getShippingCosts('IT', '00127', 'IT', '00127', $packages, 10); + $costs = $this->getShippingCosts($packages, '', '00127'); self::assertEquals($expectedCost, $costs[$methodId], 'Calculated cost is wrong!'); } /** * @param int $serviceId - * @param FixedPricePolicy[] $fixedPricePolicies + * @param array $prices * @param bool $byWeight * * @return \Packlink\BusinessLogic\ShippingMethod\Models\ShippingMethod */ protected function prepareFixedPricePolicyShippingMethod( $serviceId = 1, - array $fixedPricePolicies = array(), + array $prices = array(), $byWeight = true ) { $shippingMethod = $this->addShippingMethod($serviceId); - if ($byWeight) { - $shippingMethod->setFixedPriceByWeightPolicy($fixedPricePolicies); - } else { - $shippingMethod->setFixedPriceByValuePolicy($fixedPricePolicies); + foreach ($prices as $price) { + $shippingMethod->addPricingPolicy($this->getFixedPricePolicy($byWeight, $price[0], $price[1], $price[2])); } $this->shippingMethodService->save($shippingMethod); @@ -857,8 +710,8 @@ protected function prepareFixedPricePolicyShippingMethod( protected function preparePercentPricePolicyShippingMethod($serviceId = 1, $increase = false, $percent = 14) { $shippingMethod = $this->addShippingMethod($serviceId); + $shippingMethod->addPricingPolicy($this->getPercentPricePolicy($percent, $increase)); - $shippingMethod->setPercentPricePolicy(new PercentPricePolicy($increase, $percent)); $this->shippingMethodService->save($shippingMethod); return $shippingMethod; @@ -866,20 +719,111 @@ protected function preparePercentPricePolicyShippingMethod($serviceId = 1, $incr /** * @param $serviceId - * * @param bool $active + * @param string $carrier * * @return \Packlink\BusinessLogic\ShippingMethod\Models\ShippingMethod */ - protected function addShippingMethod($serviceId, $active = true) + protected function addShippingMethod($serviceId, $active = true, $carrier = 'PSP') { - $shippingMethod = $this->shippingMethodService->add($this->getShippingServiceDetails($serviceId, 'PSP')); + $shippingMethod = $this->shippingMethodService->add($this->getShippingServiceDetails($serviceId, $carrier)); $shippingMethod->setActivated($active); $this->shippingMethodService->save($shippingMethod); return $shippingMethod; } + protected function getMethodWithBothRanges( + $fallbackToDefault = true, + $fromPrice = 5, + $toPrice = 15, + $fromWeight = 0.5, + $toWeight = 1.5, + $fixedPrice = 5.55 + ) { + /** @noinspection PhpUnhandledExceptionInspection */ + $policy = ShippingPricePolicy::fromArray( + array( + 'range_type' => ShippingPricePolicy::RANGE_PRICE_AND_WEIGHT, + 'from_price' => $fromPrice, + 'to_price' => $toPrice, + 'from_weight' => $fromWeight, + 'to_weight' => $toWeight, + 'pricing_policy' => ShippingPricePolicy::POLICY_FIXED_PRICE, + 'fixed_price' => $fixedPrice, + ) + ); + $shippingMethod = $this->addShippingMethod(1); + $shippingMethod->setUsePacklinkPriceIfNotInRange($fallbackToDefault); + $shippingMethod->addPricingPolicy($policy); + $this->shippingMethodService->save($shippingMethod); + + return $shippingMethod; + } + + protected function getShippingCosts(array $packages = null, $id = '', $zip = '00127', $to = 'IT', $total = 9.9) + { + if (empty($packages)) { + $packages = array(Package::defaultPackage()); + } + + if ($id) { + return $this->shippingMethodService->getShippingCost($id, 'IT', $zip, $to, $zip, $packages, $total); + } + + return $this->shippingMethodService->getShippingCosts('IT', $zip, $to, $zip, $packages, $total); + } + + protected function getPercentPricePolicy($percent, $increase = false) + { + return $this->getPricingPolicy( + ShippingPricePolicy::RANGE_PRICE, + 0, + 10, + ShippingPricePolicy::POLICY_PACKLINK_ADJUST, + $percent, + $increase + ); + } + + protected function getFixedPricePolicy($byWeight, $from, $to, $price) + { + return $this->getPricingPolicy( + $byWeight ? ShippingPricePolicy::RANGE_WEIGHT : ShippingPricePolicy::RANGE_PRICE, + $from, + $to, + ShippingPricePolicy::POLICY_FIXED_PRICE, + 0, + false, + $price + ); + } + + protected function getPricingPolicy( + $rangeType = ShippingPricePolicy::RANGE_PRICE, + $from = 0, + $to = 0, + $policy = ShippingPricePolicy::POLICY_PACKLINK, + $changePercent = 50, + $increase = true, + $fixedPrice = 20 + ) { + /** @noinspection PhpUnhandledExceptionInspection */ + return ShippingPricePolicy::fromArray( + array( + 'range_type' => $rangeType, + 'from_price' => $from, + 'to_price' => $to, + 'from_weight' => $from, + 'to_weight' => $to, + 'pricing_policy' => $policy, + 'increase' => $increase, + 'change_percent' => $changePercent, + 'fixed_price' => $fixedPrice, + ) + ); + } + /** * Retrieves shipping service details. * diff --git a/tests/BusinessLogic/ShippingMethod/ShippingMethodServiceTest.php b/tests/BusinessLogic/ShippingMethod/ShippingMethodServiceTest.php index b87fd607..6351ff39 100644 --- a/tests/BusinessLogic/ShippingMethod/ShippingMethodServiceTest.php +++ b/tests/BusinessLogic/ShippingMethod/ShippingMethodServiceTest.php @@ -1,10 +1,9 @@ shippingMethodService->getActiveMethods()); self::assertCount(2, $this->shippingMethodService->getAllMethods()); + self::assertCount(2, $this->shippingMethodService->getInactiveMethods()); $this->shippingMethodService->activate($method->getId()); diff --git a/tests/BusinessLogic/ShippingMethod/TestShopShippingMethodService.php b/tests/BusinessLogic/ShippingMethod/TestShopShippingMethodService.php index c03639cf..3ab5dac9 100644 --- a/tests/BusinessLogic/ShippingMethod/TestShopShippingMethodService.php +++ b/tests/BusinessLogic/ShippingMethod/TestShopShippingMethodService.php @@ -114,4 +114,14 @@ public function getCarrierLogoFilePath($carrierName) { return 'tmp://' . $carrierName; } + + /** + * Disables shop shipping services/carriers. + * + * @return boolean TRUE if operation succeeded; otherwise, false. + */ + public function disableShopServices() + { + return true; + } } diff --git a/tests/BusinessLogic/Utility/DtoValidatorTest.php b/tests/BusinessLogic/Utility/DtoValidatorTest.php new file mode 100644 index 00000000..96287b79 --- /dev/null +++ b/tests/BusinessLogic/Utility/DtoValidatorTest.php @@ -0,0 +1,72 @@ +assertTrue(DtoValidator::isPhoneValid($number), "[$number] failed phone validation."); + } + } + + public function testInvalidPhoneNumbers() + { + $numbers = array( + '', + 'a754-3010', + '75b43010', + '754 30c10', + '754.3010d', + '#754+3010', + '75f43010', + '~-7543010', + '.75@43010', + '+754 &3010', + '(541) 754-3010\\', + '(541) (754)-3010?', + '(541) 754-(3010)!', + '@@(541754-3010)', + '#+1-541-754-3010', + '1-*541-754-3010', + '001-541-754-3010\'', + '"191 541 754 3010', + '\\636\\-48018', + '(089)` / 636-48018', + '`+49`-89-636-48018', + '?19-49-89-636-48018', + ); + + foreach ($numbers as $number) { + $this->assertFalse(DtoValidator::isPhoneValid($number), "[$number] should not pass phone validation."); + } + } +} \ No newline at end of file diff --git a/tests/Infrastructure/Common/TestComponents/ORM/MemoryRepository.php b/tests/Infrastructure/Common/TestComponents/ORM/MemoryRepository.php index 8a88cd26..f735afbf 100644 --- a/tests/Infrastructure/Common/TestComponents/ORM/MemoryRepository.php +++ b/tests/Infrastructure/Common/TestComponents/ORM/MemoryRepository.php @@ -46,7 +46,7 @@ public function select(QueryFilter $filter = null) $groups = $filter ? $this->buildConditionGroups($filter, $fieldIndexMap) : array(); $all = array_filter( - MemoryStorage::$storage, + $this->getStorage(), function ($a) use ($type) { return $a['type'] === $type; } @@ -102,7 +102,7 @@ public function selectOne(QueryFilter $filter = null) */ public function save(Entity $entity) { - $id = MemoryStorage::generateId(); + $id = $this->generateId(); $entity->setId($id); $this->saveEntityToStorage($entity); @@ -118,7 +118,8 @@ public function save(Entity $entity) */ public function update(Entity $entity) { - $result = $entity->getId() !== null && isset(MemoryStorage::$storage[$entity->getId()]); + $storage = $this->getStorage(); + $result = $entity->getId() !== null && isset($storage[$entity->getId()]); if ($result) { $this->saveEntityToStorage($entity); } @@ -135,9 +136,10 @@ public function update(Entity $entity) */ public function delete(Entity $entity) { - $result = $entity->getId() !== null && isset(MemoryStorage::$storage[$entity->getId()]); + $storage = $this->getStorage(); + $result = $entity->getId() !== null && isset($storage[$entity->getId()]); if ($result) { - unset(MemoryStorage::$storage[$entity->getId()]); + $this->deleteFromStorage($entity->getId()); } return $result; @@ -163,6 +165,47 @@ public function setEntityClass($entityClass) $this->entityClass = $entityClass; } + /** + * Gets the storage. + * + * @return \Logeecom\Infrastructure\ORM\Entity[] + */ + protected function getStorage() + { + return MemoryStorage::$storage; + } + + /** + * Saves an item to storage. + * + * @param $key + * @param $item + */ + protected function saveToStorage($key, $item) + { + MemoryStorage::$storage[$key] = $item; + } + + /** + * Deletes an item from storage. + * + * @param $key + */ + protected function deleteFromStorage($key) + { + unset(MemoryStorage::$storage[$key]); + } + + /** + * Generates a new ID. + * + * @return int + */ + protected function generateId() + { + return MemoryStorage::generateId(); + } + /** * Saves entity to the database. * @@ -195,7 +238,7 @@ private function saveEntityToStorage(Entity $entity) $storageItem['index_' . $index] = $value; } - MemoryStorage::$storage[$entity->getId()] = $storageItem; + $this->saveToStorage($entity->getId(), $storageItem); } /** diff --git a/tests/Infrastructure/Common/TestComponents/TestRegistrationInfoService.php b/tests/Infrastructure/Common/TestComponents/TestRegistrationInfoService.php new file mode 100644 index 00000000..573262e4 --- /dev/null +++ b/tests/Infrastructure/Common/TestComponents/TestRegistrationInfoService.php @@ -0,0 +1,14 @@ +insertQueueItems(); $repository = RepositoryRegistry::getQueueItemRepository(); - $this->assertCount(1, $repository->findOldestQueuedItems(Priority::LOW)); $this->assertCount(1, $repository->findOldestQueuedItems(Priority::NORMAL)); }