Files
kupshop/bundles/KupShop/AdminBundle/Util/SplitVariationsToProductUtil.php
2025-08-02 16:30:27 +02:00

321 lines
11 KiB
PHP

<?php
namespace KupShop\AdminBundle\Util;
use Query\Operator;
class SplitVariationsToProductUtil
{
/**
* @var ProductUtils
*/
protected $productUtils;
/**
* @required
*/
public function setProductUtils(ProductUtils $productUtils): void
{
$this->productUtils = $productUtils;
}
/**
* Splits variations by choices label. Removes label from a product, creates n-1 new copies of the product for each n label values
* and splits the variations to them. If there's no label left on new products after operation, it removes last variation from products and
* copies data of variation to product.
*
* @param $idProduct int
* @param $idLabel int
* @param false $nameAppend
*/
public function splitByLabel($idProduct, $idLabel, $nameAppend = false)
{
$productsCreated = [];
sqlGetConnection()->transactional(
function () use ($idProduct, $idLabel, $nameAppend, &$productsCreated) {
$productLabelsVariations = sqlFetchAll(
sqlQueryBuilder()->select("GROUP_CONCAT(pv.id SEPARATOR ',') variations, pvc.id_value, pvcv.value")
->from('products_variations', 'pv')
->innerJoin('pv', 'products_variations_combination', 'pvc', 'pvc.id_variation = pv.id')
->innerJoin('pvc', 'products_variations_choices_values', 'pvcv', 'pvcv.id = pvc.id_value')
->where(
Operator::equals(
[
'pv.id_product' => $idProduct,
'pvc.id_label' => $idLabel,
]
)
)
->groupBy('pvc.id_value')
->execute(),
'id_value'
);
$this->removeLabelFromProduct($idProduct, $idLabel);
$productLabels = $this->getChoicesLabels($idProduct);
$originalProductLabel = array_key_last($productLabelsVariations);
foreach ($productLabelsVariations as $idLabel => $productLabelVariations) {
$variations = explode(',', $productLabelVariations['variations']);
$newIdProduct = $idProduct;
if ($originalProductLabel != $idLabel) {
$newIdProduct = $this->productUtils->duplicateProduct($idProduct);
$productsCreated[] = $newIdProduct;
$this->productUtils->copyProductData($idProduct, $newIdProduct, ['_all_' => true, 'pricelist' => 'products', 'photos' => 'products', 'variations' => false]);
foreach ($productLabels ?? [] as $label) {
sqlQueryBuilder()->insert('products_variations_choices_categorization')
->directValues(
[
'id_product' => $newIdProduct,
'id_label' => $label['id_label'],
'list_order' => $label['list_order'],
]
)
->execute();
}
$this->updateVariationProductId($variations, $newIdProduct);
}
if ($productLabels) {
$this->fixProductPrice($newIdProduct);
}
if ($nameAppend) {
$this->appendProductTitle($newIdProduct, $productLabelVariations['value']);
}
}
\Variations::recalcInStore();
if (!$productLabels) {
foreach ($productLabelsVariations as $productLabelVariations) {
$variations = explode(',', $productLabelVariations['variations']);
if (count($variations) == 1) {
$this->updateProductFromVariation($variations[0]);
} else {
throw new \Exception('A product without label has more than one variation.');
}
}
}
}
);
return $productsCreated;
}
private function getChoicesLabels($idProduct)
{
return sqlQueryBuilder()->select('id_label, list_order')
->from('products_variations_choices_categorization')
->where(
Operator::equals(
[
'id_product' => $idProduct,
]
)
)
->execute()->fetchAll();
}
/**
* Moves data to a product from its last variation and removes the variation.
*/
private function updateProductFromVariation($idVariation)
{
$fields = [
'id_product',
'code',
'price',
'ean',
'in_store',
'delivery_time',
'figure',
'updated',
'date_added',
'width',
'height',
'depth',
];
if (findModule(\Modules::PRODUCTS, \Modules::SUB_PRICE_BUY)) {
$fields[] = 'price_buy';
}
if (findModule(\Modules::PRODUCTS, \Modules::SUB_NOTE)) {
$fields[] = 'note';
}
if (findModule(\Modules::BONUS_PROGRAM)) {
$fields[] = 'bonus_points';
}
if (findModule('products', 'weight')) {
$fields[] = 'weight';
}
if (findModule(\Modules::MISSING_PRODUCTS)) {
$fields[] = 'in_store_min';
}
$variationFields = sqlQueryBuilder()
->select(implode(',', $fields))
->from('products_variations')
->where(Operator::equals(['id' => $idVariation]))
->execute()->fetch();
$idProduct = $variationFields['id_product'];
unset($variationFields['id_product']);
$this->cleanPriceListVariations($idProduct);
$this->removeVariationForeignKeys($idVariation);
sqlQueryBuilder()->delete('products_variations')
->where(Operator::equals(['id' => $idVariation]))
->execute();
sqlQueryBuilder()->update('products')
->directValues(
array_filter(
$variationFields,
function ($field) {
return !empty($field);
}
)
)
->where(Operator::equals(['id' => $idProduct]))
->execute();
}
/**
* Updates product ID of variations to given product ID in multiple places.
*
* @param $variations array
* @param $newIdProduct int
*/
private function updateVariationProductId($variations, $newIdProduct)
{
$update = function ($table, $idProductField = 'id_product', $idVariationField = 'id_variation') use ($variations, $newIdProduct) {
sqlQueryBuilder()->update($table)
->directValues([$idProductField => $newIdProduct])
->where(Operator::inStringArray($variations, $idVariationField))
->execute();
};
$update('products_variations', 'id_product', 'id');
$this->productUtils->updateTablesWithCallback($update);
}
private function cleanPriceListVariations($idProduct): void
{
if (findModule(\Modules::PRICELISTS)) {
sqlQueryBuilder()
->delete('pricelists_products')
->andWhere(Operator::equals(['id_product' => $idProduct]))
->andWhere(Operator::isNull('id_variation'))
->execute();
}
}
/**
* Sets null to all foreign keys of a variation.
*/
private function removeVariationForeignKeys($idVariation)
{
$update = function ($table, $idProductField = null, $idVariationField = 'id_variation') use ($idVariation) {
sqlQueryBuilder()->update($table)
->directValues([$idVariationField => null])
->where(Operator::equals([$idVariationField => $idVariation]))
->execute();
};
$this->productUtils->updateTablesWithCallback($update);
}
/**
* Sets the most frequent price of variations to a product and sets null to the variations with that price.
* If the variations already have null price before operation, then nothing happens.
*
* @param $idProduct int
*/
public function fixProductPrice($idProduct)
{
$hasNullPriceVariation = sqlQueryBuilder()->select('id')
->from('products_variations', 'pv')
->where(Operator::equals(['id_product' => $idProduct]))
->andWhere(Operator::equalsNullable(['price' => null]))
->execute()->fetch();
if ($hasNullPriceVariation) {
return;
}
$topPrice = sqlQueryBuilder()->select("price, GROUP_CONCAT(id SEPARATOR ',') variations")
->from('products_variations', 'pv')
->where(Operator::equals(['id_product' => $idProduct]))
->groupBy('price')
->orderBy('price')
->setMaxResults(1)
->execute()->fetch();
sqlQueryBuilder()->update('products')
->directValues(['price' => $topPrice['price']])
->where(Operator::equals(['id' => $idProduct]))
->execute();
sqlQueryBuilder()->update('products_variations')
->set('price', 'null')
->where(Operator::inStringArray(explode(',', $topPrice['variations']), 'id'))
->execute();
}
/**
* Appends value to the title of product.
*
* @param $idProduct int
* @param $value string
*/
private function appendProductTitle($idProduct, $value)
{
sqlQueryBuilder()->update('products')
->set('title', "CONCAT(title, ' - ', :append)")
->where(Operator::equals(['id' => $idProduct]))
->setParameter('append', $value)
->execute();
}
/**
* Removes label from a product and its variations.
*
* @param $idProduct int
* @param $idLabel int
*/
private function removeLabelFromProduct($idProduct, $idLabel)
{
$variations = sqlFetchAll(
sqlQueryBuilder()->select('id')
->from('products_variations')
->where(Operator::equals(['id_product' => $idProduct]))
->execute(),
['id' => 'id']
);
sqlQueryBuilder()->delete('products_variations_combination')
->where(Operator::equals(['id_label' => $idLabel]))
->andWhere(Operator::inStringArray(array_values($variations), 'id_variation'))
->execute();
sqlQueryBuilder()->delete('products_variations_choices_categorization')
->where(
Operator::equals(
[
'id_product' => $idProduct,
'id_label' => $idLabel,
]
)
)
->execute();
}
}