getCategory(); } public function preview(ModelType $modelType, array $newStructure): SyncPreviewResult { $products = $this->em->getRepository(Product::class)->findBy(['typeProduct' => $modelType]); $existingFields = $this->em->getRepository(CustomField::class)->findBy( ['typeProduct' => $modelType], ['orderIndex' => 'ASC'] ); $proposedFields = $newStructure['customFields'] ?? []; // Map existing fields by orderIndex $existingByOrder = []; foreach ($existingFields as $field) { $existingByOrder[$field->getOrderIndex()] = $field; } // Map proposed fields by orderIndex (falls back to array index) $proposedByOrder = []; foreach ($proposedFields as $i => $pf) { $order = $pf['orderIndex'] ?? $i; $proposedByOrder[$order] = $pf; } $addedFields = 0; $deletedFields = 0; $modifiedFields = 0; // New fields (in proposed but not in existing) foreach ($proposedByOrder as $orderIndex => $pf) { if (!isset($existingByOrder[$orderIndex])) { ++$addedFields; } elseif ($existingByOrder[$orderIndex]->getType() !== ($pf['type'] ?? $pf['value']['type'] ?? null)) { ++$modifiedFields; } } // Deleted fields (in existing but not in proposed) foreach ($existingByOrder as $orderIndex => $ef) { if (!isset($proposedByOrder[$orderIndex])) { ++$deletedFields; } } $itemCount = count($products); return new SyncPreviewResult( modelTypeId: $modelType->getId(), category: 'product', itemCount: $itemCount, additions: ['customFieldValues' => $addedFields * $itemCount], deletions: ['customFieldValues' => $deletedFields * $itemCount], modifications: ['customFieldValues' => $modifiedFields * $itemCount], ); } public function execute(ModelType $modelType, SyncConfirmation $confirmation): SyncExecutionResult { $products = $this->em->getRepository(Product::class)->findBy(['typeProduct' => $modelType]); $customFields = $this->em->getRepository(CustomField::class)->findBy( ['typeProduct' => $modelType], ['orderIndex' => 'ASC'] ); $addedValues = 0; $deletedValues = 0; $modifiedValues = 0; $itemsUpdated = 0; foreach ($products as $product) { $changed = false; // Get existing custom field values for this product $existingValues = $this->em->getRepository(CustomFieldValue::class)->findBy([ 'product' => $product, ]); // Map existing values by custom field ID $existingByFieldId = []; foreach ($existingValues as $cfv) { $existingByFieldId[$cfv->getCustomField()->getId()] = $cfv; } // For each custom field defined on the model type, ensure a value exists foreach ($customFields as $cf) { if (!isset($existingByFieldId[$cf->getId()])) { // Create missing custom field value $cfv = new CustomFieldValue(); $cfv->setCustomField($cf); $cfv->setProduct($product); $cfv->setValue(''); $this->em->persist($cfv); ++$addedValues; $changed = true; } } // Delete orphaned values if confirmDeletions if ($confirmation->confirmDeletions) { $fieldIds = array_map(fn (CustomField $cf) => $cf->getId(), $customFields); foreach ($existingValues as $cfv) { if (!in_array($cfv->getCustomField()->getId(), $fieldIds, true)) { $this->em->remove($cfv); ++$deletedValues; $changed = true; } } } if ($changed) { $product->incrementVersion(); ++$itemsUpdated; } } $this->em->flush(); return new SyncExecutionResult( itemsUpdated: $itemsUpdated, additions: ['customFieldValues' => $addedValues], deletions: ['customFieldValues' => $deletedValues], modifications: ['customFieldValues' => $modifiedValues], ); } }