Finally, it seems to be a bug in Prestashop core. Customization fields don't seem to be configured correctly for multi-shop Prestashop.
For anyone facing the same issue, you can fix this bug by patching the core Prestashop files:
src/Adapter/Product/AdminProductWrapper.php
// make function changes public function processProductCustomization($product, $data) { $customization_ids = []; if ($data) { foreach ($data as $customization) { $customization_ids[] = (int) $customization['id_customization_field']; } } $shopList = Shop::getContextListShopID(); $usedCustomizationIds = $product->getUsedCustomizationFieldsIds(); $usedCustomizationIds = array_column($usedCustomizationIds, 'index'); $usedCustomizationIds = array_map('intval', $usedCustomizationIds); $usedCustomizationIds = array_unique(array_merge($usedCustomizationIds, $customization_ids), SORT_REGULAR); /* Update the customization fields to be deleted in the next step if not used */ $product->softDeleteCustomizationFieldsByShop($usedCustomizationIds, $shopList); // only from current shop //remove customization field langs for current context shops $productCustomization = $product->getCustomizationFieldIdsByShop($shopList); // for current shop only $toDeleteCustomizationIds = []; if($productCustomization){ foreach ($productCustomization as $customizationFiled) { if (!in_array((int) $customizationFiled['id_customization_field'], $usedCustomizationIds)) { $toDeleteCustomizationIds[] = (int) $customizationFiled['id_customization_field']; } //if the customization_field is still in use, only delete the current context shops langs, if (in_array((int) $customizationFiled['id_customization_field'], $customization_ids)) { Customization::deleteCustomizationFieldLangByShop($customizationFiled['id_customization_field'], $shopList); } } } //remove unused customization for the product $product->deleteUnusedCustomizationFields($toDeleteCustomizationIds);
Finally add the following functions to:
classes/Product.php
public function getCustomizationFieldIdsByShop(array $shopIds) { if(count($shopIds) == 0) return []; if (!Customization::isFeatureActive()) { return []; } return Db::getInstance()->executeS(' SELECT cf.`id_customization_field`, cf.`type`, cf.`required` FROM `' . _DB_PREFIX_ . 'customization_field` cf LEFT JOIN `' . _DB_PREFIX_ . 'customization_field_lang` cfl ON cf.`id_customization_field`=cfl.`id_customization_field` WHERE cf.`id_product` = ' . (int) $this->id.' AND cfl.`id_shop` in ('.implode(", ", array_map('intval', $shopIds)).') GROUP BY cf.`id_customization_field`'); } /** * Update the customization fields to be deleted if not used. * * @param array $customizationIds - Array of excluded customization fields IDs * * @param array $shopList - Array of shop ids * * @return bool * * @throws PrestaShopDatabaseException */ public function softDeleteCustomizationFieldsByShop($customizationIds, $shopList) { $return = true; $updateQuery = 'UPDATE `' . _DB_PREFIX_ . 'customization_field` cf SET cf.`is_deleted` = 1 WHERE cf.`id_product` = ' . (int) $this->id . ' AND cf.`is_deleted` = 0 '; if (is_array($customizationIds) && !empty($customizationIds)) { $updateQuery .= 'AND cf.`id_customization_field` NOT IN (' . implode(',', array_map('intval', $customizationIds)) . ')'; } if (is_array($shopList) && !empty($shopList)) { $updateQuery .= ' AND (SELECT cfl.`id_shop` from `' . _DB_PREFIX_ . 'customization_field_lang` cfl where `id_customization_field` = cf.`id_customization_field` LIMIT 1) IN (' . implode(',', array_map('intval', $shopList)) . ')'; } $return &= Db::getInstance()->execute($updateQuery); if (!$return) { throw new PrestaShopDatabaseException('An error occurred while soft deletion the customization fields'); } return $return; }