diff --git a/src/Migration/Pdk/PdkDeliveryOptionsMigration.php b/src/Migration/Pdk/PdkDeliveryOptionsMigration.php index 832f306a..70bb78cc 100644 --- a/src/Migration/Pdk/PdkDeliveryOptionsMigration.php +++ b/src/Migration/Pdk/PdkDeliveryOptionsMigration.php @@ -12,6 +12,8 @@ use MyParcelNL\PrestaShop\Migration\Util\CastValue; use MyParcelNL\PrestaShop\Migration\Util\DataMigrator; use MyParcelNL\PrestaShop\Migration\Util\MigratableValue; +use MyParcelNL\PrestaShop\Migration\Util\ToDeliveryTypeName; +use MyParcelNL\PrestaShop\Migration\Util\ToPackageTypeName; use MyParcelNL\PrestaShop\Migration\Util\TransformValue; use MyParcelNL\PrestaShop\Repository\PsOrderDataRepository; @@ -67,25 +69,8 @@ private function getDeliveryOptionsTransformationMap(): Generator }) ); - yield new MigratableValue( - 'deliveryType', - DeliveryOptions::DELIVERY_TYPE, - new TransformValue(function ($value) { - return in_array($value, DeliveryOptions::DELIVERY_TYPES_NAMES, true) - ? $value - : DeliveryOptions::DEFAULT_DELIVERY_TYPE_NAME; - }) - ); - - yield new MigratableValue( - 'packageType', - DeliveryOptions::PACKAGE_TYPE, - new TransformValue(function ($value) { - return in_array($value, DeliveryOptions::PACKAGE_TYPES_NAMES, true) - ? $value - : DeliveryOptions::DEFAULT_PACKAGE_TYPE_NAME; - }) - ); + yield new MigratableValue('deliveryType', DeliveryOptions::DELIVERY_TYPE, new ToDeliveryTypeName()); + yield new MigratableValue('packageType', DeliveryOptions::PACKAGE_TYPE, new ToPackageTypeName()); yield new MigratableValue( 'date', diff --git a/src/Migration/Pdk/PdkProductSettingsMigration.php b/src/Migration/Pdk/PdkProductSettingsMigration.php index 40e88ec1..801c77aa 100644 --- a/src/Migration/Pdk/PdkProductSettingsMigration.php +++ b/src/Migration/Pdk/PdkProductSettingsMigration.php @@ -4,24 +4,23 @@ namespace MyParcelNL\PrestaShop\Migration\Pdk; +use Generator; +use MyParcelNL\Pdk\Base\Service\CountryCodes; +use MyParcelNL\Pdk\Facade\Logger; +use MyParcelNL\Pdk\Settings\Model\ProductSettings; +use MyParcelNL\Pdk\Types\Service\TriStateService; +use MyParcelNL\PrestaShop\Facade\EntityManager; use MyParcelNL\PrestaShop\Migration\AbstractLegacyPsMigration; use MyParcelNL\PrestaShop\Migration\Util\DataMigrator; +use MyParcelNL\PrestaShop\Migration\Util\MigratableValue; +use MyParcelNL\PrestaShop\Migration\Util\ToPackageTypeName; +use MyParcelNL\PrestaShop\Migration\Util\ToTriStateValue; +use MyParcelNL\PrestaShop\Migration\Util\TransformValue; use MyParcelNL\PrestaShop\Repository\PsProductSettingsRepository; +use MyParcelNL\Sdk\src\Support\Collection; final class PdkProductSettingsMigration extends AbstractPsPdkMigration { - private const LEGACY_PRODUCT_SETTINGS_MAP = [ - 'MYPARCELNL_PACKAGE_TYPE' => 'packageType', - 'MYPARCELNL_CUSTOMS_ORIGIN' => 'countryOfOrigin', - 'MYPARCELNL_CUSTOMS_CODE' => 'customsCode', - 'MYPARCELNL_INSURANCE' => 'exportInsurance', - 'MYPARCELNL_SIGNATURE_REQUIRED' => 'exportSignature', - 'MYPARCELNL_RETURN_PACKAGE' => 'exportReturn', - 'MYPARCELNL_PACKAGE_FORMAT' => 'exportLargeFormat', - 'MYPARCELNL_ONLY_RECIPIENT' => 'exportOnlyRecipient', - 'MYPARCELNL_AGE_CHECK' => 'exportAgeCheck', - ]; - /** * @var \MyParcelNL\PrestaShop\Repository\PsProductSettingsRepository */ @@ -45,7 +44,6 @@ public function __construct(PsProductSettingsRepository $productSettingsReposito /** * @return void - * @throws \Doctrine\ORM\ORMException * @throws \PrestaShopDatabaseException */ public function up(): void @@ -54,36 +52,115 @@ public function up(): void } /** - * @throws \PrestaShopDatabaseException + * @return \Generator<\MyParcelNL\PrestaShop\Migration\Util\MigratableValue> + */ + private function getProductSettingsTransformationMap(): Generator + { + yield new MigratableValue( + 'MYPARCELNL_PACKAGE_TYPE', + ProductSettings::PACKAGE_TYPE, + new ToPackageTypeName(TriStateService::INHERIT) + ); + + yield new MigratableValue( + 'MYPARCELNL_AGE_CHECK', + ProductSettings::EXPORT_AGE_CHECK, + new ToTriStateValue() + ); + + yield new MigratableValue( + 'MYPARCELNL_RECIPIENT_ONLY', + ProductSettings::EXPORT_ONLY_RECIPIENT, + new ToTriStateValue() + ); + + yield new MigratableValue( + 'MYPARCELNL_RETURN_PACKAGE', + ProductSettings::EXPORT_RETURN, + new ToTriStateValue() + ); + + yield new MigratableValue( + 'MYPARCELNL_SIGNATURE_REQUIRED', + ProductSettings::EXPORT_SIGNATURE, + new ToTriStateValue() + ); + + yield new MigratableValue( + 'MYPARCELNL_INSURANCE', + ProductSettings::EXPORT_INSURANCE, + new ToTriStateValue() + ); + + yield new MigratableValue( + 'MYPARCELNL_PACKAGE_FORMAT', + ProductSettings::EXPORT_LARGE_FORMAT, + new TransformValue(function ($value) { + switch ((int) $value) { + case 1: + return TriStateService::DISABLED; + + case 2: + return TriStateService::ENABLED; + + default: + return TriStateService::INHERIT; + } + }) + ); + + yield new MigratableValue( + 'MYPARCELNL_CUSTOMS_CODE', + ProductSettings::CUSTOMS_CODE, + new ToTriStateValue(TriStateService::TYPE_STRING) + ); + + yield new MigratableValue( + 'MYPARCELNL_CUSTOMS_ORIGIN', + ProductSettings::COUNTRY_OF_ORIGIN, + new TransformValue(function ($value) { + // Assume the user did not intend to set Afghanistan as the country of origin. It's the default value + // that is saved whenever the user saves any product, even without opening the module settings. + if (CountryCodes::CC_AF === $value) { + return TriStateService::INHERIT; + } + + return in_array($value, CountryCodes::ALL, true) + ? $value + : TriStateService::INHERIT; + }) + ); + } + + /** + * @return void * @throws \Doctrine\ORM\ORMException + * @throws \PrestaShopDatabaseException */ private function migrateProductSettings(): void { - $oldProductSettings = $this->getAllRows(AbstractLegacyPsMigration::LEGACY_TABLE_PRODUCT_CONFIGURATION); + $allRows = $this->getAllRows(AbstractLegacyPsMigration::LEGACY_TABLE_PRODUCT_CONFIGURATION); + $rowsByProduct = $allRows->groupBy('id_product'); - $productsWithSettings = []; + $rowsByProduct->each(function (Collection $rows, int $productId) { + if ($this->productSettingsRepository->findOneBy(['productId' => $productId])) { + Logger::info("Product settings for product $productId already exist, skipping"); - foreach ($oldProductSettings as $oldProductSetting) { - if (! array_key_exists($oldProductSetting['name'], self::LEGACY_PRODUCT_SETTINGS_MAP)) { - continue; + return; } - $productsWithSettings[$oldProductSetting['id_product']][self::LEGACY_PRODUCT_SETTINGS_MAP[$oldProductSetting['name']]] = - $oldProductSetting['value']; - } - - foreach ($productsWithSettings as $productId => $productSettings) { - $this->productSettingsRepository->updateOrCreate( - [ - 'productId' => (int) $productId, - ], - [ - 'data' => json_encode([ - 'id' => 'product', - 'data' => $productSettings, - ]), - ] - ); - } + $oldSettings = $rows + ->pluck('value', 'name') + ->toArrayWithoutNull(); + + $newSettings = $this->valueMigrator->transform($oldSettings, $this->getProductSettingsTransformationMap()); + + $this->productSettingsRepository->create([ + 'productId' => $productId, + 'data' => json_encode(['settings' => $newSettings]), + ]); + }); + + EntityManager::flush(); } } diff --git a/src/Migration/Pdk/PdkSettingsMigration.php b/src/Migration/Pdk/PdkSettingsMigration.php index 9bb8ba85..419b398a 100644 --- a/src/Migration/Pdk/PdkSettingsMigration.php +++ b/src/Migration/Pdk/PdkSettingsMigration.php @@ -390,7 +390,7 @@ private function getSettingsTransformationMap(): Generator 'MYPARCELNL_LABEL_SIZE', implode('.', [LabelSettings::ID, LabelSettings::FORMAT]), new TransformValue(function ($value): string { - return 'A6' === $value ? 'a6' : 'a4'; + return 'A6' === $value ? LabelSettings::FORMAT_A6 : LabelSettings::FORMAT_A4; }) ); diff --git a/src/Migration/Util/ToDeliveryTypeName.php b/src/Migration/Util/ToDeliveryTypeName.php index 44f0438b..2e5371e2 100644 --- a/src/Migration/Util/ToDeliveryTypeName.php +++ b/src/Migration/Util/ToDeliveryTypeName.php @@ -1,12 +1,26 @@ defaultValue = $defaultValue; } /** @@ -16,16 +30,6 @@ public function __construct() */ protected function convert($value): string { - if (is_numeric($value)) { - if (in_array((int) $value, DeliveryOptions::DELIVERY_TYPES_IDS, true)) { - return array_flip(DeliveryOptions::DELIVERY_TYPES_NAMES_IDS_MAP)[$value]; - } - - return DeliveryOptions::DEFAULT_DELIVERY_TYPE_NAME; - } - - return in_array($value, DeliveryOptions::DELIVERY_TYPES_NAMES, true) - ? $value - : DeliveryOptions::DEFAULT_DELIVERY_TYPE_NAME; + return Utils::convertToName($value, DeliveryOptions::DELIVERY_TYPES_NAMES_IDS_MAP) ?? $this->defaultValue; } } diff --git a/src/Migration/Util/ToPackageTypeName.php b/src/Migration/Util/ToPackageTypeName.php index 2f6ca0de..caefc4f0 100644 --- a/src/Migration/Util/ToPackageTypeName.php +++ b/src/Migration/Util/ToPackageTypeName.php @@ -1,23 +1,36 @@ defaultValue = $defaultValue; + } - return in_array($value, DeliveryOptions::PACKAGE_TYPES_NAMES, true) - ? $value - : DeliveryOptions::DEFAULT_PACKAGE_TYPE_NAME; - }); + /** + * @param mixed $value + * + * @return mixed + */ + protected function convert($value) + { + return Utils::convertToName($value, DeliveryOptions::PACKAGE_TYPES_NAMES_IDS_MAP) ?? $this->defaultValue; } } + diff --git a/src/Migration/Util/ToTriStateValue.php b/src/Migration/Util/ToTriStateValue.php index 43cbe4f8..eacfa054 100644 --- a/src/Migration/Util/ToTriStateValue.php +++ b/src/Migration/Util/ToTriStateValue.php @@ -1,12 +1,25 @@ type = $type; } /** @@ -16,8 +29,20 @@ public function __construct() */ protected function convert($value) { - $triStateService = Pdk::get(TriStateServiceInterface::class); + /** @var TriStateServiceInterface $service */ + $service = Pdk::get(TriStateServiceInterface::class); + + switch ($this->type) { + case TriStateService::TYPE_COERCED: + return $service->coerce($value); + + case TriStateService::TYPE_STRICT: + return $service->cast($value); + + case TriStateService::TYPE_STRING: + return $service->coerceString($value); + } - return $triStateService->cast($value); + return $value; } } diff --git a/src/Migration/Util/TransformValue.php b/src/Migration/Util/TransformValue.php index f2b1b8ad..0dac157a 100644 --- a/src/Migration/Util/TransformValue.php +++ b/src/Migration/Util/TransformValue.php @@ -4,7 +4,7 @@ namespace MyParcelNL\PrestaShop\Migration\Util; -final class TransformValue implements ValueModifierInterface +class TransformValue implements ValueModifierInterface { /** * @var callable-string|callable diff --git a/src/Pdk/Product/Repository/PdkProductRepository.php b/src/Pdk/Product/Repository/PdkProductRepository.php index 1b998de4..b0fef454 100644 --- a/src/Pdk/Product/Repository/PdkProductRepository.php +++ b/src/Pdk/Product/Repository/PdkProductRepository.php @@ -15,6 +15,7 @@ use MyParcelNL\PrestaShop\Entity\MyparcelnlProductSettings; use MyParcelNL\PrestaShop\Pdk\Base\Service\PsWeightService; use MyParcelNL\PrestaShop\Repository\PsProductSettingsRepository; +use MyParcelNL\Sdk\src\Support\Arr; use Product; class PdkProductRepository extends AbstractPdkPdkProductRepository @@ -68,7 +69,7 @@ public function __construct( */ public function getProduct($identifier): PdkProduct { - return $this->retrieve('product_' . $identifier, function () use ($identifier) { + return $this->retrieve((string) $identifier, function () use ($identifier) { $psProduct = new Product($identifier); $translate = static function (array $strings) { return $strings[Context::getContext()->language->id] ?? $strings[1] ?? reset($strings); @@ -95,14 +96,12 @@ public function getProduct($identifier): PdkProduct */ public function getProductSettings($identifier): ProductSettings { - return $this->retrieve("product_settings_$identifier", function () use ($identifier) { - /** @var \MyParcelNL\PrestaShop\Entity\MyparcelnlProductSettings $psProductSettings */ - $psProductSettings = $this->psProductRepository->findOneBy(['productId' => $identifier]); - $data = $psProductSettings ? $psProductSettings->toArray() : []; - $parameters = json_decode($data['data'] ?? '', true); + /** @var \MyParcelNL\PrestaShop\Entity\MyparcelnlProductSettings $psProductSettings */ + $psProductSettings = $this->productSettingsRepository->findOneBy(['productId' => $identifier]); - return new ProductSettings($parameters); - }); + $array = $psProductSettings ? $psProductSettings->toArray() : []; + + return new ProductSettings(Arr::get($array, 'data.settings', [])); } /** @@ -130,9 +129,19 @@ public function update(PdkProduct $product): void 'productId' => (int) $product->externalIdentifier, ], [ - 'data' => json_encode($product->settings->toArray()), + 'data' => json_encode($product->toStorableArray()), ] ); + + $this->save($product->externalIdentifier, $product); + } + + /** + * @return string + */ + protected function getKeyPrefix(): string + { + return 'product_'; } /** diff --git a/src/Service/ModuleService.php b/src/Service/ModuleService.php index 31d3e876..228e2437 100644 --- a/src/Service/ModuleService.php +++ b/src/Service/ModuleService.php @@ -114,6 +114,7 @@ public function install(): bool /** * @return void * @throws \MyParcelNL\PrestaShop\Pdk\Installer\Exception\InstallationException + * @noinspection PhpUnused */ public function registerHooks(): void { diff --git a/tests/Unit/Migration/Pdk/PdkProductSettingsMigrationTest.php b/tests/Unit/Migration/Pdk/PdkProductSettingsMigrationTest.php index e7c90608..976ea90b 100644 --- a/tests/Unit/Migration/Pdk/PdkProductSettingsMigrationTest.php +++ b/tests/Unit/Migration/Pdk/PdkProductSettingsMigrationTest.php @@ -5,9 +5,104 @@ namespace MyParcelNL\PrestaShop\Migration\Pdk; +use MyParcelNL\Pdk\App\Order\Contract\PdkProductRepositoryInterface; +use MyParcelNL\Pdk\Facade\Pdk; +use MyParcelNL\Pdk\Settings\Model\ProductSettings; +use MyParcelNL\Pdk\Shipment\Model\DeliveryOptions; +use MyParcelNL\Pdk\Types\Service\TriStateService; +use MyParcelNL\PrestaShop\Migration\AbstractLegacyPsMigration; +use MyParcelNL\PrestaShop\Tests\Mock\MockPsDb; use MyParcelNL\PrestaShop\Tests\Uses\UsesMockPsPdkInstance; +use Product; use function MyParcelNL\Pdk\Tests\usesShared; +use function MyParcelNL\PrestaShop\psFactory; usesShared(new UsesMockPsPdkInstance()); -it('migrates product settings to pdk', function () {})->skip(); +it('migrates product settings to pdk', function (array $productConfigurations, array $result) { + /** @var PdkProductRepositoryInterface $pdkProductRepository */ + $pdkProductRepository = Pdk::get(PdkProductRepositoryInterface::class); + + psFactory(Product::class) + ->withId(1) + ->store(); + + MockPsDb::insertRows( + AbstractLegacyPsMigration::LEGACY_TABLE_PRODUCT_CONFIGURATION, + $productConfigurations, + 'id_configuration' + ); + + /** @var \MyParcelNL\PrestaShop\Migration\Pdk\PdkOrderShipmentsMigration $migration */ + $migration = Pdk::get(PdkProductSettingsMigration::class); + $migration->up(); + $migration->up(); // done twice to test that it doesn't migrate product settings twice + + $pdkProduct = $pdkProductRepository->getProduct(1); + + expect($pdkProduct->settings->toStorableArray())->toEqual( + array_replace([ + ProductSettings::COUNTRY_OF_ORIGIN => TriStateService::INHERIT, + ProductSettings::CUSTOMS_CODE => TriStateService::INHERIT, + ProductSettings::DISABLE_DELIVERY_OPTIONS => TriStateService::INHERIT, + ProductSettings::DROP_OFF_DELAY => TriStateService::INHERIT, + ProductSettings::EXPORT_AGE_CHECK => TriStateService::INHERIT, + ProductSettings::EXPORT_HIDE_SENDER => TriStateService::INHERIT, + ProductSettings::EXPORT_INSURANCE => TriStateService::INHERIT, + ProductSettings::EXPORT_LARGE_FORMAT => TriStateService::INHERIT, + ProductSettings::EXPORT_ONLY_RECIPIENT => TriStateService::INHERIT, + ProductSettings::EXPORT_RETURN => TriStateService::INHERIT, + ProductSettings::EXPORT_SIGNATURE => TriStateService::INHERIT, + ProductSettings::FIT_IN_DIGITAL_STAMP => TriStateService::INHERIT, + ProductSettings::FIT_IN_MAILBOX => TriStateService::INHERIT, + ProductSettings::PACKAGE_TYPE => TriStateService::INHERIT, + ], $result) + ); +})->with([ + 'all options' => [ + 'rows' => [ + ['id_product' => 1, 'name' => 'MYPARCELNL_AGE_CHECK', 'value' => '1'], + ['id_product' => 1, 'name' => 'MYPARCELNL_CUSTOMS_CODE', 'value' => '123'], + ['id_product' => 1, 'name' => 'MYPARCELNL_CUSTOMS_FORM', 'value' => 'Add'], + ['id_product' => 1, 'name' => 'MYPARCELNL_CUSTOMS_ORIGIN', 'value' => 'DE'], + ['id_product' => 1, 'name' => 'MYPARCELNL_INSURANCE', 'value' => '1'], + ['id_product' => 1, 'name' => 'MYPARCELNL_PACKAGE_FORMAT', 'value' => '2'], + ['id_product' => 1, 'name' => 'MYPARCELNL_PACKAGE_TYPE', 'value' => '2'], + ['id_product' => 1, 'name' => 'MYPARCELNL_RECIPIENT_ONLY', 'value' => '1'], + ['id_product' => 1, 'name' => 'MYPARCELNL_RETURN_PACKAGE', 'value' => '1'], + ['id_product' => 1, 'name' => 'MYPARCELNL_SIGNATURE_REQUIRED', 'value' => '1'], + ], + + 'result' => [ + ProductSettings::PACKAGE_TYPE => DeliveryOptions::PACKAGE_TYPE_MAILBOX_NAME, + ProductSettings::COUNTRY_OF_ORIGIN => 'DE', + ProductSettings::CUSTOMS_CODE => '123', + ProductSettings::EXPORT_AGE_CHECK => TriStateService::ENABLED, + ProductSettings::EXPORT_INSURANCE => TriStateService::ENABLED, + ProductSettings::EXPORT_LARGE_FORMAT => TriStateService::ENABLED, + ProductSettings::EXPORT_ONLY_RECIPIENT => TriStateService::ENABLED, + ProductSettings::EXPORT_RETURN => TriStateService::ENABLED, + ProductSettings::EXPORT_SIGNATURE => TriStateService::ENABLED, + ], + ], + + 'changes country of origin "AF" to inherit' => [ + 'rows' => [['id_product' => 1, 'name' => 'MYPARCELNL_CUSTOMS_ORIGIN', 'value' => 'AF']], + 'result' => [], + ], + + 'large format: 1' => [ + 'rows' => [['id_product' => 1, 'name' => 'MYPARCELNL_PACKAGE_FORMAT', 'value' => '1']], + 'result' => [ProductSettings::EXPORT_LARGE_FORMAT => TriStateService::DISABLED], + ], + + 'large format: 2' => [ + 'rows' => [['id_product' => 1, 'name' => 'MYPARCELNL_PACKAGE_FORMAT', 'value' => '2']], + 'result' => [ProductSettings::EXPORT_LARGE_FORMAT => TriStateService::ENABLED], + ], + + 'large format: 3' => [ + 'rows' => [['id_product' => 1, 'name' => 'MYPARCELNL_PACKAGE_FORMAT', 'value' => '3']], + 'result' => [], + ], +]); diff --git a/tests/factories/ProductFactory.php b/tests/factories/ProductFactory.php index ca890001..c61c3016 100644 --- a/tests/factories/ProductFactory.php +++ b/tests/factories/ProductFactory.php @@ -2,14 +2,26 @@ declare(strict_types=1); +use MyParcelNL\Pdk\Tests\Factory\Contract\FactoryInterface; use MyParcelNL\PrestaShop\Tests\Factory\AbstractPsObjectModelFactory; /** + * @see \ProductCore + * @method self withName(array $names) */ -final class LangFactory extends AbstractPsObjectModelFactory +final class ProductFactory extends AbstractPsObjectModelFactory { + protected function createDefault(): FactoryInterface + { + return parent::createDefault() + ->withName([ + 1 => 'Test product', + 2 => 'Test product 2', + ]); + } + protected function getObjectModelClass(): string { - return Lang::class; + return Product::class; } } diff --git a/tests/mock_class_map.php b/tests/mock_class_map.php index 88f26782..9d44a8c5 100644 --- a/tests/mock_class_map.php +++ b/tests/mock_class_map.php @@ -175,6 +175,14 @@ abstract class OrderStateCore extends ObjectModel final class OrderState extends OrderStateCore { } +/** @see \ProductCore */ +abstract class ProductCore extends ObjectModel +{ + protected $hasCustomIdKey = true; +} + +final class Product extends ProductCore { } + /** @see \RangePriceCore */ abstract class RangePriceCore extends MockPsRangeObjectModel { }