Skip to content

Commit

Permalink
Merge pull request #4 from techdivision/PAC-894
Browse files Browse the repository at this point in the history
PAC-894 Add clean-up for grouped products
  • Loading branch information
kenza-ya authored Jul 29, 2024
2 parents 96f5209 + 3adc480 commit 59fa410
Show file tree
Hide file tree
Showing 9 changed files with 439 additions and 3 deletions.
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
# Version 20.0.1

## Bugfixes

* refactoring clean-up-links and clean-up-variants to remove old data from relation table

# Version 20.0.0

## Features
Expand Down Expand Up @@ -218,4 +224,4 @@

## Features

* Initial Release
* Initial Release
61 changes: 61 additions & 0 deletions src/Actions/Processors/ProductRelationDeleteProcessor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php
/**
* Copyright (c) 2024 TechDivision GmbH <info@techdivision.com> - TechDivision GmbH
* All rights reserved
*
* This product includes proprietary software developed at TechDivision GmbH, Germany
* For more information see https://www.techdivision.com
*
* To obtain a valid license for using this software, please contact us at
* license@techdivision.com
*/
declare(strict_types=1);

namespace TechDivision\Import\Product\Grouped\Actions\Processors;

use TechDivision\Import\Dbal\Collection\Actions\Processors\AbstractBaseProcessor;
use TechDivision\Import\Product\Grouped\Utils\SqlStatementKeys;
use TechDivision\Import\Product\Utils\MemberNames;

/**
* @copyright Copyright (c) 2024 TechDivision GmbH <info@techdivision.com> - TechDivision GmbH
* @link http://www.techdivision.com
* @author MET <met@techdivision.com>
*/
class ProductRelationDeleteProcessor extends AbstractBaseProcessor
{
/**
* Delete all relations that are not imported.
*
* @param array $row The row to persist
* @param string|null $name The name of the prepared statement that has to be executed
* @param string|null $primaryKeyMemberName The primary key member name of the entity to use
*
* @return void
*/
public function execute($row, $name = null, $primaryKeyMemberName = null): void
{
// load the SKUs from the row
$skus = $row[MemberNames::SKU];

// make sure we've an array
if (!is_array($skus)) {
$skus = [$skus];
}

// all SKUs that should NOT be deleted
$vals = implode(',', $skus);

// concatenate the SKUs as comma separated SQL string
$vals = str_replace(',', "','", sprintf("'%s'", $vals));

// replace the placeholders
$sql = str_replace(
[':parent_id', ':skus'],
[$row[MemberNames::PARENT_ID], $vals],
$this->loadStatement(SqlStatementKeys::DELETE_PRODUCT_RELATION)
);

$this->getConnection()->query($sql);
}
}
196 changes: 196 additions & 0 deletions src/Observers/CleanUpGroupedProductRelationObserver.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
<?php
/**
* Copyright (c) 2024 TechDivision GmbH <info@techdivision.com> - TechDivision GmbH
* All rights reserved
*
* This product includes proprietary software developed at TechDivision GmbH, Germany
* For more information see https://www.techdivision.com
*
* To obtain a valid license for using this software, please contact us at
* license@techdivision.com
*/
declare(strict_types=1);

namespace TechDivision\Import\Product\Grouped\Observers;

use Exception;
use TechDivision\Import\Observers\StateDetectorInterface;
use TechDivision\Import\Product\Grouped\Services\ProductGroupedProcessorInterface;
use TechDivision\Import\Product\Grouped\Utils\ColumnKeys;
use TechDivision\Import\Product\Grouped\Utils\ProductTypes;
use TechDivision\Import\Product\Observers\AbstractProductImportObserver;
use TechDivision\Import\Product\Utils\ConfigurationKeys;
use TechDivision\Import\Product\Utils\MemberNames;

/**
* @copyright Copyright (c) 2024 TechDivision GmbH <info@techdivision.com> - TechDivision GmbH
* @link http://www.techdivision.com
* @author MET <met@techdivision.com>
*/
class CleanUpGroupedProductRelationObserver extends AbstractProductImportObserver
{
/**
* @var ProductGroupedProcessorInterface
*/
protected ProductGroupedProcessorInterface $productGroupedProcessor;

/**
* Initialize the observer with the passed grouped product data processor instance.
*
* @param ProductGroupedProcessorInterface $productGroupedProcessor The grouped product processor instance
* @param StateDetectorInterface|null $stateDetector The state detector instance to use
*/
public function __construct(
ProductGroupedProcessorInterface $productGroupedProcessor,
StateDetectorInterface $stateDetector = null
) {
// pass the state detector to the parent constructor
parent::__construct($stateDetector);

// initialize the grouped product processor instance
$this->productGroupedProcessor = $productGroupedProcessor;
}

/**
* Return's the grouped product processor instance.
*
* @return ProductGroupedProcessorInterface The grouped product processor instance
*/
protected function getProductGroupedProcessor(): ProductGroupedProcessorInterface
{
return $this->productGroupedProcessor;
}

/**
* Process the observer's business logic.
*
* @return void
* @throws Exception
*/
protected function process()
{
// query whether or not we've found a grouped product
if ($this->getValue(ColumnKeys::PRODUCT_TYPE) !== ProductTypes::GROUPED) {
return;
}

$subject = $this->getSubject();
$subjectConfiguration = $subject->getConfiguration();

if ($subjectConfiguration->hasParam(ConfigurationKeys::CLEAN_UP_LINKS)
&& $subjectConfiguration->getParam(ConfigurationKeys::CLEAN_UP_LINKS)) {
$this->cleanUpGrouped();

$subject->getSystemLogger()->info(
$subject->appendExceptionSuffix(
sprintf(
'Successfully clean up grouped product with SKU "%s"',
$this->getValue(ColumnKeys::SKU)
)
)
);
}
}

/**
* Search for child products in the artefacts and check for differences in the database.
* Remove entries in DB that do not exist in artefact.
*
* @return void
* @throws Exception Is thrown if all the child products cannot be deleted
*/
protected function cleanUpGrouped()
{
// load the available artefacts from the subject
$subject = $this->getSubject();
$artefacts = $subject->getArtefacts();

// return, if we do NOT have any grouped product artefacts
if (!isset($artefacts[ProductGroupedObserver::ARTEFACT_TYPE])) {
return;
}

// load the entity ID of the parent product
$parentIdForArtefacts = $this->getLastEntityId();

// return, if we do NOT have any artefacts for the actual entity ID
if (!isset($artefacts[ProductGroupedObserver::ARTEFACT_TYPE][$parentIdForArtefacts])) {
return;
}

// initialize the array with the SKUs of the child IDs and the attribute codes
$actualGrouped = [];

// load the grouped product artefacts for the actual entity ID
$allGrouped = $artefacts[ProductGroupedObserver::ARTEFACT_TYPE][$parentIdForArtefacts];

// iterate over the artefacts with the grouped product data
foreach ($allGrouped as $groupedData) {
// add the child SKU to the array
$actualGrouped[] = $groupedData[ColumnKeys::GROUPED_CHILD_SKU];
}

// load the row/entity ID of the parent product
$parentId = $this->getLastPrimaryKey();

try {
$this->cleanUpGroupedRelation($parentId, $actualGrouped);
} catch (Exception $e) {
// log a warning if debug mode has been enabled
if ($subject->isDebugMode()) {
$subject->getSystemLogger()->critical($subject->appendExceptionSuffix($e->getMessage()));
} else {
throw $e;
}
}
}

/**
* Delete not exists import relations from database.
*
* @param int $parentProductId The ID of the parent product
* @param array $childData The array of child products
*
* @return void
* @throws Exception
*/
protected function cleanUpGroupedRelation(int $parentProductId, array $childData)
{
// we don't want to delete everything
if (empty($childData)) {
return;
}

// load the SKU of the parent product
$parentSku = $this->getValue(ColumnKeys::SKU);

// remove the old child products from the database
$this->getProductGroupedProcessor()->deleteProductRelation(
[
MemberNames::PARENT_ID => $parentProductId,
MemberNames::SKU => $childData,
]
);

// log a debug message that the image has been removed
$subject = $this->getSubject();
$subject->getSystemLogger()->info(
$subject->appendExceptionSuffix(
sprintf(
'Successfully clean up relations for product with SKU "%s"',
$parentSku
)
)
);
}

/**
* Return's the PK to create the product => child relation.
*
* @return int The PK to create the relation with
*/
protected function getLastPrimaryKey(): int
{
return (int)$this->getLastEntityId();
}
}
54 changes: 54 additions & 0 deletions src/Repositories/SqlStatementRepository.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php
/**
* Copyright (c) 2024 TechDivision GmbH <info@techdivision.com> - TechDivision GmbH
* All rights reserved
*
* This product includes proprietary software developed at TechDivision GmbH, Germany
* For more information see https://www.techdivision.com
*
* To obtain a valid license for using this software, please contact us at
* license@techdivision.com
*/
declare(strict_types=1);

namespace TechDivision\Import\Product\Grouped\Repositories;

use IteratorAggregate;
use TechDivision\Import\Dbal\Utils\SqlCompilerInterface;
use TechDivision\Import\Product\Grouped\Utils\SqlStatementKeys;

/**
* @copyright Copyright (c) 2024 TechDivision GmbH <info@techdivision.com> - TechDivision GmbH
* @link http://www.techdivision.com
* @author MET <met@techdivision.com>
*/
class SqlStatementRepository extends \TechDivision\Import\Product\Repositories\SqlStatementRepository
{
/**
* The SQL statements.
*
* @var array
*/
private array $statements = [
SqlStatementKeys::DELETE_PRODUCT_RELATION =>
'DELETE
FROM ${table:catalog_product_relation}
WHERE parent_id = :parent_id
AND child_id
NOT IN (SELECT `entity_id` FROM ${table:catalog_product_entity} WHERE `sku` IN (:skus))',
];

/**
* Initializes the SQL statement repository with the primary key and table prefix utility.
*
* @param IteratorAggregate<SqlCompilerInterface> $compilers The array with the compiler instances
*/
public function __construct(IteratorAggregate $compilers)
{
// pass primary key + table prefix utility to parent instance
parent::__construct($compilers);

// compile the SQL statements
$this->compile($this->statements);
}
}
27 changes: 27 additions & 0 deletions src/Services/ProductGroupedProcessor.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,20 @@ class ProductGroupedProcessor implements ProductGroupedProcessorInterface
*/
protected $connection;

/**
* The repository to access product relations.
*
* @var \TechDivision\Import\Product\Repositories\ProductRelationRepositoryInterface
*/
protected $productRelationRepository;

/**
* The action for product relation CRUD methods.
*
* @var \TechDivision\Import\Dbal\Actions\ActionInterface
*/
protected $productRelationAction;

/**
* Initialize the processor with the necessary assembler and repository instances.
*
Expand Down Expand Up @@ -189,4 +203,17 @@ public function persistProductRelation($productRelation, $name = null)
{
return $this->getProductRelationAction()->persist($productRelation, $name);
}

/**
* Deletes the passed product relation data.
*
* @param array $row The product relation to be deleted
* @param string|null $name The name of the prepared statement that has to be executed
*
* @return void
*/
public function deleteProductRelation(array $row, string $name = null): void
{
$this->getProductRelationAction()->delete($row, $name);
}
}
9 changes: 9 additions & 0 deletions src/Services/ProductGroupedProcessorInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,13 @@
*/
interface ProductGroupedProcessorInterface extends ProductProcessorInterface, ProductRelationAwareProcessorInterface
{
/**
* Deletes the passed product relation data.
*
* @param array $row The product relation to be deleted
* @param null|string $name The name of the prepared statement that has to be executed
*
* @return void
*/
public function deleteProductRelation(array $row, string $name = null): void;
}
Loading

0 comments on commit 59fa410

Please sign in to comment.